Android Activity

weixin_34320159發表於2017-12-20

【Android Activity】

<span id="什麼是 Activity?"/>

什麼是 Activity?

四大元件之一,通常一個使用者互動介面對應一個 activity。activity 是Context 的子類,同時實現了 window.callback和 keyevent.callback, 可以處理與窗體使用者互動的事件。

常見的 Activity 型別有 FragmentActivitiy,ListActivity,TabAcitivty 等。 如果介面有共同的特點或者功能的時候,還會自己定義一個 BaseActivity。

<span id="Activity 生命週期"/>

Activity 生命週期

9028834-e0a718821b61fa15.png

兩兩對應:onCreate 建立與 onDestroy 銷燬;onStart 可見與 onStop 不可見;onResume 可編輯(即焦點)與 onPause;

<span id="什麼情況下Activity走了onCreat(),而不走onStart();"/>

什麼情況下Activity走了onCreat(),而不走onStart();

當onCreate中發生Crash的時候。

何時呼叫onRestart

在 Activity 被 onStop 後,但是沒有被 onDestroy,在再次啟動此 Activity 時就呼叫 onRestart(而不再呼叫 onCreate)方法;如果被 onDestroy 了,則是呼叫 onCreate 方法。

onPause vs onStop

(a) 只要activity還可見,就不會呼叫onStop。所以我們可以知道onStop呼叫時機是:當一個activity對使用者已經不可見的時候就會呼叫。
(b) 官方說明:onStop可能永遠不會被呼叫:當處於低記憶體狀態下,系統沒有足夠的記憶體保持你的activity的處理執行在activity的onPause呼叫之後。
(c) 從(b)知道onStop在低記憶體情況下不會被呼叫,但是:onPause一定會被呼叫

onResume

對於你的activity來說,onResume呼叫後就可以和使用者互動,這是很好的地方去開始動畫,開啟互斥訪問裝置元件(例如:照相機),etc.
但是有一點我們必須認清:onResume不是最好的指向標說明你的activity對於使用者是可見的,例如系統的視窗可能在你的activity前面。所以應該用:onWindowFocusChanged來知道我們的activity是否對使用者可見。

onWindowFocusChanged

當前activity的視窗獲得或失去焦點的時候會呼叫。(activity是否對使用者可見)

大多數情況下只要呼叫了onResume 就會呼叫 onWindowFocusChanged,也有例外,比如下拉系統選單的時候只會呼叫onWindowFocusChanged。
應用:我們在下拉選單中改變了網路的狀態(開啟或者關閉),我們這時候就不能在onResume()中處理更新網路狀態,而應該將更新網路狀態放到onWindowFocusChanged中處理。

官方解釋
當前activity的視窗獲得或失去焦點的時候會呼叫。
這個函式是最好的方向標對於activity是否對使用者可見,它預設的實現是清除鍵跟蹤狀態,所以應該總是被呼叫。
它也提供了全域性的焦點狀態,它的管理是獨立於activity生命週期的。當焦點改變時一般都伴隨著生命週期的改變,你不應該依賴onWindowFocusChanged 呼叫和其他生命週期的方法(例如onResume) 的先後順序,來處理我們要做的事情。
通常的規則是,當一個activity被喚醒,那麼就擁有視窗焦點。除非這個視窗已經顯示了對話方塊或者其他彈出框搶佔焦點,這個視窗只有等到這些對話方塊關閉後,才能獲取焦點,同理,當系統顯示系統級的視窗,系統級的視窗會臨時的獲取視窗輸入焦點同時不會暫停前景 activity。

onStart()和onResume()有什麼區別?

在onStart()中檢視不可見,在onResume()中檢視可見;onStart()屬於可見程式,onResume()屬於前臺程式;

<span id="橫豎屏切換時 Activity 的生命週期"/>

橫豎屏切換時 Activity 的生命週期

此時的生命週期跟清單檔案裡的配置有關係。
1、不設定 Activity 的 android:configChanges 時,切屏會重新呼叫各個生命週期 預設首先銷燬當前 activity,然後重新載入
如下圖,當橫豎屏切換時先執行 onPause/onStop 方法

9028834-190e1a47936c04c6.png

2、設定 Activity 的 android:configChanges="orientation|screenSize"時(兩個都要設定),切屏不會重新呼叫各個生命週期,會執行 onConfigurationChanged 方法。
3、設定Activity的android:screenOrientation屬性時,切屏任何方法都不呼叫
注意:
1.如果<activity>配置了android:screenOrientation屬性,則會使android:configChanges="orientation"失效。
2.模擬器與真機差別很大:模擬器中如果不配置android:configChanges屬性或配置值為orientation,切到橫屏執行一次銷燬->重建,切到豎屏執行兩次。真機均為一次。模擬其中如果配置android:configChanges="orientation|keyboardHidden",切豎屏執行一次onConfigurationChanged,切豎屏執行兩次。真機均為一次。

兩個Activity之間跳轉的生命週期過程

一般情況下比如說有兩個 activity,分別叫 A,B,當在 A 裡面啟用 B 元件的時候, A 會呼叫 onPause()方法,然後 B 調 用 onCreate() ,onStart(), onResume()。
這個時候 B 覆蓋了窗體, A 會呼叫 onStop()方法. 如果 B 是個透明的,或者是對話方塊的樣式, 就不會呼叫 A 的onStop()方法

Activity 的狀態

a) foreground activity
b) visible activity
c) background activity
d) empty process

<span id="如何儲存 Activity 狀態"/>

如何儲存 Activity 狀態

Activity 的狀態通常情況下系統會自動儲存
一般來說, 呼叫 onPause()和 onStop()方法後的 activity 例項仍然存在於記憶體中, activity 的所有資訊和狀態資料不會消失, 當 activity 重新回到前臺之後, 所有的改變都會得到保留。
但是當系統記憶體不足時, 呼叫 onPause()和 onStop()方法後的 activity 可能會被系統摧毀, 此時記憶體中就不會存有該 activity 的例項物件了。如果之後這個 activity 重新回到前臺, 之前所作的改變就會消失。

解決方案:覆寫 onSaveInstanceState()方法。
onSaveInstanceState()方法接受一個 Bundle 型別的引數, 開發者可以 將狀態資料儲存到這個 Bundle 物件中, 這樣即使 activity 被系統摧毀, 當使用者重新啟動這個 activity 而呼叫它的 onCreate()方法時, 上述的 Bundle 物件會作為實參傳遞給 onCreate()方法, 開發者可以從 Bundle 物件中取出儲存的 資料, 然後利用這些資料將 activity 恢復到被摧毀之前的狀態。

需要注意的是, onSaveInstanceState()方法並不是一定會被呼叫的, 因為有些場景是不需要儲存狀態資料的。比如使用者按下 BACK 鍵退出 activity 時, 使用者顯然想要關閉這個 activity, 此時是沒有必要儲存資料以供下次恢復的, 也就是 onSaveInstanceState()方法不會被呼叫。
如果呼叫 onSaveInstanceState()方法, 呼叫將發生在 onPause()或onStop()方法之前

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
}

<span id="各種情況下函式呼叫過程"/>

各種情況下函式呼叫過程

第一次進入ActivityA:

A | onCreate
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

下拉系統選單(已開啟程式,從螢幕上往下拉)

A | onWindowFocusChanged | hasFocus:false

收回系統下拉選單(已開啟程式,且下拉選單已顯示)

A | onWindowFocusChanged | hasFocus:true

從ActivityA中退出:

A | onPause
A | onWindowFocusChanged | hasFocus:false
A | onStop
A | onDestory

ActivityA啟動ActivityB(普通Activity):

A | onWindowFocusChanged | hasFocus:false
A | onPause
B | onCreate
B | onStart
B | onResume
B | onWindowFocusChanged | hasFocus:true
A | onSaveInstanceState | param: 1
A | onStop

ActivityA啟動ActivityB(對話方塊Activity):

A | onWindowFocusChanged | hasFocus:false
A | onPause
B | onCreate
B | onStart
B | onResume
B | onWindowFocusChanged | hasFocus:true

從AcitivityB(普通Activity)返回到ActivityA:

B | onPause
A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true
B | onWindowFocusChanged | hasFocus:false
B | onStop
B | onDestory

從ActivityB(對話方塊Activity)返回到ActivityA:

B | onPause
A | onResume
A | onWindowFocusChanged | hasFocus:true
B | onWindowFocusChanged | hasFocus:false
B | onStop
B | onDestory

手機黑屏時:

A | onPause
A | onSaveInstanceState | param: 1
A | onStop
A | onWindowFocusChanged | hasFocus:false

手機亮屏時:

A | onWindowFocusChanged | hasFocus:true
A | onRestart
A | onStart
A | onResume

按home鍵(已啟動ActivityA)

