點此進入:從零快速構建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開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:
聯繫方式: