2.1 藉助 Intent 讓 Activity 拿着數據瞬間移動

點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖

本節例程下載地址:WillFlowIntent

只有一個Activity的應用也太簡單了吧?沒錯,我們的追求應該更高一點。不管我們想創建多少個Activity,方法都和之前介紹的是一樣的。 唯一的問題在於,我們在啓動器中點擊應用的圖標只會進入到該應用的主Activity, 那麼怎樣才能由主Activity跳轉到其他Activity呢?

Android 系統設計的獨特之處在於,任何應用都可以利用一個帶有action的intent使當前app能夠跳轉到其他App。 例如,如果我們想讓用戶使用設備的相機拍攝照片,很可能有另一個應用可以執行該操作,那麼我們的應用就可以利用該應用,而不是開發一個 Activity 來自行拍攝照片。 我們不需要集成甚至鏈接到該相機應用的代碼,而是隻需啓動拍攝照片的相機應用中的 Activity。 完成拍攝時,系統甚至會將照片返回我們的應用,以便我們使用。這對用戶而言,就好像相機真正是我們應用的組成部分。

這是怎麼辦到的呢?當系統啓動某個組件時,會啓動該應用的進程(如果尚未運行),並實例化該組件所需的類。 例如,如果您的應用啓動相機應用中拍攝照片的 Activity,則該 Activity 會在屬於相機應用的進程,而不是您的應用的進程中運行。因此,與大多數其他系統上的應用不同,Android 應用並沒有單一入口點(例如,沒有 main()函數)。

由於系統在單獨的進程中運行每個應用,且其文件權限會限制對其他應用的訪問,因此您的應用無法直接啓動其他應用中的組件, 但 Android 系統卻可以。因此,要想啓動其他應用中的組件,您必須向系統傳遞一則消息,說明您想啓動特定組件的 Intent,系統隨後便會爲您啓動該組件。

一、使用顯式 Intent

你應該已經對創建Activity的流程比較熟悉了,我們可以參照之前生命週期裏面講的代碼。

這是它的的佈局文件是:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.wgh.willflowlifecycle.MainActivity">

    <Button
        android:id="@+id/buttonPanel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="創建第二個Activity"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

由於 SecondActivity 不是主活動,因此不需要配置標籤裏的內容,註冊Activity的代碼也是簡單了許多,只要這樣就可以了:

<activity android:name=".SecondActivity"></activity>

現在第二個Activity已經創建完成,剩下的問題就是如何去啓動這第二個活動了, 這裏我們需要引入一個新的概念:Intent。

Intent 是 Android 程序中各組件之間進行交互的一種重要方式,它不僅可以指明當前組件想要執行的動作,還可以在不同組件之間傳遞數據。Intent 一般可被用於啓動Activity、啓動服務、以及發送廣播等場景, 由於服務、廣播等概念你暫時還未涉及,那麼本篇我們的目光無疑就鎖定在了啓動Activity上面。

Intent 的用法大致可以分爲兩種,顯式 Intent 和隱式 Intent,我們先來看一下顯式 Intent 如何使用。

Intent 有多個構造函數的重載,其中一個是 Intent(Context packageContext, Class

核心代碼:
    private void initView() {
        Button button = (Button) findViewById(R.id.buttonPanel);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
                Log.d(TAG, "開啓:SecondActivity!");
            }
        });
    }

我們首先構建出了一個 Intent,傳入 MainActivity.this 作爲上下文,傳入 SecondActivity.class
作爲目標活動,這樣我們的“意圖”就非常明顯了,即在 MainActivity 的基礎上打開 SecondActivity,然後通過 startActivity() 方法來執行這個 Intent。

可以看到,我們已經成功啓動 SecondActivity 了,如果你想要回到上一個活動怎麼辦呢? 很簡單,按下 Back 鍵就可以銷燬當前活動,從而回到上一個活動了。使用這種方式來啓動活動, Intent 的“意圖”非常明顯,因此我們稱之爲顯式 Intent。

二、使用隱示Intent

相比於顯式Intent,隱示Intent並不聲明要啓動組件的具體類名,而是聲明一個需要執行的action。這個action指定了我們想做的事情,例如查看,編輯,發送或者是獲取一些東西。Intents通常會在發送action的同時附帶一些數據,例如你想要查看的地址或者是你想要發送的郵件信息。數據的具體類型取決於我們想要創建的Intent,比如Uri或其他規定的數據類型,或者甚至也可能根本不需要數據。

也就是說,隱式Intent比顯式Intent含蓄了許多,它並不明確指出我們想要啓動哪一個Activity,而是指定了一系列更爲抽象的 action 和 category 等信息,然後交由系統去分析這個 Intent,並幫我們找出合適的Activity去啓動。

什麼叫做合適的Activity呢? 簡單來說就是可以響應我們這個隱式 Intent 的Activity,那麼目前
SecondActivity 可以響應什麼樣的隱式 Intent 呢?額,現在好像還什麼都響應不了,不過很快就會有了。

通過在標籤下配置的內容, 可以指定當前活動能夠響應的 action
和 category,打開 AndroidManifest.xml,添加如下代碼:

    <activity android:name=".SecondActivity">
        <intent-filter>
            <action android:name="com.example.activitytest.ACTION_START" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </activity>

在標籤中我們指明瞭當前Activity可以響應 com.example.activitytest.ACTION_START 這個 action,而標籤則包含了一些附加信息,更精確地指明瞭當前的Activity 能夠響應的 Intent 中還可能帶有的 category。只有和中的內容同時能夠匹配上 Intent 中指定的 action 和 category 時,這個Activity才能響應該 Intent。

修改 MainActivity 中按鈕的點擊事件,代碼如下所示:
    Intent intent = new Intent("com.example.activitytest.ACTION_START");
    startActivity(intent);

可以看到,我們使用了 Intent 的另一個構造函數,直接將 action 的字符串傳了進去,表明我們想要啓動能夠響應 com.example.activitytest.ACTION_START 這個 action 的Activity。那前面不是說要和同時匹配上才能響應的嗎?怎麼沒看到哪裏有指定category呢?這是因爲 android.intent.category.DEFAULT 是一種默認的 category,在調用startActivity()方法的時候會自動將這個 category 添加到 Intent 中。

重新運行程序, 在 MainActivity 的界面點擊一下按鈕, 你同樣成功啓動 SecondActivity
了。不同的是,這次你是使用了隱式 Intent 的方式來啓動的,說明我們在標籤下配
置的 action 和 category 的內容已經生效了!

每個 Intent 中只能指定一個 action,但卻能指定多個 category。目前我們的 Intent 中只有
一個默認的 category,那麼現在再來增加一個吧!

修改 MainActivity 中按鈕的點擊事件,代碼如下所示:
    Intent intent = new Intent("com.example.activitytest.ACTION_START");
    intent.addCategory("com.example.activitytest.MY_CATEGORY");
    startActivity(intent);

可以調用 Intent 中的 addCategory() 方法來添加一個 category,這裏我們指定了一個自定
義的 category,值爲 com.example.activitytest.MY_CATEGORY。

現在重新運行程序, 在 MainActivity 的界面點擊一下按鈕,你會發現程序崩潰了!不過別緊張,其實大多數的崩潰問題都是很好解決的,只要我們善於分析。

在 LogCat 界面查看錯誤日誌,你會看到如下所示的錯誤信息。

這裏面關鍵的是這一行:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.activitytest.ACTION_START cat=[com.example.activitytest.MY_CATEGORY] }

可以看到錯誤信息中提醒我們,沒有任何一個活動可以響應我們的 Intent,爲什麼呢?這是因爲我們剛剛在 Intent 中新增了一個 category,而 SecondActivity 的標籤中並沒有聲明可以響應這個 category,所以就出現了沒有任何活動可以響應該 Intent 的情況。現在我們在中再添加一個 category 的聲明,如下所示:

    <activity android:name=".SecondActivity">
        <intent-filter>
            <action android:name="com.example.activitytest.ACTION_START" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="com.example.activitytest.MY_CATEGORY"/>
        </intent-filter>
    </activity>

再次重新運行程序,你就會發現一切都正常了。

驗證Intent的有效性

事實上,儘管Android系統會確保每一個確定的intent會被系統內置的App(such as the Phone, Email, or Calendar app)之一接收,但是我們還是應該在觸發一個intent之前做驗證是否有App接受這個intent的步驟。

注意:如果觸發了一個intent,而且沒有任何一個app會去接收這個intent,則app會crash,就像上面那樣。

爲了驗證是否有合適的activity會響應這個intent,需要執行 queryIntentActivities() 來獲取到能夠接收這個intent的所有activity的list。若返回的List非空,那麼我們纔可以安全的使用這個intent。例如:

PackageManager packageManager = getPackageManager();
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isIntentSafe = activities.size() > 0;

如果isIntentSafe爲true, 那麼至少有一個app可以響應這個intent,false則說明沒有App可以處理這個intent。

三、藉助Intent實現Activity之間的數據傳遞

經過上面的學習,我們已經對 Intent 有了一定的瞭解。不過到目前爲止,我們都只是簡單地使用 Intent 來啓動一個活動,其實 Intent 還可以在啓動活動的時候傳遞數據的,我們來一起看一下。

1、向下一個Activity傳遞數據

在啓動活動時傳遞數據的思路很簡單,Intent 中提供了一系列 putExtra() 方法的重載,可以把我們想要傳遞的數據暫存在 Intent 中,啓動了另一個活動後,只需要把這些數據再從Intent 中取出就可以了。比如說 MainActivity 中有一個字符串,現在想把這個字符串傳遞到SecondActivity 中,我們就可以這樣編寫:
- MainActivity :

    private void initView() {
        Button button = (Button) findViewById(R.id.buttonPanel);
        final EditText editText = (EditText) findViewById(R.id.editView);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                intent.putExtra("extra_data", editText.getText().toString());
                startActivity(intent);
                Log.d(TAG, "開啓:SecondActivity!");
            }
        });
    }