A | onPause
A | onWindowFocusChanged | hasFocus:false
A | onSaveInstanceState | param: 1
A | onStop

多工切回程式(開啟程式,home鍵切換程式到後臺)

A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

點選應用圖示重啟程式(開啟程式,home鍵切換到後臺)

A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

點選back鍵(已開啟程式,back鍵未自己處理)

A | onPause
A | onWindowFocusChanged | hasFocus:false
A | onStop
A | onDestory

點選多工鍵(已開啟程式)

A | onWindowFocusChanged | hasFocus:false
A | onPause
A | onSaveInstanceState | param: 1
A | onStop

切回程式(已開啟程式,且已點選多工鍵)

A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

橫豎屏切換(未配置android:configChanges)

A | onPause
A | onSaveInstanceState | param: 1
A | onStop
A | onDestory
A | onCreate
A | onStart
A | onRestoreInstanceState | param: 1
A | onResume
A | onWindowFocusChanged | hasFocus:true

橫豎屏切換(配置android:configChanges="orientation")

豎切橫
A | onConfigurationChanged | newConfig:{1.0 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w592dp h336dp 320dpi nrml land finger -keyb/v/h -nav/h suim:1 s.22}
A | onPause
A | onSaveInstanceState | param: 1
A | onStop
A | onDestory
A | onCreate
A | onStart
A | onRestoreInstanceState | param: 1
A | onResume
A | onWindowFocusChanged | hasFocus:true
橫切豎
A | onConfigurationChanged | newConfig:{1.0 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w360dp h580dp 320dpi nrml port finger -keyb/v/h -nav/h suim:1 s.23}

