大家好,又見面了,我是你們的朋友全棧君。
Activity 跳轉詳解
你好! 我是 Graydalf ,有可能也叫 Gdalf ~
今天被朋友問到如何設定一個廣播來啟動一個應用并顯示資料,于是将自己了解到的記錄下來,有什麼較為 DEMO 的地方希望你能留言告訴我,謝謝。
- 本節說明
- Activity 跳轉的方式
- 跳轉傳值問題(包括非 Activity 的跳轉到 Activity)
- 跳轉傳遞值時生命周期回調函數調用情況
1. 顯示跳轉
通過位元組碼方式進行跳轉,需要擷取到位元組碼,是以多用于工程内跳轉。
邏輯步驟:
- 通過Activity的實作類對象調用
方法跳轉startActivity(Intent intent)
- 然後需要建立一個Intent對象
,參數1 可以使用通用的Context對象,參數2 則是需要跳轉到的Activity位元組碼對象Intent i = new Intent(getApplicationContext(), MyActivity.class)
- 可以在Intent對象中存放資料
i.putExtra(key, value)
- 然後再調轉到的Activity中使用
等方法來擷取資料getIntent().getStringExtra(key)
2. 隐式跳轉
通過意圖攔截器 <intent-filter />
來實作跳轉
邏輯步驟:
- 配置意圖攔截器
<intent-filter>
<action android:name="android.intent.action.MyActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="call"/>
<data android:mimeType="data/url"/>
</intent-filter>
<!-- action android:name 配置用于啟動此Activity的請求字串 -->
<!-- category android:name 隻能使用系統定義好的類型,這裡類型為預設 -->
<!-- data android:scheme 資料字首,請求的URI必須以此字首+`:`開頭 -->
<!-- data android:mimeType 資料類型限制 -->
複制
- 通過Activity的實作類對象調用
方法跳轉startActivity(Intent intent)
- 然後需要建立一個Intent對象
Intent i = new Intent()
- 設定請求字串
i.setAction("android.intent.action.MyActivity")
- 設定請求類型
i.addCategory("android.intent.category.DEFAULT")
- 設定Data和Type
,注意不能分别調用i.setDataAndType(Uri.parse("src:"+"values"), "data/url")
和setData(uri)
方法,方法内部互相置空,列出其中一個的源碼解釋:setType(str)
public Intent setType(String type) {
mData = null;//這裡置空了對方
mType = type;
return this;
}
複制
- 然後再調轉到的Activity中使用
等方法來擷取資料getIntent().getStringExtra(key)
非Activity跳轉到Activity
我們用一個執行個體來講解這種情況下遇到的問題
廣播監視短信,啟動Activity并且顯示短信,流程圖如下:
Created with Raphaël 2.1.0 Received sms Receiver onReceive Start Activity Show Message
1. Create BroadCastReceiver
public class SmsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Object[] objects = (Object[])intent.getExtras().get("pdus");
for(Object obj:objects) {
SmsMessage message = SmsMessage.createFromPdu((byte[])obj);
//擷取發送人号碼
message.getOriginatingAddress();
//擷取内容
String messageBody = message.getMessageBody();
Intent i = new Intent(context, MainActivity.class);
i.putExtra("sms", messageBody);
//此處有BUG,我們在後面說明
context.startActivity(i);
Toast.makeText(context, "接收短信完畢:"+messageBody, Toast.LENGTH_SHORT).show();
//這裡示範,要是短信長度超過一條我就隻取第一條
break;
}
}
}
複制
2. Register BroadCastReceiver
<receiver android:name="com.example.receiver.SmsReceiver" >
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED"/>
</intent-filter>
</receiver>
複制
3. 建立MyActivity
故事情節可能會很波折,這是因為涉及到:
-
擷取Intent異常getIntent()
- 非Activity對象調用
startActivity()
- Activity重複啟動周期
在onCreate()方法中處理顯示
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent i = getIntent();
if (i != null) {
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(i.getStringExtra("sms"));
}
}
複制
這時候我們讓程式跑起來,系統向我們我們報錯
erro: android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
複制
上面的意思是說我們在非Activity context中調用
startActivity()
,必須申明
FLAG_ACTIVITY_NEW_TASK
标記
這是什麼意思呢,我們
Ctrl + 左鍵
進入
context.startActivity(i)
内部檢視下源碼
/** * ... * <p>Note that if this method is being called from outside of an * {@link android.app.Activity} Context, then the Intent must include * the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag. This is because, * without being started from an existing Activity, there is no existing * task in which to place the new activity and thus it needs to be placed * in its own separate task. * ... */
public abstract void startActivity(Intent intent, Bundle options);
/* 若使用Activity之外的上下文對象啟動一個Activity,則必須讓Intent擁有 FLAG_ACTIVITY_NEW_TASK 啟動辨別,這是為了: 1.若已經有此Activity對象存在(也就是存在一個放置此Activity的任務棧),則在其任務棧中放置新的Activity對象 2.若沒有現有的Activity對象存在(任務棧也不存在),是以需要将其放置在其自己獨立的任務棧中。 */
複制
是以我們在
SmsReceiver
的
onReceive()
方法中調用
context.startActivity(i)
前加上
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
,然後再次運作程式,模拟發送短信,此時我們能夠接收到短信并且顯示出來。
在onResume()方法中處理顯示
但是上面的寫法對使用者的體驗非常不好,因為每條短信都會重新建立一個Activity對象壓入任務棧,我們要是想不建立新的Activity隻在目前Activity中顯示又該如何做呢?
首先想到的是在将Activity的啟動模式設定成
android:launchMode="singleTop"
這代表任務棧棧頂隻能存在一個此Activity對象,這樣我們在重複跳轉的時候就不會重新建立了,設定如下:
<activity android:launchMode="singleTop" android:name="com.example.receiver.MainActivity" android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
複制
這時我們運作程式并且模拟發送短信,發現沒有顯示短信内容,這是因為我們的顯示短信代碼在onCreate()方法中,此方法隻能在Activity被建立時調用,這裡因為
singleTop
啟動模式并沒有重新建立Activity,我們将7個生命周期回調方法都打上Log,發現當不建立新Activity對象的前提下調用
startActivity()
方法,聲明周期函數隻會先執行
onPause()
再執行
onResume
,所有我們将
onCreate()
中的顯示代碼移動到
onResume()
中來,再次測試,新的問題又出現啦,打上Log明明
onResume()
執行了,死活不出結果!!(波折麼?)
getIntent()方法的特點
我們将顯示代碼打上斷點可以觀測到,每次啟動時,
getIntent()
取得的方法總是
null
(注:若你在模拟發送短信前,應用已經關閉,那麼會回顯示第一次的資料,再次發送短信取得的都是第一次的資料),我們繼續
Ctrl + 左鍵
進入
getIntent()
方法内部檢視源碼:
/** Return the intent that started this activity. */
public Intent getIntent() {
return mIntent;
}
/* 翻譯:傳回啟動Activity時的intent */
複制
光看這個你很難了解到什麼叫傳回啟動時候的intent,本可以最簡潔地口頭描述給你看,但是這裡還是準備用事實說話,我們繼續查找名稱中帶有intent的方法,果然找到個文字叙述比較多且痛快的方法
onNewIntent()
的,如下:
/** * This is called for activities that set launchMode to "singleTop" in * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} * flag when calling {@link #startActivity}. In either case, when the * activity is re-launched while at the top of the activity stack instead * of a new instance of the activity being started, onNewIntent() will be * called on the existing instance with the Intent that was used to * re-launch it. * * <p>An activity will always be paused before receiving a new intent, so * you can count on {@link #onResume} being called after this method. * * <p>Note that {@link #getIntent} still returns the original Intent. You * can use {@link #setIntent} to update it to this new Intent. */
protected void onNewIntent(Intent intent) {
}
/* 第一句話就很給力,描述了此方法可能的使用場景,正好符合,翻譯:當我們調用一個 singleTop 啟動模式的 Activity,或者調用 startActivity(intent) 方法時參數為辨別 FLAG_ACTIVITY_SINGLE_TOP 的 Intent 對象時。 然後是說:當已經有此 Activity 執行個體存在棧頂時,上面兩種情況都會導緻 onNewIntent() 方法被調用。 下面兩個段落是說: 1. Activity 将總是被 paused 之後才去接收一個新的intent,是以你可以等此方法(onNewIntent)被調用完畢時,在 onResume() 方法中去寫自己的代碼。 2. getIntent 方法總是傳回原來的值,你可以使用 setIntent() 方法去更新一個新的intent。 */
複制
看完解釋已經很明白了,我們現在修改重寫
onNewIntent(intent)
方法如下:
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
複制
現在再來運作程式,完美~
謝閱!
有什麼好的建議和疑問可以在下面留言。
GitHub,才是男人的浪漫!
釋出者:全棧程式員棧長,轉載請注明出處:https://javaforall.cn/143179.html原文連結:https://javaforall.cn