Flutter和Android中的Intent

Intent
Intent 是一個消息傳遞對象,使用它可以向其他Android組件請求操作,其基本用途如:啓動 Activity、啓動服務、傳遞廣播等。
Flutter中雖然沒有Intent的概念,但可以通過 Flutter的intent插件 來集成Intent的功能,其頁面跳轉等的實現也與Android的intent類似。
最常見的就是點擊按鈕跳轉頁面的事件,下文中的Intent也可以添加在點擊事件中查看效果,創建點擊事件可以看這裏

一、Android

(一)Activity

1、顯式Intent

(1)最簡單常用的方式
Intent intent = new Intent(this, SecondActivity.class);  
startActivity(intent); 

(2)setComponent()方法
//第一個參數是被打開APP的包名,第二個參數是SecondActivity這個頁面所在的全路徑、也可以理解爲包名+activity名
ComponentName cName = new ComponentName("com.example.test", "com.example.test.SecondActivity");
Intent intent = new Intent();
intent.setComponent(cName);
//上述代碼也可以是intent.setComponent(new ComponentName("com.example.test","com.example.test.SecondActivity"));
startActivity(intent);
                
(3)setClass()/setClassName()方法
Intent intent = new Intent();    
intent.setClass(this, SecondActivity.class);  
//或者intent.setClassName(this, "com.example.app.SecondActivity");  
//或者intent.setClassName(this.getPackageName(),"com.example.app.SecondActivity");            
startActivity(intent);  

2、隱式Intent

隱式Intent,即不明確指定啓動哪個Activity,而是通過設置action、category、data等抽象信息,讓系統來找出合適的Activity。

(1)先在AndroidManifest.xml中找到你想要調用的Activity(被Intent調用的),然後添加或修改類似下列<intent-filter>的內容:
<activity  
    android:name="com.example.app.SecondActivity">  
    <intent-filter>  
        <action android:name="Second"/>  //爲了防止應用程序之間互相影響,一般的標準命名方式是包名+Action名,例如這裏就應該改成"com.example.test.SecondActivity"。
        <category android:name="android.intent.category.DEFAULT"/>  //至少必須要有這個category才能響應
    </intent-filter>  
</activity>  

(2)Intent創建
Intent intent = new Intent();  
intent.setAction("Second");  
startActivity(intent);  

(3)每個Intent只能指定一個action,但卻可以指定多個category,需要在上述(1)(2)的AndroidManifest.xml和Intent中修改如下:
<activity  
    android:name="com.example.app.SecondActivity">  
    <intent-filter>  
        <action android:name="Second"/>
        <category android:name="android.intent.category.DEFAULT"/> 
        <category android:name="com.example.text.addcategory.ADD"/>  
    </intent-filter>  
</activity>  

Intent intent = new Intent();  
intent.setAction("Second");  
intent.addCategory("com.example.text.addcategory.ADD");
startActivity(intent);  

3、Intent的更多用法

Intent對象大致包括7個屬性:Action(動作)、Category(類別)、Data(數據)、Type(數據類型)、Component(組件)、Extra(擴展信息)、Flag(標誌位),可以用於在不同頁面之間傳遞參數、調用手機系統內其他應用程序、打開網頁等等很多功能。

Activity的Action類型 作用
ACTION_MAIN 表示程序入口
ACTION_DAIL 跳轉至撥號界面
ACTION_CALL 直接撥號(需要先添加權限)
Broadcast的Action類型 作用
ACTION_TIME_TICK 當前時間改變,並即時發送時間,只能通過系統發送。調用格式"android.intent.action.TIME_TICK"
ACTION_TIME_CHENGED 設置時間。調用格式"android.intent.action.TIME_SET"

(更多屬性的類型和用法可查看這裏

(二)IntentService

Service一般用於處理一些後臺進程(不是線程)。IntentService則是繼承並處理異步請求的一個類,可以看做Service和HandlerThread的結合體,在完成了使命之後會自動停止,適合需要在工作線程處理UI無關任務、後臺耗時較多的場景。

使用步驟

1、自定義LocalIntentService繼承自IntentService。
2、實現onHandleIntent(), 用於實現任務邏輯。

public class MyIntentService extends IntentService{
    public MyIntentService(String name) {
        super(name);
    }
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getStringExtra("task_action");
        Log.d("IntentService", "receive task :" + action);
        SystemClock.sleep(2000); //即使第一個任務休眠,後續的任務也會等待其執行完畢再執行
        if("com.example.action.TASK1".equals(action)){
            Log.d("IntentService", "handle task :" + action);//運行時可以在控制檯看到Log打印的日誌
        }
    }
}

3、啓動IntentService。

Intent intentService = new Intent(this, MyIntentService.class);
intentService.putExtra("tast_action", "com.example.action.TASK1");
startService(service);

intentService.putExtra("tast_action", "com.example.action.TASK2");
startService(service);

intnetService.putExtra("tast_action", "com.example.action.TASK3");
startService(service);