橫豎屏切換(配置android:configChanges="orientation|scre

豎切橫
A | onConfigurationChanged | newConfig:{1.15 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w592dp h336dp 320dpi nrml land finger -keyb/v/h -nav/h suim:1 s.46}
橫切豎
A | onConfigurationChanged | newConfig:{1.15 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w360dp h580dp 320dpi nrml port finger -keyb/v/h -nav/h suim:1 s.47}

ActivityA啟動ActivityB,再從ActivityB回到ActivityA,此時ActivityB的onDestory先呼叫還是ActivityA的onResume先呼叫?

ActivityA的onResume()先呼叫,ActivityB的onDestory後呼叫。

<span id="Activity 啟動模式"/>

Activity 啟動模式

啟動模式(launchMode)在多個 Activity 跳轉的過程中扮演著重要的角色,它可以決定是否生成新的 Activity 例項,是否重用已存在的 Activity 例項,是否和其他 Activity 例項公用一個 task 裡。

task 是一個具有棧結構的物件,一個 task 可以管理多個 Activity,啟動一個應用,也就建立一個與之對應的 task。

Activity 一共有以下四種 launchMode:
1.standard
2.singleTop
3.singleTask
4.singleInstance
可以在 AndroidManifest.xml 配置<activity>的 android:launchMode 屬性為以上四種之一即可。

1 standard

預設啟動模式,每次啟用Activity時都會建立Activity,並放入任務棧中。

standard 模式是預設的啟動模式,不用為<activity>配置 android:launchMode 屬性即可,當然也可以指定值 為 standard。

原理如下:

9028834-bb38380bd43aeaf3.png

如圖所示,每次跳轉系統都會在 task 中生成一個新的 FirstActivity 例項,並且放於棧結構的頂部,當我們按下後退鍵時,才能看到原來的 FirstActivity 例項。

2 singleTop

如果在任務的棧頂正好存在該Activity的例項, 就重用該例項,否者就會建立新的例項並放入棧頂(即使棧中已經存在該Activity例項,只要不在棧頂,都會建立例項)。
原理:


9028834-e0eb53c4a50b1300.png
9028834-acd2e306fd3cf50e.png

3 singleTask

如果在棧中已經有該Activity的例項,就重用該例項(會呼叫例項的onNewIntent())。重用時,會讓該例項回到棧頂,因此在它上面的例項將會被移除棧。如果棧中不存在該例項,將會建立新的例項放入棧中。
原理:


9028834-e3696bc0a119d301.png

在圖中的下半部分是 SecondActivity 跳轉到 FirstActivity 後的棧結構變化的結果,我們注意到,SecondActivity消失了,沒錯,在這個跳轉過程中系統發現有存在的 FirstActivity 例項,於是不再生成新的例項,而是將 FirstActivity之上的 Activity 例項統統出棧,將 FirstActivity 變為棧頂物件,顯示到幕前。

4 singleInstance

在一個新棧中建立該Activity例項,並讓多個應用共享改棧中的該Activity例項。一旦該模式的Activity的例項存在於某個棧中,任何應用再啟用改Activity時都會重用該棧中的例項,其效果相當於多個應用程式共享一個應用,不管誰啟用該Activity都會進入同一個應用中

我們修改 FirstActivity 的 launchMode="standard",SecondActivity 的launchMode="singleInstance",由於涉及到了多個棧結構,我們需要在每個 Activity 中顯示當前棧結構的 id,所以我們為每個 Activity 新增如下程式碼:

textView.setText(this.toString());
taskIdView.setText("current task id: " + this.getTaskId());

然後我們再演示一下這個流程:


9028834-a9e35c023f1a999e.png

我們發現這兩個 Activity 例項分別被放置在不同的棧結構中,關於 singleInstance 的原理圖如下


9028834-efcb051af1d178d2.png

我們看到從 FirstActivity 跳轉到 SecondActivity 時,重新啟用了一個新的棧結構,來放置 SecondActivity 例項,然後按下後退鍵,再次回到原始棧結構;圖中下半部分顯示的在 SecondActivity 中再次跳轉到 FirstActivity,這個時候系統會在原始棧結構中生成一個 FirstActivity 例項,然後回退兩次,注意,並沒有退出,而是回到了 SecondActivity,

為什麼呢?是因為從 SecondActivity 跳轉到 FirstActivity 的時候,我們的起點變成了 SecondActivity 例項所在的棧結構,這樣一來,我們需要“迴歸”到這個棧結構。

如果我們修改 FirstActivity 的 launchMode 值為 singleTop、singleTask、singleInstance 中的任意一個,流程將會如圖所示:


9028834-1be112409ab05727.png

singleInstance 啟動模式可能是最複雜的一種模式,為了幫助大家理解,我舉一個例子,假如我們有一個 share 應用, 其中的 ShareActivity 是入口 Activity,也是可供其他應用呼叫的 Activity,我們把這個 Activity 的啟動模式設定為 singleInstance,然後在其他應用中呼叫。我們編輯 ShareActivity 的配置:

        <activity
            android:name=".ShareActivity"
            android:launchMode="singleInstance">
            <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.SINGLE_INSTANCE_SHARE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

然後我們在其他應用中這樣啟動該 Activity:

Intent intent = new Intent("android.intent.action.SINGLE_INSTANCE_SHARE");
startActivity(intent);

當我們開啟 ShareActivity 後再按後退鍵回到原來介面時,ShareActivity 做為一個獨立的個體存在,如果這時我 們開啟 share 應用,無需建立新的 ShareActivity 例項即可看到結果,因為系統會自動查詢,存在則直接利用。大家 可以在 ShareActivity 中列印一下 taskId,看看效果。關於這個過程,原理圖如下:


9028834-631ce871ae14128d.png

onNewIntent

重複啟動同一個Activity例項時會呼叫onNewIntent()
Activity第一次啟動的時候執行onCreate()---->onStart()---->onResume()等後續生命週期函式,也就時說第一次啟動Activity並不會執行到onNewIntent(). 而後面如果再有想啟動該Activity的時候,那就是執行onNewIntent()---->onResart()--->onStart()----->onResume()。如果android系統由於記憶體不足把已存在Activity釋放掉了,那麼再次呼叫的時候會重新啟動Activity即執行onCreate()---->onStart()---->onResume()等。

當呼叫到onNewIntent(intent)的時候,需要在onNewIntent() 中使用setIntent(intent)賦值給Activity的Intent.否則,後續的getIntent()都是得到老的Intent

一個啟動模式為 singleTop 的 activity,如果再試圖啟動會怎樣?

面試官想問的是onNewIntent()

Activity 有一個 onNewIntent(Intent intent)回撥方法,該方法我們幾乎很少使用,導致已經將其忽略掉。該方法的官方解釋如下:
This is called for activities that set launchMode to "singleTop" in their package, or if a client used
the Intent.FLAG_ACTIVITY_SINGLE_TOP flag when calling 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.
An activity will always be paused before receiving a new intent, so you can count on onResume being called after this method.
Note that getIntent still returns the original Intent. You can use setIntent to update it to this new
Intent.

上文大概意思如下:

該方法被啟動模式設定為“singleTop”的 Activity 回撥,或者當通過設定 Intent.FLAG_ACTIVITY_SINGLE_TOP 的 Intent 啟動 Activity 時被回撥。在任何情況下,只要當棧頂的 Activity 被重新啟動時沒有重新建立一個新的 Activity 例項而是依然使用該 Activity 物件,那麼 onNewIntent(Intent)方法就會被回撥。
當一個 Activity 接收到新 Intent 的時候會處於暫停狀態,因此你可以統計到 onResume()方法會被再次執行,當 然這個執行是在 onNewIntent 之後的。
注意:如果我們在 Activity 中呼叫了 getIntent()方法,那麼返回的 Intent 物件還是老的 Intent(也就是第一 次啟動該 Activity 時的傳入的 Intent 物件),但是如果想讓 getIntent()返回最新的 Intent,那麼我們可以通過 setIntent(Intent)方法設定。

實際演示:

啟動方式 啟動Activity 執行函式 task中Activity
Standard StandardActivity@96267d3 onCreate
onStart
onResume TaskId: 187 StandardActivity@96267d3 TaskId【187】
SingleInstance SingleInstanceActivity@a2e46b7 onCreate
onStart
onResume TaskId: 188 StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleInstance SingleInstanceActivity@a2e46b7 onNewIntent
onResume TaskId: 188 StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleTask SingleTaskActivity@e0f0b0a onCreate
onStart
onResume TaskId: 187 SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleTask SingleTaskActivity@e0f0b0a onNewIntent
onResume TaskId: 187 SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleTop SingleTopActivity@21bfd59 onCreate
onStart
onResume TaskId: 187 SingleTopActivity@21bfd59
SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleTop SingleTopActivity@21bfd59 onNewIntent
onResume TaskId: 187 SingleTopActivity@21bfd59
SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
Standard StandardActivity@9f84b54 onCreate
onStart
onResume TaskId: 187 StandardActivity@9f84b54
SingleTopActivity@21bfd59
SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
Standard StandardActivity@a364129 onCreate
onStart
onResume TaskId: 187 StandardActivity@a364129
StandardActivity@9f84b54
SingleTopActivity@21bfd59
SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleTop SingleTopActivity@a9c182a onCreate
onStart
onResume TaskId: 187 SingleTopActivity@a9c182a
StandardActivity@a364129
StandardActivity@9f84b54
SingleTopActivity@21bfd59
SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
SingleTask SingleTopActivity@21bfd59 onDestroy
StandardActivity@9f84b54 onDestroy
StandardActivity@a364129 onDestroy
SingleTaskActivity@e0f0b0a onNewIntent
onRestart
onStart
onResume TaskId: 187
SingleTopActivity@a9c182a onDestroy SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
back StandardActivity@96267d3 onRestart
onStart
onResume TaskId: 187
SingleTaskActivity@e0f0b0a onDestroy SingleTaskActivity@e0f0b0a
StandardActivity@96267d3【187】
SingleInstanceActivity@a2e46b7【188】
back StandardActivity@96267d3 onDestroy
SingleInstanceActivity@a2e46b7 onRestart
onStart
onResume TaskId: 188 SingleInstanceActivity@a2e46b7【188】

程式碼設定啟動模式

Intent intent = new Intent(this, B.class); 
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
startActivity(intent); 

Task就像一個容器,而Activity就相當與填充這個容器的東西,第一個東西(Activity)則會處於最下面,最後新增的東西(Activity)則會在最上面。從Task中取出東西(Activity)是從最頂端取出,也就是說最先取出的是最後新增的東西(Activity),以此類推,最後取出的是第一次新增的Activity,而Activity在Task中的順序是可以控制的,在Activity跳轉時用到Intent Flag可以設定新建activity的建立方式;

Intent常用標識:

FLAG_ACTIVITY_BROUGHT_TO_FRONT

這個標誌一般不是由程式程式碼設定的,如在launchMode中設定singleTask模式時系統幫你設定。

FLAG_ACTIVITY_CLEAR_TOP

+FLAG_ACTIVITY_SINGLE_TOP=singleTask:如果設定,並且這個Activity已經在當前的Task中執行,因此,不再是重新啟動一個這個Activity的例項,而是在這個Activity上方的所有Activity都將關閉,然後這個Intent會作為一個新的Intent投遞到老的Activity(現在位於頂端)中。
  例如,假設一個Task中包含這些Activity:A,B,C,D。如果D呼叫了startActivity(),並且包含一個指向Activity B的Intent,那麼,C和D都將結束,然後B接收到這個Intent,因此,目前stack的狀況是:A,B。
  上例中正在執行的Activity B既可以在onNewIntent()中接收到這個新的Intent,也可以把自己關閉然後重新啟動來接收這個Intent。如果它的啟動模式宣告為 “multiple”(預設值),並且你沒有在這個Intent中設定FLAG_ACTIVITY_SINGLE_TOP標誌,那麼它將關閉然後重新建立;對於其它的啟動模式,或者在這個Intent中設定FLAG_ACTIVITY_SINGLE_TOP標誌,都將把這個Intent投遞到當前這個例項的onNewIntent()中。
  這個啟動模式還可以與FLAG_ACTIVITY_NEW_TASK結合起來使用:用於啟動一個Task中的根Activity,它會把那個Task中任何執行的例項帶入前臺,然後清除它直到根Activity。這非常有用,例如,當從Notification Manager處啟動一個Activity。

FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET

API 21,被FLAG_ACTIVITY_NEW_DOCUMENT代替

  如果設定,這將在Task的Activity stack中設定一個還原點,當Task恢復時,需要清理Activity。也就是說,下一次Task帶著 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記進入前臺時(經過測試發現,對於一個處於後臺的應用,如果在launcher中點選應用,這個動作中含有FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記;長按Home鍵,然後點選最近記錄,這個動作不含FLAG_ACTIVITY_RESET_TASK_IF_NEEDED標記),這個Activity和它之上的都將關閉,以至於使用者不能再返回到它們,但是可以回到之前的Activity。
  這在你的程式有分割點的時候很有用。例如,一個e-mail應用程式可能有一個操作是檢視一個附件,需要啟動圖片瀏覽Activity來顯示。這個 Activity應該作為e-mail應用程式Task的一部分,因為這是使用者在這個Task中觸發的操作。然而,當使用者離開這個Task,然後從主畫面選擇e-mail app,我們可能希望回到檢視的會話中,但不是檢視圖片附件,因為這讓人困惑。通過在啟動圖片瀏覽時設定這個標誌,瀏覽及其它啟動的Activity在下次使用者返回到mail程式時都將全部清除。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

  如果設定,新的Activity不會在最近啟動的Activity的列表中儲存。

FLAG_ACTIVITY_FORWARD_RESULT

  如果設定,並且這個Intent用於從一個存在的Activity啟動一個新的Activity,那麼,這個作為答覆目標的Activity將會傳到這個新的Activity中。這種方式下,新的Activity可以呼叫setResult(int),並且這個結果值將傳送給那個作為答覆目標的 Activity。

FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY

  這個標誌一般不由應用程式程式碼設定,如果這個Activity是從歷史記錄裡啟動的(常按HOME鍵),那麼,系統會幫你設定。

FLAG_ACTIVITY_MULTIPLE_TASK

  不要使用這個標誌,除非你自己實現了應用程式啟動器。與FLAG_ACTIVITY_NEW_TASK結合起來使用,可以禁用把已存的Task送入前臺的行為。當設定時,新的Task總是會啟動來處理Intent,而不管這是是否已經有一個Task可以處理相同的事情。
  由於預設的系統不包含圖形Task管理功能,因此,你不應該使用這個標誌,除非你提供給使用者一種方式可以返回到已經啟動的Task。
  如果FLAG_ACTIVITY_NEW_TASK標誌沒有設定,這個標誌被忽略。

FLAG_ACTIVITY_NEW_TASK

  如果設定,這個Activity會成為歷史stack中一個新Task的開始。一個Task(從啟動它的Activity到下一個Task中的 Activity)定義了使用者可以遷移的Activity原子組。Task可以移動到前臺和後臺;在某個特定Task中的所有Activity總是保持相同的次序。
  這個標誌一般用於呈現“啟動”型別的行為:它們提供使用者一系列可以單獨完成的事情,與啟動它們的Activity完全無關。
  使用這個標誌,如果正在啟動的Activity的Task已經在執行的話,那麼,新的Activity將不會啟動;代替的,當前Task會簡單的移入前臺,保持棧中的狀態不變,即棧中的順序不變。
  這個標誌不能用於呼叫方對已經啟動的Activity請求結果。

設定此狀態,記住以下原則,首先會查詢是否存在和被啟動的Activity具有相同的親和性的任務棧(即taskAffinity,注意同一個應用程式中的activity的親和性一樣,所以下面的a情況會在同一個棧中,前面這句話有點拗口,請多讀幾遍),如果有,剛直接把這個棧整體移動到前臺,並保持棧中的狀態不變,即棧中的activity順序不變,如果沒有,則新建一個棧來存放被啟動的activity

例項
a. 前提: Activity A和Activity B在同一個應用中。
  操作: Activity A啟動開僻Task堆疊(堆疊狀態: A), 在Activity A中啟動Activity B, 啟動Activity B的Intent的Flag設為FLAG_ACTIVITY_NEW_TASK
==>Activity B被壓入Activity A所在堆疊(堆疊狀態: AB)。
  原因: 預設情況下同一個應用中的所有Activity擁有相同的關係(taskAffinity)。

b. 前提: Activity A在名稱為"TaskOne應用"的應用中, Activity C和Activity D在名稱為"TaskTwo應用"的應用中。
  操作1: 在Launcher中單擊"TaskOne應用"圖示, Activity A啟動開僻Task堆疊, 命名為TaskA(TaskA堆疊狀態: A),在Activity A中啟動Activity C, 啟動Activity C的Intent的Flag設為FLAG_ACTIVITY_NEW_TASK,Android系統會為Activity C開僻一個新的Task, 命名為TaskB(TaskB堆疊狀態: C), 長按Home鍵, 選擇TaskA,Activity A回到前臺, 再次啟動Activity C(兩種情況1。從桌面啟動;2。從Activity A啟動,兩種情況一樣), 這時TaskB回到前臺, Activity C顯示, 供使用者使用, 即:
==>包含FLAG_ACTIVITY_NEW_TASK的Intent啟動Activity的Task正在執行, 則不會為該Activity建立新的Task,而是將原有的Task返回到前臺顯示。

  操作2: 在Launcher中單擊"TaskOne應用"圖示, Activity A啟動開僻Task堆疊, 命名為TaskA(TaskA堆疊狀態: A),在Activity A中啟動Activity C,啟動Activity C的Intent的Flag設為FLAG_ACTIVITY_NEW_TASK,Android系統會為Activity C開僻一個新的Task, 命名為TaskB(TaskB堆疊狀態: C), 在Activity C中啟動Activity D(TaskB的狀態: CD) 長按Home鍵, 選擇TaskA, Activity A回到前臺, 再次啟動Activity C(從桌面或者ActivityA啟動,也是一樣的),這時TaskB回到前臺, Activity D顯示,供使用者使用。
==>說明了在此種情況下設定FLAG_ACTIVITY_NEW_TASK後,會先查詢是不是有Activity C存在的棧,根據親和性(taskAffinity),如果有,剛直接把這個棧整體移動到前臺,並保持棧中的狀態不變,即棧中的順序不變

FLAG_ACTIVITY_NO_ANIMATION

  如果在Intent中設定,並傳遞給Context.startActivity()的話,這個標誌將阻止系統進入下一個Activity時應用 Acitivity遷移動畫。這並不意味著動畫將永不執行——如果另一個Activity在啟動顯示之前,沒有指定這個標誌,那麼,動畫將被應用。這個標誌可以很好的用於執行一連串的操作,而動畫被看作是更高一級的事件的驅動。

FLAG_ACTIVITY_NO_HISTORY

  如果設定,新的Activity將不再歷史stack中保留。使用者一離開它,這個Activity就關閉了。這也可以通過設定noHistory特性。

FLAG_ACTIVITY_NO_USER_ACTION

  如果設定,作為新啟動的Activity進入前臺時,這個標誌將在Activity暫停之前阻止從最前方的Activity回撥的onUserLeaveHint()。
  典型的,一個Activity可以依賴這個回撥指明顯式的使用者動作引起的Activity移出後臺。這個回撥在Activity的生命週期中標記一個合適的點,並關閉一些Notification。
  如果一個Activity通過非使用者驅動的事件,如來電或鬧鐘,啟動的,這個標誌也應該傳遞給Context.startActivity,保證暫停的Activity不認為使用者已經知曉其Notification。

FLAG_ACTIVITY_PREVIOUS_IS_TOP

  If set and this intent is being used to launch a new activity from an existing one, the current activity will not be counted as the top activity for deciding whether the new intent should be delivered to the top instead of starting a new one. The previous activity will be used as the top, with the assumption being that the current activity will finish itself immediately.

FLAG_ACTIVITY_REORDER_TO_FRONT

  如果在Intent中設定,並傳遞給Context.startActivity(),這個標誌將引發已經執行的Activity移動到歷史stack的頂端
  例如,假設一個Task由四個Activity組成:A,B,C,D。如果D呼叫startActivity()來啟動Activity B,那麼,B會移動到歷史stack的頂端,現在的次序變成A,C,D,B。如果FLAG_ACTIVITY_CLEAR_TOP標誌也設定的話,那麼這個標誌將被忽略

FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

  If set, and this activity is either being started in a new task or bringing to the top an existing task, then it will be launched as the front door of the task. This will result in the application of any affinities needed to have that task in the proper state (either moving activities to or from it), or simply resetting that task to its initial state if needed.
一般為系統使用,比如要把一個應用從後臺移到前臺,有兩種方式:從多工列表中恢復(不包含該flag);從啟動器中點選icon恢復(包含該flag);需結合FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_DOCUMENT (API21)理解

FLAG_ACTIVITY_SINGLE_TOP

  如果設定,當這個Activity位於歷史stack的頂端執行時,不再啟動一個新的
注意:如果是從BroadcastReceiver啟動一個新的Activity,或者是從Service往一個Activity跳轉時,不要忘記新增Intent的Flag為FLAG_ACTIVITY_NEW_TASK。

taskAffinity

  每個Activity都有taskAffinity屬性,這個屬性指出了它希望進入的Task。如果一個Activity沒有顯式的指明該 Activity的taskAffinity,那麼它的這個屬性就等於Application指明的taskAffinity,如果 Application也沒有指明,那麼該taskAffinity的值就等於包名。而Task也有自己的affinity屬性,它的值等於它的根 Activity的taskAffinity的值。

  TaskAffinity屬性主要和SingleTask啟動模式或者allowTaskReparenting屬性配對使用,在其他情況下沒有意義。當TaskAffinity和singleTask啟動模式配對使用的時候,他是具有該模式的Activity的目前任務棧的名字,待啟動的Activity會執行在名字和TaskAffinity相同的任務棧中。allowTaskReparenting用於配置是否允許該activity可以更換從屬task,通常情況二者連在一起使用,用於實現把一個應用程式的Activity移到另一個應用程式的Task中。allowTaskReparenting用來標記Activity能否從啟動的Task移動到taskAffinity指定的Task,預設是繼承至application中的allowTaskReparenting=false,如果為true,則表示可以更換;false表示不可以。

  如果載入某個Activity的intent,Flag被設定成FLAG_ACTIVITY_NEW_TASK時,它會首先檢查是否存在與自己taskAffinity相同的Task,如果存在,那麼它會直接宿主到該Task中,如果不存在則重新建立Task。

若是已經啟動了四個Activity:A,B,C和D,在D Activity裡,想再啟動一個Actvity B,但不變成A,B,C,D,B,而是A,C,D,B,則可以像下面寫程式碼:

Intent intent = new Intent(this, MainActivity.class);  
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);   
startActivity(intent);