這裏我們還是使用顯式 Intent 的方式來啓動 SecondActivity,並通過 putExtra() 方法傳遞了一個字符串。注意這裏 putExtra()方法接收兩個參數,第一個參數是鍵,用於後面從 Intent
中取值,第二個參數纔是真正要傳遞的數據。

  • SecondActivity:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sceond);
        Log.d(TAG, "onCreate(2)被調用");

        EditText editText = (EditText) findViewById(R.id.editView2);
        Intent intent = getIntent();
        editText.setText(intent.getStringExtra("extra_data"));
    }

首先可以通過 getIntent() 方法獲取到用於啓動 SecondActivity 的 Intent,然後調用getStringExtra() 方法,傳入相應的鍵值,就可以得到傳遞的數據了。這裏由於我們傳遞的是
字符串,所以使用 getStringExtra() 方法來獲取傳遞的數據,如果傳遞的是整型數據,則使用
getIntExtra() 方法,傳遞的是布爾型數據,則使用 getBooleanExtra()方法,以此類推。

我們在 SecondActivity 中成功得到了從 MainActivity 傳遞過來的數據:

2、向上一個Activity返還數據

既然可以傳遞數據給下一個Activity,那麼能不能夠返回數據給上一個Activity呢?答案是肯定
的。不過不同的是,返回上一個Activity只需要按一下 Back 鍵就可以了, 並沒有一個用於啓動Activity Intent 來傳遞數據。

通過查閱文檔你會發現, Activity 中還有一個 startActivityForResult() 方法也是用於啓動Activity的,但這個方法期望在Activity銷燬的時候能夠返回一個結果給上一個Activity。毫無疑問,這就是我們所需要的。

startActivityForResult()方法接收兩個參數,第一個參數還是 Intent,第二個參數是請求碼,用於在之後的回調中判斷數據的來源。我們還是來實戰一下, 修改 MainActivity 中按鈕的點擊事件,代碼如下所示:

    private void initView() {
        Button button = (Button) findViewById(R.id.buttonPanel);
        final EditText editText = (EditText) findViewById(R.id.editView);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                intent.putExtra("extra_data", editText.getText().toString());
//                startActivity(intent);
                startActivityForResult(intent, 1);
                Log.d(TAG, "開啓:SecondActivity!");
            }
        });
    }

這裏我們使用了 startActivityForResult() 方法來啓動 SecondActivity,請求碼只要是一個唯一值就可以了,這裏傳入了 1。接下來我們在 SecondActivity 中給圖片註冊點擊事件,並在點擊事件中添加返回數據的邏輯,代碼如下所示:

        ImageView imageView = (ImageView) findViewById(R.id.imageView);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.putExtra("data_return", editText.getText().toString());
                setResult(RESULT_OK, intent);
                finish();
            }
        });

可以看到,我們還是構建了一個 Intent,只不過這個 Intent 僅僅是用於傳遞數據而已,它沒有指定任何的“意圖”。緊接着把要傳遞的數據存放在 Intent 中,然後調用了 setResult() 方法。這個方法非常重要,是專門用於向上一個Activity返回數據的。

setResult() 方法接收兩個參數,第一個參數用於向上一個Activity返回處理結果,一般只使用 RESULT_OK 或RESULT_CANCELED 這兩個值,第二個參數則是把帶有數據的 Intent 傳遞回去, 然後調用了 finish() 方法來銷燬當前 Activity。由於我們是使用 startActivityForResult()方法來啓動 SecondActivity 的,在 SecondActivity被銷燬之後會回調上一個Activity的 onActivityResult()方法,因此我們需要在 MainActivity中重寫這個方法來得到返回的數據,如下所示:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case 1:
                if (resultCode == RESULT_OK) {
                    String returnedData = data.getStringExtra("data_return");
                    mEditText.setText(returnedData);
                    Log.i(TAG, returnedData);
                }
                break;
            default:
        }
    }

onActivityResult()方法帶有三個參數,第一個參數 requestCode,即我們在啓動活動時傳入的請求碼。第二個參數 resultCode,即我們在返回數據時傳入的處理結果。第三個參數 data,即攜帶着返回數據的 Intent。由於在一個活動中有可能調用 startActivityForResult()方法去啓動很多不同的活動,每一個活動返回的數據都會回調到 onActivityResult()這個方法中,因此我 們首先要做的就是通過檢查 requestCode 的值來判斷數據來源 。 確定數據是從 SecondActivity 返回的之後,我們再通過 resultCode 的值來判斷處理結果是否成功。最後從 data 中取值並打印出來, 這樣就完成了向上一個活動返回數據的工作。

重新運行程序觀察效果:

好了,至此爲止,我們就學會了Activity 跳轉的各種方法,後續會介紹一些系統常用 Activity 的啓動方法,敬請期待。

點此進入:GitHub開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:


聯繫方式:

簡書:WillFlow
CSDN:WillFlow
微信公衆號:WillFlow

微信公衆號:WillFlow

發佈了81 篇原創文章 · 獲贊 21 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章