二、Flutter

Flutter不具有Intents的概念,但如果需要的話,Flutter可以通過Native整合來觸發Intents,也可以集成Intent插件來調用外部組件。

(一)路由與導航

要在Flutter中切換屏幕,可以訪問路由以繪製新的Widget。 管理多個屏幕有兩個核心概念和類:Route 和 Navigator。Route是應用程序的“屏幕”或“頁面”的抽象(可以認爲是Activity), Navigator是管理Route的Widget。Navigator可以通過push和pop route以實現頁面切換。

和Android在AndroidManifest.xml中聲明Activitiy類似,在Flutter中,可以將具有指定Route的Map傳遞到頂層MaterialApp實例:

void main() {
  runApp(new MaterialApp(
    home: new MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => new MyPage(title: 'page A'),
      '/b': (BuildContext context) => new MyPage(title: 'page B'),
      '/c': (BuildContext context) => new MyPage(title: 'page C'),
    },
  ));
}

然後,就可以在需要的地方通過Navigator來切換到命名路由的頁面:

Navigator.of(context).pushNamed('/b');

(二)在Flutter中處理來自外部應用程序傳入的Intents

Flutter可以通過直接與Android層通信並請求共享的數據來處理來自Android的Intents。

在這個例子中,我們註冊文本共享intent,所以其他應用程序可以共享文本到我們的Flutter應用程序

這個應用程序的基本流程是我們首先處理Android端的共享文本數據,然後等待Flutter請求數據,然後通過MethodChannel發送。

首先像隱式Intent一樣在AndroidManifest.xml中註冊我們想要處理的intent:

<activity
       android:name=".MainActivity"
       android:launchMode="singleTop"
       android:theme="@style/LaunchTheme"
       android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
       android:hardwareAccelerated="true"
       android:windowSoftInputMode="adjustResize">
       <!-- This keeps the window background of the activity showing
            until Flutter renders its first frame. It can be removed if
            there is no splash screen (such as the default splash screen
            defined in @style/LaunchTheme). -->
       <!-- 這將保持活動的窗口背景顯示直到Flutter呈現出它的第一幀。如果沒有啓動屏
       		幕(例如默認啓動屏幕在@style/LaunchTheme中定義)。 -->
       <meta-data
           android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
           android:value="true" />
       <intent-filter>
           <action android:name="android.intent.action.MAIN"/>
           <category android:name="android.intent.category.LAUNCHER"/>
       </intent-filter>
       <intent-filter>
           <action android:name="android.intent.action.SEND" />
           <category android:name="android.intent.category.DEFAULT" />
           <data android:mimeType="text/plain" />
       </intent-filter>
   </activity>

然後,在MainActivity中處理intent,一旦我們從intent中獲得共享文本數據,我們就會持有它,直到Flutter在完成準備就緒時請求它:

package com.example.flutterintent;

import android.content.Intent;
import android.os.Bundle;

import java.nio.ByteBuffer;

import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.ActivityLifecycleListener;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
    String sharedText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        Intent intent = getIntent();
        String action = intent.getAction();
        String type = intent.getType();

        if (Intent.ACTION_SEND.equals(action) && type != null) {
            if ("text/plain".equals(type)) {
                handleSendText(intent); // Handle text being sent
            }
        }

        new MethodChannel(getFlutterView(), "app.channel.shared.data").setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if (methodCall.method.contentEquals("getSharedText")) {
                    result.success(sharedText);
                    sharedText = null;
                }
            }
        });
    }


    void handleSendText(Intent intent) {
        sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
    }
}

最後,就可以在渲染Flutter視圖時請求數據:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(new SampleApp());
}

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Sample Shared App Handler',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  SampleAppPage({Key key}) : super(key: key);

  @override
  _SampleAppPageState createState() => new _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  static const platform = const MethodChannel('app.channel.shared.data');
  String dataShared = "No data";

  @override
  void initState() {
    super.initState();
    getSharedText();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(body: new Center(child: new Text(dataShared)));
  }

  getSharedText() async {
    var sharedData = await platform.invokeMethod("getSharedText");
    if (sharedData != null) {
      setState(() {
        dataShared = sharedData;
      });
    }
  }
}

(三)startActivityForResult

在Android中,startActivityForResult用於啓動一個Activity並獲得它返回的數據,如打開本地文件選擇器等。

而處理Flutter中所有路由的Navigator類可用於從已經push到棧的路由中獲取結果。 這可以通過等待push返回的Future來完成。例如,如果您要啓動讓用戶選擇其位置的位置的路由,則可以執行以下操作:

Map coordinates = await Navigator.of(context).pushNamed('/location');

然後在你的位置路由中,一旦用戶選擇了他們的位置,你可以將結果”pop”出棧:

Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

三、部分參考資料

文中不詳盡之處還望指出補充✍
Flutter中文網-Intent
AndroidDeveloper官方文檔
Android Intent用法總結
Android中Service與Intent的使用比較

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章