Android Activity面試題

什麼是 Activity?

見上文#[什麼是 Activity?](#什麼是 Activity?)

描述一下 Activity 生命週期

Activity 從建立到銷燬有多種狀態,從一種狀態到另一種狀態時會激發相應的回撥方法,這些回撥方法包括:
onCreate onStart onResume onPause onStop onDestroy
其實這些方法都是兩兩對應的,onCreate 建立與 onDestroy 銷燬;
onStart 可見與 onStop 不可見;onResume 可編輯(即焦點)與 onPause;

這 6 個方法是相對應的,那麼就只剩下一個 onRestart 方法了,這個方法在什麼時候呼叫呢?
答案就是:在 Activity 被 onStop 後,但是沒有被 onDestroy,在再次啟動此 Activity 時就呼叫 onRestart(而不再呼叫 onCreate)方法;如果被 onDestroy 了,則是呼叫 onCreate 方法。

更多見上文#[Activity 生命週期](#Activity 生命週期)

橫豎屏切換時 Activity 的生命週期

見上文#[橫豎屏切換時 Activity 的生命週期](#橫豎屏切換時 Activity 的生命週期)

兩個 Activity 之間跳轉時必然會執行的是哪幾個方法?(重要)

一般情況下比如說有兩個 activity,分別叫 A,B,當在 A 裡面啟用 B 元件的時候, A 會呼叫 onPause()方法,然後 B 調
用 onCreate() ,onStart(), onResume()。
這個時候 B 覆蓋了窗體, A 會呼叫 onStop()方法. 如果 B 是個透明的,或者是對話方塊的樣式, 就不會呼叫 A 的
onStop()方法。
第一次進入ActivityA:
A | onCreate
A | onStart
A | onResume

ActivityA啟動ActivityB(普通Activity):
A | onPause
B | onCreate
B | onStart
B | onResume
A | onStop

ActivityA啟動ActivityB(對話方塊Activity):
A | onPause
B | onCreate
B | onStart
B | onResume

更多情況見上文#各種情況下函式呼叫過程

Activity 的狀態都有哪些?

a) foreground activity
b) visible activity
c) background activity
d) empty process

如何儲存 Activity 狀態

見上文#[如何儲存 Activity 狀態](#如何儲存 Activity 狀態)

請描述一下 Activity 的啟動模式都有哪些以及各自的特點

見上文#[Activity 啟動模式](#Activity 啟動模式)

將一個 Activity 設定成視窗的樣式:

只需要給我們的 Activity 配置如下屬性即可。
注意:當前Activity繼承自Activity,而不是AppCompatActivity,否則會報主題的錯誤。

android:theme="@android:style/Theme.Dialog"

如何退出 Activity?如何安全退出已呼叫多個 Activity 的 Application?

方法1、通常情況使用者退出一個 Activity 只需按返回鍵,我們寫程式碼想退出 activity 直接呼叫 finish()方法就行。

方法2、application 記錄開啟的 Activity
每開啟一個 Activity,就記錄下來。在需要退出時,關閉每一個 Activity 即可。

private List<Activity> mActList = new LinkedList<>();// 在 application 全域性的變數裡面
    public void addActivity(Activity activity) {
        if (mActList.contains(activity)) {
            mActList.remove(activity);
        }
        mActList.add(activity);
    }

    public void exit() {
        try {
            for (Activity activity : mActList) {
                if (activity != null)
                    activity.finish();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.exit(0);
        }
    }

方法3、傳送特定廣播
在需要結束應用時,傳送一個特定的廣播,每個 Activity 收到廣播後,關閉即可。

//給每個 activity 註冊接受接受廣播的意圖

registerReceiver(receiver, filter)

//如果過接受到的是 關閉 activity 的廣播   就呼叫 finish()方法 把當前的 activity finish()掉

或者eventBus

//傳送方
EventBus.getDefault().post(map);

//接收方
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        EventBus.getDefault().register(this);
        ...
    }

    @Override
    public void onDestroyView() {
        EventBus.getDefault().unregister(this);
        super.onDestroyView();
    }

    public void onEventMainThread(HashMap<String, String> map) {
      finish();
    }

方法4、遞迴退出
在開啟新的 Activity 時使用 startActivityForResult,然後自己加標誌,在 onActivityResult 中處理,遞迴關閉。

方法5、其實 也可以通過 intent 的 flag 來實現 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)啟用 一個新的 activity。此時如果該任務棧中已經有該 Activity,那麼系統會把這個 Activity 上面的所有 Activity 幹掉。其實相當於給 Activity 配置的啟動模式為 SingleTop。

兩個 Activity 之間傳遞資料,有哪些方式?(2017-2-23)

1、通過intent傳遞資料

(1)直接傳遞,intent.putExtra(key, value)
(2)通過bundle,intent.putExtras(bundle);

注意:
(1)這兩種都要求傳遞的物件必須可序列化(Parcelable、Serializable)
(2)Parcelable實現相對複雜
(3)關於Parcelable和Serializable,官方說法:

`Serializable`: it's error prone and horribly slow. So in general: stay away from `Serializable` if possible.

也就是說和Parcelable相比Seriaizable容易出錯並且速度相當慢。是否這樣,可參見下一篇部落格說明。

(4)通過intent傳遞資料是有大小限制滴,超過限制,要麼拋異常,要麼新的Activity啟動失敗,所以還是很嚴重的啊,可參見下一篇部落格說明。

1)intent.putExtra(key, value)

// 傳遞
 Intent intent = new Intent(this,TwoActivity.class);
 intent.putExtra("data",str);
 startActivity(intent);
// 接收
 Intent intent = getIntent();
 String str = intent.getStringExtra("data");
序列化物件Seriazable

首先建立一個Person類,讓Person類實現Serializable介面。Person類中有name,sex,age三個屬性,實現Person類的 get 、set 方法。(!注意:該物件的類必需實現Serializable介面)

把要傳遞到下一個Activity的資料存放在Person類的物件中,啟動Activity把資料傳遞出去

//序列化需要傳遞的物件
public class Personimplements Serializable {
...
//傳遞
Intent i = new Intent();
Person p = new Person("錢多多","男",22); 
i.putExtra("key", p); //向Intent物件中存入Person物件P  
startActivity(i);  
//接收
Intent i = getIntent();
Serializable p = i.getSerializableExtra("key"); //反序列化資料物件  
if (p instanceof Person) {   
    Person person = (Person) p; //獲取到攜帶資料的Person物件p
}
Parcelable(最常用,傳遞資料效率更高)

實現Parcelable步驟
① implements Parcelable
② 重寫writeToParcel方法,將你的物件序列化為一個Parcel物件,即:將類的資料寫入外部提供的Parcel中,打包需要傳遞的資料到Parcel容器儲存,以便從 Parcel容器獲取資料
③ 重寫describeContents方法,內容介面描述,預設返回0就可以
④ 例項化靜態內部物件CREATOR實現介面Parcelable.Creator

public static final Parcelable.Creator<T> CREATOR

注:其中public static final一個都不能少,內部物件CREATOR的名稱也不能改變,必須全部大寫。需重寫本介面中的兩個方法:createFromParcel(Parcel in) 實現從Parcel容器中讀取傳遞資料值,封裝成Parcelable物件返回邏輯層,newArray(int size) 建立一個型別為T,長度為size的陣列,僅一句話即可(return new T[size]),供外部類反序列化本類陣列使用。

簡而言之:
通過writeToParcel將你的物件對映成Parcel物件,再通過createFromParcel將Parcel物件對映成你的物件。也可以將Parcel看成是一個流,通過writeToParcel把物件寫到流裡面,在通過createFromParcel從流裡讀取物件,只不過這個過程需要你來實現,因此寫的順序和讀的順序必須一致。

//序列化資訊類
public class InfoBean implements Parcelable {//① implements Parcelable

    public InfoBean(String name) {//構造方法
        description = name;
    }

    public String description;//描述 -- 取一個欄位(資訊類InfoBean的一個屬性)

// ② 重寫writeToParcel方法,通過writeToParcel將你的物件對映成Parcel物件
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //通過writeToParcel把物件寫到流裡面
        dest.writeString(description);
    }
//③ 重寫describeContents方法,內容介面描述,預設返回0就可以
    @Override
    public int describeContents() {
        return 0;
    }
//④ 例項化靜態內部物件CREATOR實現介面Parcelable.Creator,通過createFromParcel將Parcel物件對映成你的物件
    public static final Parcelable.Creator<InfoBean> CREATOR
            = new Parcelable.Creator<InfoBean>() {
        @Override
        public InfoBean createFromParcel(Parcel source) {
            //再通過createFromParcel從流裡讀取物件
            String description = source.readString();
            //把物件儲存在資料類物件的構造引數中,返回包含有資料的物件
            return new InfoBean(description);
        }
        @Override
        public InfoBean[] newArray(int size) {
            return new InfoBean[size];
        }
    };
}

2)intent.putExtras(bundle)

// 傳遞
 Intent intent = new Intent(MainActivity.this,TwoActivity.class);
 //用資料捆傳遞資料
 Bundle bundle = new Bundle();
 bundle.putString("data", str);
 //把資料捆設定改意圖
 intent.putExtra("bun", bundle);
 startActivity(intent);
// 接收
//獲取Bundle
 Intent intent = getIntent();
 Bundle bundle = intent.getBundleExtra("bun");
 String str = bundle.getString("data");

2、廣播接收者

3、content provider

4、Application共享資料

將資料儲存到全域性Application中,隨整個應用的存在而存在,這樣很多地方都能訪問。

//建立一個Application的類 :MyApplication繼承Application
//傳值
MyApplication application = (MyApplication) MainActivity.this.getApplication();
application.setData("abc");
//取值
MyApplication application = (MyApplication) getApplication();
String data = application.getData();

注意
當由於某些原因(比如系統記憶體不足),我們的app會被系統強制殺死,此時再次點選進入應用時,系統會直接進入被殺死前的那個介面,製造一種從來沒有被殺死的假象。那麼問題來了,系統強制停止了應用,程式死了,那麼再次啟動時Application自然新的,那裡邊的資料自然木有啦,如果直接使用很可能報空指標或者其他錯誤。

因此還是要考慮好這種情況的:
(1)使用時一定要做好非空判斷
(2)如果資料為空,可以考慮邏輯上讓應用直接返回到最初的activity,比如用 FLAG_ACTIVITY_CLEAR_TASK 或者 BroadcastReceiver 殺掉其他的activity。

5、使用單例

比如一種常見的寫法:

public class DataHolder {
  private String data;
  public String getData() {return data;}
  public void setData(String data) {this.data = data;}
  private static final DataHolder holder = new DataHolder();
  public static DataHolder getInstance() {return holder;}
}

這樣在啟動activity之前:

DataHolder.getInstance().setData(data);

新的activity中獲取資料:

String data = DataHolder.getInstance().getData();

6、靜態Statis

這個可以直接在activity中也可以單獨一個資料結構體,就和單例差不多了。

1)資料結構體中

注意:這些情況如果資料很大很多,比如bitmap,處理不當是很容易導致記憶體洩露或者記憶體溢位的。

所以可以考慮使用WeakReferences 將資料包裝起來。

比如:

public class DataHolder {
  Map<String, WeakReference<Object>> data = new HashMap<String, WeakReference<Object>>();

  void save(String id, Object object) {
    data.put(id, new WeakReference<Object>(object));
  }

  Object retrieve(String id) {
    WeakReference<Object> objectWeakReference = data.get(id);
    return objectWeakReference.get();
  }
}

啟動之前:

DataHolder.getInstance().save(someId, someObject);

新activity中:

DataHolder.getInstance().retrieve(someId);

這裡可能需要通過intent傳遞id,如果資料唯一,id都可以不傳遞的。save() retrieve()中id都固定即可。

2)activity中

在接收端的Avtivity裡面設定static的變數,在傳送端這邊改變靜態變數的值,然後啟動意圖

//傳送端
//定義一個意圖  
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
//改變OtherActivity的三個靜態變數的值  
OtherActivity.name = "wulianghuan";
OtherActivity.age = "22";
OtherActivity.address = "上海閔行";
startActivity(intent);
//接收端
public class OtherActivity extends Activity {  
    //定義靜態變數
    public static String name;  
    public static String age;  
    public static String address;  
}  

7、持久化資料

例如:File 檔案儲存、SharedPreferences 首選項、Sqlite 資料庫
優點:
(1)應用中所有地方都可以訪問
(2)即使應用被強殺也不是問題了
缺點:
(1)操作麻煩
(2)效率低下
(3)io讀寫嘛,其實還是比較容易出錯的

8、如果不跨程式,可以使用 EventBus

9、startActivityForResult

通過onActivityResult得到Activity的回傳值

10、使用剪下板傳遞資料

傳遞String

主要步驟
通過getSystemService獲取ClipboardManager物件cm。
使用cm.setPrimaryClip()方法設定ClipData資料物件。
在新Activity中獲取ClipboardManager物件cm。
使用cm.getPrimaryClip()方法獲取剪下板的ClipData資料物件,cd。
通過cd.getItemAt(0)獲取到傳遞進來的資料。

//傳遞資料
//獲取剪下板
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("data", "Jack"));
Intent intent = new Intent(MainActivity.this, otherActivity.class);
startActivity(intent);
//獲取資料:
ClipboardManager cm = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
ClipData cd=cm.getPrimaryClip();
String msg=cd.getItemAt(0).getText().toString();

傳遞物件

以上方式使用剪下板傳遞的為String型別的資料,如果需要傳遞一個物件,那麼被傳遞的物件必須可序列化,序列化通過實現Serializable介面來標記。

主要步驟
建立一個實現了Serializable介面的類MyData。
存入資料:獲取ClipboardManager,並對通過Base64類對MyData物件進行序列化,再存入剪下板中。
取出資料:在新Activity中,獲取ClipboardManager,對被序列化的資料進行反序列化,同樣使用Base64類。然後對反序列化的資料進行處理。
示例程式碼:

//序列化需要傳遞的物件
public class MyData implements Serializable {
    private String name;
    private int age;
    public MyData(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
/* 傳遞物件 */
MyData mydata = new MyData("jack", 24);
String baseToString = "";
ByteArrayOutputStream bArr = new ByteArrayOutputStream();
try {
    ObjectOutputStream oos = new ObjectOutputStream(bArr);
    oos.writeObject(mydata);
    baseToString = Base64.encodeToString(bArr.toByteArray(), Base64.DEFAULT);
    oos.close();
} catch (Exception e) {
    e.printStackTrace();

}
//獲取剪下板
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText("data", baseToString));
Intent intent = new Intent(MainActivity.this, otherActivity.class);
startActivity(intent);
/* 獲取物件 */
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData cd = cm.getPrimaryClip();
String msg = cd.getItemAt(0).getText().toString();
byte[] base64_btye = Base64.decode(msg, Base64.DEFAULT);
ByteArrayInputStream bais = new ByteArrayInputStream(base64_btye);
try {
    ObjectInputStream ois = new ObjectInputStream(bais);
    MyData mydata = (MyData) ois.readObject();

    TextView tv = (TextView) findViewById(R.id.msg);
    tv.setText(mydata.toString());
} catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

注意:
  使用剪下板傳遞資料有利有弊,剪下板為Android系統管理的,所以在一個地方存入的資料,在這個Android裝置上任何應用都可以訪問的到,但是正是因為此裝置訪問的都是同一個剪下板,可能會導致當前程式存入的資料,在使用前被其他程式覆蓋掉了,導致無法保證正確獲取資料。

怎樣在兩個 Activity 之間傳遞一張圖片(2017-2-23)

a) Intent 可以傳遞基本資料型別、Uri 和序列化物件
b) Bitmap 物件實現了 Parcelable 序列化介面,但是不建議放到 Intent 裡傳遞。因為 Pacelable 物件序列化過 程是將物件 A 的屬性暫存一份到記憶體裡,反序列化時再使用暫存的資料,建立一個屬性完全相同的物件 B。
c) 對於 Bitmap 而言,就是把圖片的二進位制資料 0 複製一份到記憶體裡構成二進位制資料 1,反序列化時根據二進 制資料 1 建立 Bitmap 物件,此時會生成二進位制資料 2。也就是同一個圖片的資料在記憶體裡存放了三份。
d) 也就是說把 Bitmap 放到 Intent 裡會導致巨大的記憶體損耗,所以在傳遞圖片時應該是傳遞 URI 地址,新介面根據 URI 生成新圖片。同時還可以到圖片快取裡使用 URI 查詢已有圖片,節約記憶體。

如何實現切換主題功能?(2017-2-23)

1.內建主題

1.1定義屬性

確認哪些屬性是需要根據主題變化而改變的,在values資料夾下,新建attrs.xml檔案,在attrs.xml裡自定義:

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
    <attr name="colorValue" format="color" />  
    <attr name="floatValue" format="float" />  
    <attr name="integerValue" format="integer" />  
    <attr name="booleanValue" format="boolean" />  
    <attr name="dimensionValue" format="dimension" />  
    <attr name="stringValue" format="string" />  
    <attr name="referenceValue" format="reference" />  
</resources>  

1.2定義主題

接著,我們需要在資原始檔中定義若干套主題。並且在主題中設定各個屬性的值。

本例中,我在styles.xml裡定義了SwitchTheme1與SwitchTheme2。

<style name="SwitchTheme1" parent="@android:style/Theme.Black">  
    <item name="colorValue">#FF00FF00</item>  
    <item name="floatValue">0.35</item>  
    <item name="integerValue">33</item>  
    <item name="booleanValue">true</item>  
    <item name="dimensionValue">76dp</item>  
    <!-- 如果string型別不是填的引用而是直接放一個字串,在佈局檔案中使用正常,但程式碼裡獲取的就有問題 -->  
    <item name="stringValue">@string/hello_world</item>  
    <item name="referenceValue">@drawable/hand</item>  
</style>  
<style name="SwitchTheme2" parent="@android:style/Theme.Wallpaper">  
    <item name="colorValue">#FFFFFF00</item>  
    <item name="floatValue">1.44</item>  
    <item name="integerValue">55</item>  
    <item name="booleanValue">false</item>  
    <item name="dimensionValue">76px</item>  
    <item name="stringValue">@string/action_settings</item>  
    <item name="referenceValue">@drawable/ic_launcher</item>  
</style>  

1.3在佈局檔案中使用

定義好了屬性,我們接下來就要在佈局檔案中使用了。

為了使用主題中的屬性來配置介面,我定義了一個名為activity_theme_switch.xml佈局檔案。

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
    <TextView  
        android:id="@+id/textView1"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentLeft="true"  
        android:layout_alignParentTop="true"  
        android:text="@string/theme_text" />  
    <TextView  
        android:id="@+id/textView3"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentLeft="true"  
        android:layout_below="@+id/textView1"  
        android:text="@string/theme_color" />        
    <TextView  
        android:id="@+id/themeColor"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignLeft="@+id/themeText"  
        android:layout_below="@+id/themeText"  
        android:text="TextView"  
        android:textColor="?attr/colorValue" />
        <!-- ?attr/colorValue 引用主題中的顏色值-->    
    <TextView  
        android:id="@+id/textView5"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentLeft="true"  
        android:layout_alignTop="@+id/theme_image"  
        android:text="@string/theme_image" />  
    <TextView  
        android:id="@+id/themeText"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignParentTop="true"  
        android:layout_centerHorizontal="true"  
        android:text="?attr/stringValue" />  
    <ImageView  
        android:id="@+id/theme_image"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:layout_alignLeft="@+id/themeColor"  
        android:layout_below="@+id/themeColor"  
        android:src="?attr/referenceValue" />  
</RelativeLayout>  

從這個佈局檔案中可以看到,我在id為themeColor、themeText及theme_image的控制元件上,分別使用了?attr/colorValue、?attr/stringValue與?attr/referenceValue來引用主題中的顏色值、字串以及圖片。

1.4設定主題

佈局檔案與主題都寫好了,接下來我們就要在Activity的onCreate方法裡使用了

public class DemoStyleThemeActivity extends Activity {
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState); 
        setTheme(R.style.SwitchTheme1); 
        //setTheme一定要在setContentView之前被呼叫
        setContentView(R.layout.activity_theme_switch);  
    }  
}  

當然,要想主題生效,有一點非常重要:“setTheme一定要在setContentView之前被呼叫”。要不然,介面都解析完了,再設定主題也不會觸發重新建立介面。

更嚴重的是,要是預設主題裡沒那些屬性,解析佈局檔案時候是會掛的啊!這點在配置多個不同style時要主題,屬性可以多,但一定不能少。

上面幾步只是在 Activity 使用自己的主題,實現動態切換主題的思路是把 setTheme 裡面的主題作為一個變數,當需要切換主題時,改變這個變數的值,然後重新建立當前 Activity。

1.5切換主題

① 在自定義 Application 中記錄當前主題

public class MyApp extends Application {
    private int currentTheme = R.style.SwitchTheme1;
    public void changeTheme(int theme) {
        this.currentTheme = theme;
    }
    public void initTheme(Context context) {
        context.setTheme(currentTheme);
    }
}

② Activity 的 oncreated 方法首先設定主題

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    myApp = (MyApp) getApplication();
    myApp.initTheme(this);
    setContentView(R.layout.activity_main);
}

③ 當想要切換主題時,先替換 MyApp 中的當前主題,然後重新建立當前 Activity,新的 Activity 建立後就使用了改變後的主題

myApp.changeTheme(R.style.BlueTheme);
recreate();//該方法是 Activity 中的方法,可以讓當前 Activity 重新建立例項

因為setTheme()方法必須要在setContentView()方法之前呼叫,所以為了使當前Activity的主題切換成功,需要呼叫recreate()方法來重新呼叫onCreate()方法。這樣也導致了當前Activity被銷燬,並重新啟動,所以會出現閃屏的現象。

2.APK主題

上面雖然可以切換 Activity 的主題,但是由於每次都需要殺死當前 Activity 重新建立新的 Activity,因此使用者體驗並不好。 上面的更改主題方案,屬於內建主題,該方案雖然可以很方便地修改介面,並且不需要怎麼改程式碼。但它有一個比較致命的缺陷,就是一旦程式釋出後,應用所支援的主題風格就固定了。要想增加更多的效果,只能釋出新的版本。

如何實現通過網路下載更換主題呢?下面給大家提供一種思路。
為了解決這種問題,便有了 APK 主題方案

APK 主題方案的基本思路是:在 Android 中,所有的資源都是基於包的。資源以 id 進
行標識,在同一個應用中,每個資源都有唯一標識。但在不同的應用中,可以有相同的 id。因此,只要獲取到了其他應用的 Context 物件,就可以通過它的 getRsources 獲取到其繫結的資源物件。然後,就可以使用 Resources 的 getXXX 方法獲取字串、顏色、dimension、圖片等。

要 想 獲 取 其 他 應 用 的 Context 對 象 , Android 已 經 為 我 們 提 供 好 了 接 口 。 那 就 是android.content.ContextWrapper.createPackageContext(String packageName, int flags)方法。
示例程式碼如下:

public class DemoRemoteThemeActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_theme);
        TextView text = (TextView) findViewById(R.id.remoteText);
        TextView color = (TextView) findViewById(R.id.remoteColor);
        ImageView image = (ImageView) findViewById(R.id.remote_image);
        try {
            //① 通過 createPackageContext 獲取到了包名為 com.xxx.themepackage 的應用的上下文
            String remotePackage = "com.xxx.themepackage";
            Context remoteContext = 
                        createPackageContext(remotePackage, CONTEXT_IGNORE_SECURITY);
            //② 通過 Resources 的getIdentifier 方法獲取到相應資源名在該應用中的 id
            //③ 通過 Resources 的getXXX 方法獲取資源
            Resources remoteResources = remoteContext.getResources();
            text.setText(remoteResources.getText(
                        remoteResources.getIdentifier("application_name", "string", remotePackage)));
            color.setTextColor(remoteResources.getColor(
                        remoteResources.getIdentifier("delete_target_hover_tint", "color", remotePackage)));
            image.setImageDrawable(remoteResources.getDrawable(
                        remoteResources.getIdentifier("ic_launcher_home", "drawable", remotePackage)));
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

首先,我通過 createPackageContext 獲取到了包名為 com.xxx.themepackage 的應用的上下文。
然後,通過 Resources 的getIdentifier 方法獲取到相應資源名在該應用中的 id。當然,有的人也可以通過使用自身應用的 id 的方式,不過這有一個前提,那就是同名資源在主題包應用與當前應用中的 id 相同。這貌似可以通過修改 編譯流程來實現,就像 framework 裡的 public.xml 那樣。不過這不在本文的討論範疇內。
最後,就是通過 Resources 的getXXX 方法獲取資源了。

Android 中 Activity 是如何啟動的?(2017-2-24)

Activity 啟動時的概要互動流程如下圖:

使用者在 Launcher 程式裡點選應用圖示時,會通知 ActivityManagerService 啟動應用的入口 Activity,ActivityManagerService 發現這個應用還未啟動,則會通知 Zygote 程式孵化出應用程式,然後在這個 dalvik 應用程式裡執行 ActivityThread 的 main 方法。在該方法裡會先準備好 Looper 和訊息佇列,然後呼叫 attach 方法將應用程式繫結到 ActivityManagerService,然後進入 loop 迴圈,不斷地讀取訊息佇列裡的訊息,並分發訊息。

    //ActivityThread 類public static void main(String[] args) {
        //...
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        AsyncTask.init();
        //...
        Looper.loop();
        //...}

在 ActivityThread 的 main 方法裡呼叫 thread.attach(false);attach 方法的主要程式碼如下所示:

//ActivityThread 類 private void attach(boolean system) {
sThreadLocal.set(this);
mSystemThread = system;
if (!system) {
    //...
    IActivityManager mgr = ActivityManagerNative.getDefault();
    try {
        //呼叫 ActivityManagerService 的 attachApplication 方法
        //將 ApplicationThread 物件繫結至 ActivityManagerService,
        //這樣 ActivityManagerService 就可以
        //通過 ApplicationThread 代理物件控制應用程式
        //呼叫的是 ActivityManagerService 的 attachApplication
        mgr.attachApplication(mAppThread);
    } catch (RemoteException ex) {
        // Ignore
    }
} else {
    //...
}
//... }

ActivityManagerService 的 attachApplication 方法執行 attachApplicationLocked(thread, callingPid)進行繫結 。 attachApplicationLocked 方 法 有 兩 個 重 要 的 函 數 調 用 thread.bindApplication 和mMainStack.realStartActivityLocked。thread.bindApplication 將應用程式的 ApplicationThread 物件繫結到ActivityManagerService , 也 就 是 說 獲 得 ApplicationThread 對 象 的 代 理 對 象 。
mMainStack.realStartActivityLocked 通知應用程式啟動 Activity。

//ActivityManagerService 類
private final boolean attachApplicationLocked(IApplicationThread thread,int pid) {
    ProcessRecord app;
    //...
    app.thread = thread;
    //...
    try {
        //...
        //thread 物件其實是 ActivityThread 裡 ApplicationThread
        //物件在 ActivityManagerService 的代理物件,故此執行
        //thread.bindApplication,最終會呼叫 ApplicationThread 的 bindApplication 方法
        //最後會呼叫 queueOrSendMessage 會往 ActivityThread 的訊息佇列傳送訊息,
        //訊息的用途是 BIND_APPLICATION 這樣會在 handler 裡處理 BIND_APPLICATION 訊息,
        //接著呼叫 handleBindApplication 方法處理繫結訊息。
        thread.bindApplication(processName, appInfo, providers,
                app.instrumentationClass, profileFile, profileFd, profileAutoStop,
                app.instrumentationArguments, app.instrumentationWatcher, testMode,
                enableOpenGlTrace, isRestrictedBackupMode || !normalMode, app.persistent,
                new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
                mCoreSettingsObserver.getCoreSettingsLocked());
        //...
    } catch (Exception e) {
        //...
    }
    //...
    ActivityRecord hr = mMainStack.topRunningActivityLocked(null);
    if (hr != null && normalMode) {
        if (hr.app == null && app.uid == hr.info.applicationInfo.uid
                && processName.equals(hr.processName)) {
            try {
                if (mHeadless) {
                    Slog.e(TAG, "Starting activities not supported on headless device: " + hr);
                    // realStartActivity 會呼叫 scheduleLaunchActivity 啟動 activity
                    //最終會呼叫 ApplicationThread 的 scheduleLaunchActivity 方法。
                    //呼叫了 queueOrSendMessage 往 ActivityThread 的訊息佇列傳送了訊息
                    //,訊息的用途是啟動 Activity
                } else if (mMainStack.realStartActivityLocked(hr, app, true, true)) {
                    //mMainStack.realStartActivityLocked 真正啟動 activity
                    didSomething = true;
                }
            } catch (Exception e) {
                //...
            }
        } else {//...} }//... Return true;} 

綜上時序圖為:

ActivityThread 的 handler 呼叫 handleLaunchActivity 處理啟動 Activity 的訊息,handleLaunchActivity 的主要程式碼如下所示:

//ActivityThread 類
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //...
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
        //...
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);
        //...
    } else {
        //...
    }
}

handleLaunchActivity 方 法 裡 有 兩 個 重 要 的 函 數 調 用 ,performLaunchActivity 和
handleResumeActivity,performLaunchActivity 會呼叫 Activity 的onCreate,onStart,onResotreInstanceState 方法,handleResumeActivity 會呼叫 Activity 的 onResume 方法.程式碼如下:
performLaunchActivity 的主要程式碼如下所示:

//ActivityThread 類
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    //...
    //會呼叫 Activity 的 onCreate 方法
    mInstrumentation.callActivityOnCreate(activity, r.state);
    //...
    //...
    //呼叫 Activity 的 onStart 方法
    if (!r.activity.mFinished) {
        activity.performStart();
        r.stopped = false;
    }
    if (!r.activity.mFinished) {
        if (r.state != null) {
            //會呼叫 Activity 的 onRestoreInstanceState 方法
            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
        }
        //....
    }

啟動 Activity 主要涉及到的類:

Activity 的管理採用 binder 機制,管理 Activity 的介面是 IActivityManager. ActivityManagerService 實現了Activity 管理功能,位於 system_server 程式,ActivityManagerProxy 物件是 ActivityManagerService 在普通應用程式的一個代理物件,應用程式通過 ActivityManagerProxy 物件呼叫 ActivityManagerService 提供的功能。應用程式並不會直接建立 ActivityManagerProxy 物件,而是通過呼叫 ActiviyManagerNative 類的工具方法 getDefault 方法得到 ActivityManagerProxy 物件。所以在應用程式裡通常這樣啟動 Activty。

 ActivityManagerNative.getDefault().startActivity()

ActivityManagerService 也需要主動呼叫應用程式以控制應用程式並完成指定操作。這樣
ActivityManagerService 也需要應用程式的一個 Binder 代理物件,而這個代理物件就是ApplicationThreadProxy物件。 ActivityManagerService 通過 IApplicationThread 介面管理應用程式,ApplicationThread 類實現了IApplicationThread 介面,實現了管理應用的操作,ApplicationThread 物件執行在應用程式裡。
ApplicationThreadProxy 物件是 ApplicationThread 物件在 ActivityManagerService 執行緒
(ActivityManagerService 執行緒執行在 system_server 程式)內的代理物件,ActivityManagerService 通過ApplicationThreadProxy 物件呼叫 ApplicationThread 提供的功能,比如讓應用程式啟動某個 Activity。
public static void main(String[] args) {

Process.setArgV0("<pre-initialized>");
//建立 looper
Looper.prepareMainLooper();
//會通過 thread.attach(false)來初始化應用程式的執行環境
ActivityThread thread = new ActivityThread();
thread.attach(false);

在建立訊息迴圈之前,會通過 thread.attach(false)來初始化應用程式的執行環境,並建立 activityThread 和ActivityManagerService 之間的橋 mAppThread, mAppThread 是 IApplicationThread 的一個例項。

RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
    mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
    throw ex.rethrowFromSystemServer();
}

注意:每個應用程式對應著一個 ActivityThread 例項,應用程式由 ActivityThread.main 開啟訊息迴圈。每個應用程式同時也對應著一個 ApplicationThread 物件。該物件是 activityThread 和 ActivityManagerService 之間的橋樑。在 attach 中還做了一件事情,就是通過代理呼叫 attachApplication,並利用 binder 的 transact 機制,在 ActivityManagerService 中建立了 ProcessRecord 資訊。

ActivityManagerService:

在 ActivityManagerService 中,也有一個用來管理 activity 的地方:mHistory 棧,這個 mHistory 棧裡存放的 是服務端的 activity 記錄 HistoryActivity(class HistoryRecord extendsIApplicationToken.Stub)。處於棧頂的就是當前running 狀態的 activity。
我們來看一下 Activity 的 startActivity 方法的請求過程:


從該時序圖中可以看出,Activity.startActivity()方法最終是通過代理類和 Binder 機制,在ActivityManagerService.startActivity 方法中執行的。那麼在 ActivityManagerService 的 startActivity 中,主要做了那些事情?我們來看下里面比較重要的程式碼段:
根據 activity、ProcessRecord 等資訊建立 HistoryRecord 例項 r

HistoryRecord r = new HistoryRecord(
        this, callerApp, callingUid,intent, resolvedType, aInfo,
        mConfiguration,resultRecord, resultWho, requestCode, componentSpecified);

//把 r 加入到 mHistory 中。
mHistory.add(addPos, r);

//activity 被加入到 mHistory 之後,只是說明在服務端可以找到該 activity 記錄了,但是在客戶端目前還沒有該 activity記錄。還需要通過 ProcessRecord 中的 thread(IApplication)變數,呼叫它的 scheduleLaunchActivity 方法在ActivityThread 中建立新的 ActivityRecord 記錄(之前我們說過,客戶端的 activity 是用 ActivityRecord 記錄的,並放在 mActivities 中)。
app.thread.scheduleLaunchActivity(
        new Intent(r.intent), r,System.identityHashCode(r),
        r.info, r.icicle, results, newIntents, !andResume, isNextTransitionForward());

在這個裡面主要是根據服務端返回回來的資訊建立客戶端 activity 記錄 ActivityRecord. 並通過 Handler 傳送訊息到訊息佇列,進入訊息迴圈。在 ActivityThread.handleMessage()中處理訊息。最終在 handleLaunchActivity 方法中把 ActivityRecord 記錄加入到 mActivities(mActivities.put(r.token,r))中,並啟動 activity。

總結:
1)在客戶端和服務端分別有一個管理 activity 的地方,服務端是在 mHistory 中,處於 mHistory 棧頂的就是當前處於 running 狀態的 activity,客戶端是在 mActivities 中。
2)在 startActivity 時,首先會在ActivityManagerService 中建立 HistoryRecord,並加入到 mHistory 中,然後通過 scheduleLaunchActivity 在客戶端建立 ActivityRecord 記錄並加入到 mActivities 中。最終在 ActivityThread 發起請求,進入訊息迴圈,完成 activity的啟動和視窗的管理等。

引用:
關於Activity的生命週期
onCreate & onStart & onResume & onStop & onPause & onDestroy & onRestart & onWindowFocusChanged
Activity的四種啟動模式和onNewIntent()
關於程式碼實現activity的啟動模式
Intent相關FLAG介紹和Activity啟動模式
android深入解析Activity的launchMode啟動模式,Intent Flag,taskAffinity
Activity的啟動方式和flag詳解
Android Intent.FLAG_NEW_TASK詳解,包括其他的標記的一些解釋
Android--使用剪下板在Activity中傳值
Android入門篇三:使用靜態變數在Activity之間傳遞資料
Activity之間傳遞資料的方式及常見問題總結
關於Android Activity之間傳遞資料的6種方式
Android基礎之Activity系列 - Activity間的資料傳遞
Android主題切換方案總結

相關文章