Activity 的啟動模式(LaunchMode)紀錄

概述


Activity 的啟動模式主要包含 4 種:
Standard(標準模式,預設),SingleTop(棧頂重用),SingleTask(棧內重用),
SingleInstance(單例模式)。

Standard 每次啟動都會建立新的 Activity 實體,其它 3 種會根據情況選擇新增或
是重用 Activity 實體。

建立新實體的生命週期為 onCreate -> onStart -> onResume
重用舊實體的生命週期為 onNewIntent -> onResume

如何使用啟動模式A.


在 AndroidManifest.xml 中可以透過設定 launchMode 設定不同的啟動模式
使用 taskAffinity 並加入套件名稱便可以設定不同的栈,預設棧為目前套件名
稱,taskAffinity 只能用於 SingleTop, SingleTask, SingleInstance

1.Standard(標準):


啟動 Activity 的預設模式,當使用 Standard 每次都會產生新的實體。

2.SingleTop(棧頂重用):


當要啟動的 Activity 在棧頂,則重用該實體。若不在棧頂則建立新的實體(建立完成後該實體位於棧頂)。
啟動的生命週期:
D: onCreate
D: onStart
D: onResume
重用的生命週期
D: onPause
D: onNewIntent
D: onResume

3.SingleTask(棧內重用)


A.沒有設定 taskAffinity (情況較為簡單):

1.當要啟動的 Activity 位於棧頂時,重用該實體,不會建立新的實體。

重用的生命週期
D: onPause
D: onNewIntent
D: onResume

2.當要啟動的 Activity 位於棧內時,會重用該實體並清除該實體上方的所有其它Activity。(此時該啟動的 Activity 位於棧頂)。

重用的生命週期
D: onPause
D: onNewIntent
D: onResume

B.有設定 taskAffinity:(注意 taskAffinity 只能用於 SingleTop, SingleTask, SingleInstance)

當設定 taskAffinity 時有幾個重點:
1.被啟動的 Activity 會跟著啟動的 Activity 處於同一個棧中。
2.有一些操作會切換棧,如按下 home key,再點擊 app icon 會重用預設栈
3.當從棧退出 Activity,會優先退出同一個棧的 Activity,而當退完同一個棧的所有 Activity,會切換回桌面,不會啟動另一個棧。

以範例說明:

FirstActivity 啟動 SecondActivity,SecondActivity 啟動 ThirdActivity(SingleTask, 不同棧,棧名為 another.stack))啟動 FourthActivity (預設)

此時 FirstActivity 和 SecondActivity 位於預設棧,而 ThirdActivity 和 FourthActivity 位於 another.stack 棧。顯示在螢幕最上方的為 FourthActivity。

2個走向:

1.按下 home key 再點擊 app icon,此時會啟動預設棧,因此顯示在螢幕上的為 SecondActivity,點擊 back key(SecondActivity 銷毀),顯示 FirstActivity,點擊 back key(FirstActivity 銷毀),回到桌面。
(注意此時 ThirdActivity 和 FourthActivity 還未銷毀,保存在記憶體中)

2.按下 back key(FourthActivity 銷毀),顯示 ThirdActivity,按下 back key(ThirdActivity 銷毀),顯示 SecondActivity,按下 back key( SecondActivity 銷毀),顯示 FirstActivity,按下 back key(FirstActivity 銷毀),回到桌面。

重點:


1.若設定新的任務棧,有些操作會啟動不同的任務棧,這些操作可能會讓使用者感覺混淆。

2.當啟動新的 Activity 而產生新的任務棧時,畫面切換的動畫會不同。

4.SingleInstance(單例實體)


啟動設定為單例實體的 Activity 時,會建立單獨的栈且該棧無法加入其它的 Activiity,之後都會重用該實體。

以範例說明:

FirstActivity 啟動 SecondActivity,SecondActivity 啟動 FifthActivity(單例實體),FifthActivity 啟動 FourthActivity。

(此時有 2 個棧,預設棧有 FirstActivity,SecondActivity,FourthActivity。另一個棧只有 FifthActivity)

2個走向:

1.按下 back key(FourthActivity 銷毀),顯示 SecondActivity
(注意這裡因為出棧的規則是同一個棧的 Activity 會先出完,因此顯示的是 SecondActivity,而不是 FifthActivity),按下 back key(SecondActivity 銷毀),顯示 FirstActivity,按下 back key(FirstActivity 銷毀),顯示 FifthActivity,按下 back  key(FifthActivity 銷毀),回到桌面。

2.FourthActivity 啟動 FifthActivity,FifthActivity 啟動 FourthActivity,按下 back key(FourthActivity 銷毀),顯示 FourthActivity,按下 back key(FourthActivity 銷毀),顯示 SecondActivity,按下 back key(SecondActivity 銷毀),顯示FirstActivity,按下 back key(FirstActivity 銷毀),顯示 FifthActivity,按下back key(FifthActivity 銷毀),回到桌面。

重點:


1.設定為單例實體之後一定都會重用實體。

2.單例實體的 Activity 啟動另一個 Activity 後,被啟動的 Activity 不會加入到單例實體的棧中,而會加到預設棧。

3.出棧規則也是先出完同一個棧,待同一個棧的所有的 Activity 出完之後再切換到另一個棧。

 

如何使用啟動模式B.


呼叫 startActivity 方法時使用參數 intent 設定啟動標幟(flag),如下
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

常用的標幟如下:

1.FLAG_ACTIVITY_SINGLE_TOP: 和棧頂重用(SingleTop)相同。

2.FLAG_ACTIVITY_NEW_TASK: 和棧內重用(SingleTask)相同,但不會清除該 Activity以上的其它 Activity。

3.FLAG_ACTIVITY_CLEAR_TOP:如果是棧內重用的啟動模式,則會清除棧上其它的 Activity 並重用實體且呼叫 onNewIntent 方法。
如果是預設的啟動模式則會清除自己和其它的實體,再重新建立,呼叫 onCreate

在 AndroidManifest.xml 設定和在 startActivity 的 intent 設定的不同點:


1.2 種方式選擇 1 個使用即可。如果同時使用,startActivity 的優先級較高會蓋過AndroidManifest.xml 的設定

2.startActivity 無法設定 SingleInstance 模式

3.AndroidManifest.xml 只有 SingleTask 具有清除頂部的功能,但 startActivity 可以透過設定 FLAG_ACTIVITY_CLEAR_TOP 清除頂部,在標準的啟動模式也可以使用

4.當使用 startActivityForResult 啟動 Activity 時不會重用實體,會重複建立多個實體,因此使用 startActivityForResult 時不建議同時使用會重用實體的啟動模式(SingleTop, SingleTask, SingleInstance)

 

總結:


1.SingleTop(棧頂重用)
當要啟動的 Activity 位於棧頂時重用該實體。如果不在棧頂則建立新的實體。

2.SingleTask(棧內重用)
當要啟動的 Activity 位於棧頂時重用該實體。如果不在棧頂則清除該 Activity 以上的其它 Activity。

3.SingleInstance(單例實體)
當要啟動的 Activity 不存在時直接建立新的任務棧並放入該 Activity,之後都會重用該 Activity。

4.任務棧的切換
SingleTop, SingleTask, SingleInstance 都可透過 taskAffinity 建立新的任務棧,當使用 back key退出 Activity 時會先把同一個棧的 Activity 全部退出。切換任務棧可能會搞混。

5.設定啟動模式的方法
有 2 種,可在 AndroidManifest.xml 以及 startActivity 的intent。startActivity 的優先性大於 AndroidManifest.xml。

6.startActivityForResult 建議不要和會重用實體的模式同時使用,因為該方法無法重用實體。

Orignal From: Activity 的啟動模式(LaunchMode)紀錄

Activity 生命週期紀錄

概要:


生命週期主要包含 6 種狀態: onCreat, onStart, onResume, onPause, onStop, onDestroy。

各種狀態意義為
onCreate 代表 Activity 的建立
onStart 代表 Activity 的啟動
onResume 代表 Activity 的恢復
onPause 代表 Activity 的暫停
onStop 代表 Activity 的停止
onDestroy 代表 Activity 的銷毀

這些狀態分別互相配對,構成 3 種生命週期,分別為完整的生命週期,可視的生命週期,前台的生命週期。
完整的生命週期包含可視的生命週期,可視的生命週期包含前台的生命週期。

完整的生命週期:

Activity 從建立到銷毀的全部過程。最外層生命週期,生命週期發生在 onCreate 到 onDestroy 之間。

可視的生命週期:

Activity 從使用者可視到離開使用者視線的過程。生命週期發生在 onStart 到 onStop 之間。注意可視包含該 Activity 被其它元件遮蓋只顯示一部分的情況。

前台的生命週期:

Activity 顯示在所有的元件之前並可與使用者互動。生命週期發生在 onResume 到 onPause 之間。

另外還有 4 種特殊的生命週期狀態,各為 onRestart, onSaveInstanceState, on RestoreInstanceState, onNewIntent。

onRestart 的呼叫點為從 onStop 到 onStart 之間,也就是 onStop -> onRestart ->  onStart。

onSaveInstanceState 用於儲存 Activity 的資料。

onNewIntent 只有在 LaunchMode 為 SingleTop 且啟動的 Activity 為符合重用該 Activity 的規則時才會被呼叫。(onNewIntent -> onResume)

為了實際了解生命週期的變化,建立 FirstActivity 並覆寫所有生命週期印出 Log
FirstActivity.java
public class FirstActivity extends AppCompatActivity implements OnClickListener {  

private static final String TAG = FirstActivity.class.getSimpleName();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, TAG + " onCreate");
setContentView(R.layout.first_activity);
}

@Override
protected void onStart() {
super.onStart();
Log.d(TAG, TAG + " onStart");
}

@Override
protected void onResume() {
super.onResume();
Log.d(TAG, TAG + " onResume");
}

@Override
protected void onPause() {
super.onPause();
Log.d(TAG, TAG + " onPause");
}

@Override
protected void onStop() {
super.onStop();
Log.d(TAG, TAG + " onStop");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, TAG + " onDestroy");
}

@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, TAG + " onRestart");
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, TAG + " onSaveInstanceState");
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG, TAG + " onRestoreInstanceState");
}

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, TAG + " onNewIntent");
}

}

 

紀錄1


使用者點擊 back key, home key, menu key 所觸發的生命週期變化:


A.啟動 FirstActivity 後點擊 back key 關閉 FirstActivity
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 back key:
D: FirstActivity onPause
D: FirstActivity onStop
D: FirstActivity onDestroy

B.啟動 FirstActivity 後點擊 home key
1.點擊App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 home key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop

C.啟動 FirstActivity 後點擊 home key 再點擊 App icon
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 home key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.點擊 App icon:
D: FirstActivity onRestart
D: FirstActivity onStart
D: FirstActivity onResume

D.啟動 FirstActivity 後點擊 home key 再點擊 menu key 再拖曳移除 App頁面
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 home key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.點擊 menu key,拖曳移除 App:
D: FirstActivity onDestroy(注意有時候不會顯示)

E.啟動 FirstActivit y後點擊 home key 再點擊 menu key 再點擊 App 頁面
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 home key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.點擊 menu key 並點擊 App 頁面:
D: FirstActivity onRestart
D: FirstActivity onStart
D: FirstActivity onResume

F.啟動 FirstActivity後點擊 menu key
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 menu key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop

G.啟動 FirstActivity 後點擊 menu key 再拖曳移除 App
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 menu key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.拖曳移除 App:
沒有顯示Log , 推測應該呼叫onDestroy

H.啟動 FirstActivity 後點擊 menu key 再點擊 App頁面
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 menu key:
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.點擊 App 頁面:
D: FirstActivity onRestart
D: FirstActivity onStart
D: FirstActivity onResume

重點:


1.按下 home key 和 menu key 都會觸發 onSaveInstanceState (在 onPause 之後),但按下 back key 不會觸發 onSaveInstanceState。

2.當 Activity 從後台回到前台都會觸發 onRestart -> onStart -> onResume

 

紀錄2


Activity之間切換的生命週期變化:


2.1修改 FirstActivity,加入啟動 SecondActivity 的功能。

FirstActivity.java
public class FirstActivity extends AppCompatActivity implements OnClickListener {  

private Button mLaunchSecondBtn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, TAG + " onCreate");
setContentView(R.layout.first_activity);
initUI();
}

private void initUI() {
mLaunchSecondBtn = findViewById(R.id.first_activity_launch);
mLaunchSecondBtn.setOnClickListener(this);
}

@Override
public void onClick(View v) {
int uiID = v.getId();
switch (uiID) {
case R.id.first_activity_launch_second_activity:
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
finish();
break;
}
}

2.2 新增 SecondActivity 和 FirstActivity 互相切換來觀察生命週期。

SecondActivity.java
public class SecondActivity extends AppCompatActivity implements OnClickListener {  

private static final String TAG = SecondActivity.class.getSimpleName();
private Button mLaunchFirstBtn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, TAG + " onCreate");
setContentView(R.layout.second_activity);
initUI();
}

@Override
protected void onStart() {
super.onStart();
Log.d(TAG, TAG + " onStart");
}

@Override
protected void onResume() {
super.onResume();
Log.d(TAG, TAG + " onResume");
}

@Override
protected void onPause() {
super.onPause();
Log.d(TAG, TAG + " onPause");
}

@Override
protected void onStop() {
super.onStop();
Log.d(TAG, TAG + " onStop");
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, TAG + " onDestroy");
}

@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, TAG + " onRestart");
}

@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG, TAG + " onSaveInstanceState");
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG, TAG + " onRestoreInstanceState");
}

@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, TAG + " onNewIntent");
}

private void initUI() {
mLaunchFirstBtn = findViewById(R.id.second_activity_launch_first_activity);
mLaunchFirstBtn.setOnClickListener(this);
}

@Override
public void onClick(View v) {
int uiID = v.getId();
switch (uiID) {
case R.id.second_activity_launch_first_activity:
Intent intent = new Intent(this,FirstActivity.class);
startActivity(intent);
finish();
break;
}
}
}

A.FirstActivity 啟動 SecondActivity (FirstActivity 不會 finish),再關閉 SecondActivity,再關閉 FirstActivity
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 FirstActivity 的按鈕啟動 SecondActivity。注意 FirstActivity 沒有 finish
D: FirstActivity onPause
D: SecondActivity onCreate
D: SecondActivity onStart
D: SecondActivity onResume
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.點擊 back key 關閉 SecondActivity,讓 FirstActivity 回到前台
D: SecondActivity onPause
D: FirstActivity onRestart
D: FirstActivity onStart
D: FirstActivity onResume
D: SecondActivity onStop
D: SecondActivity onDestroy
4.點擊 back key 關閉 FirstActivity
D: FirstActivity onPause
D: FirstActivity onStop
D: FirstActivity onDestroy

B.FirstActivity 啟動 SecondActivity(FirstActivity 會 finish),再關閉 SecondActivity
1.點擊 App icon 啟動 FristActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 FirstActivity 的按鈕啟動 SecondActivity。注意 FristActivity 會 finish
D: FirstActivity onPause
D: SecondActivity onCreate
D: SecondActivity onStart
D: SecondActivity onResume
D: FirstActivity onStop
D: FirstActivity onDestroy
3.按下 back key 關閉 SecondActivity
D: SecondActivity onPause
D: SecondActivity onStop
D: SecondActivity onDestroy

C.FirstActivity 啟動 SecondActivity(FirstActivity 不會 finish), SecondActivity 再啟動 FirstActivity (SecondActivity不會finish)
1.點擊 App icon 啟動 FristActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 FirstActivity 的按鈕啟動 SecondActivity。注意 FirstActivity 不會 finish
D: FirstActivity onPause
D: SecondActivity onCreate
D: SecondActivity onStart
D: SecondActivity onResume
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
3.點擊 SecondActivity 的按鈕啟動 FirstActivity。注意 SecondActivity 不會 finish
D: SecondActivity onPause
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
D: SecondActivity onSaveInstanceState
D: SecondActivity onStop

D.FirstActivity 啟動 SecondActivity,注意 SecondActivity 使用透明主題(FirstActivity 不會 finish), SecondActivity在啟動 FirstActivity(SecondActivity 不會 finish)
1.點擊 App icon 啟動 FristActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.點擊 FirstActivity 的按鈕啟動 SecondActivity(注意 SecondActivity 使用透明主題注意 FirstActivity不會 finish)
D: FirstActivity onPause
D: SecondActivity onCreate
D: SecondActivity onStart
D: SecondActivity onResume
D: FirstActivity onSaveInstanceState
3.點擊 SecondActivity 的按鈕啟動 FirstActivity。注意 SecondActivity 不會 finish
D: SecondActivity onPause
D: FirstActivity onStop
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
D: SecondActivity onSaveInstanceState
D: SecondActivity onStop

重點:
1.當第 1 個 Activity 啟動第 2 個 Activity時,會讓第 1 個 Activity先呼叫完 onPause,再進行第 2 個 Activity 的初始化(onCreate -> onStart -> onResume)直到第 2 個 Activity 呼叫完 onResume 之後再進行第 1 個 Activity 的後續動作。

2.當第 1 個 Activity 啟動第 2 個 Activity 時,若第 1 個 Activity 會 finish,則 onSaveInstanceState 不會被呼叫。動作順序如下
(第 1 個 Activity 啟動第 2 個 Activity)
Activity1 onPause
Activity2 onCreate
Activity2 onStart
Activity2 onResume
Activity1 onStop
Activity1 onDestroy
反之,若第 1 個Activity 不會 finish,則 onSaveInstanceState 會在 onStop 之前被呼叫。
(第 1 個 Activity 啟動第 2 個 Activity)
Activity1 onPause
Activity2 onCreate
Activity2 onStart
Activity2 onResume
Activity1 onSaveInstanceState
Activity1 onStop

3.如果要啟動的 Activity(SecondActivity)是透明主題,則啟動者(FirstActivity)啟動 SecondActivity 之後不會呼叫 onStop,如下
(當 FirstActivity 啟動 SecondActivity 之後)
D: FirstActivity onPause
D: SecondActivity onCreate
D: SecondActivity onStart
D: SecondActivity onResume
D: FirstActivity onSaveInstanceState
而當 SecondActivity 啟動 FirstActivity之後才會呼叫 onStop 如下
D: SecondActivity onPause
D: FirstActivity onStop
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
D: SecondActivity onSaveInstanceState
D: SecondActivity onStop

紀錄3.


系統配置改變所造成的生命週期變化


當系統配置發生改變,Activity 就會觸發重建過程,最常見的系統配置發生變化為旋轉螢幕。因為這種生命週期變化為系統控制,所以當觸發 onSaveInstanceState 時也會一併呼叫 onRestoreInstanceState 來恢復資料。

A.啟動 FirstActivity 後旋轉螢幕
1.點擊 App icon 啟動 FirstActivity:
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.旋轉螢幕觸發從直屏到橫屏導致 FirstActivity 重建
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
D: FirstActivity onDestroy
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onRestoreInstanceState
D: FirstActivity onResume
3.再次旋轉螢幕從橫屏到直屏導致 FristActivity 重建
D: FirstActivity onPause
D: FirstActivity onSaveInstanceState
D: FirstActivity onStop
D: FirstActivity onDestroy
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onRestoreInstanceState
D: FirstActivity onResume

重點:


1.注意旋轉螢幕之後 onSaveInstanceState 會在 onPause 之後呼叫,而 onRestoreInstanceState 會在 onStart 之後呼叫。

2.若 Activity 要恢復資料可以選擇 onCreate 或 onRestoreInstanceState,差別為於在 onCreate 中需要判斷傳入的參數 Bundle 是否為空,如果是空則不需要恢復資料,而非空才需要進行恢復資料的動作。如下
  @Override  
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
if(savedInstanceState != null){
// restore data
}
}

 

3.若要支援旋轉螢幕,但不重建 Activity,可以在 AndroidManifest.xml 中對該 Activity 設定為
      android:configChanges="orientation|screenSize"

如此,旋轉螢幕之後 Activity 還是會切換直橫屏,但不會重建,資料也不會初始化,在這種情況下若想在旋轉螢幕時收到通知,可在 Activity 覆寫以下方法。(注意該方法只有在 android:configChanges="orientation|screenSize" 被設定時才呼叫)。如下
  @Override  
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG, "newConfig.toString():"+newConfig.toString());
}

測試生命週期如下
1.點擊 App icon 啟動 FirstActivity
D: FirstActivity onCreate
D: FirstActivity onStart
D: FirstActivity onResume
2.旋轉螢幕從直屏轉為橫屏
D: newConfig.toString():{0 1.0 ?mcc?mnc zh_TW ldltr sw360dp w640dp h335dp
320dpi nrml long land finger -keyb/v/h -nav/h s.47}
3.旋轉螢幕從橫屏轉為直屏
D: newConfig.toString():{0 1.0 ?mcc?mnc zh_TW ldltr sw360dp w360dp h615dp 320dpi nrml long port finger -keyb/v/h -nav/h s.48}

4.若想保持螢幕為直屏或橫屏,可在 AndroidManifest.xml 的 Activity 加入以下
      android:screenOrientation="portrait"

螢幕便會保持直屏不會旋轉。

5.注意 onRestoreInstanceState 僅用於系統配置變化時(如旋轉螢幕)導致的重建。如果是使用者導致的重建(點擊 back key or home key)則需要在 onCreate 恢復資料。

注意以上 Activity 的生命週期實驗都是以 LaunchMode (啟動模式)為 default,在其它的啟動模式(SingleTop, SingleTask, SingleInstance)則另外需要另外紀錄。

Orignal From: Activity 生命週期紀錄

OKHttp 使用注意

1.若想在 Callback 中修改畫面(包含產生 Dialog, Toast 等等),必須使用 runonUiThread 包含要更改的畫面的行為,如下為使用 OKHttp 包裝器的方法宣告,
  public static void sendPostWithJSONFormat(String URL, Map<String, String> requestParameter, Callback callback) {  
MediaType JSON = MediaType.parse("application/json; charset=utf-8");
RequestBody requestBody = RequestBody.create(JSON, new JSONObject(requestParameter).toString());
Request request = new Request.Builder().url(URL).post(requestBody).build();
Call call = sOKHttpClient.newCall(request);
call.enqueue(callback);
}

以下是使用該包裝器方法的範例
    OKHttpWrapper.sendPostWithJSONFormat(URL, httpParameter, new Callback() {  
@Override
public void onResponse(Call call, Response response) throws IOException {
//更改畫面行為
}

@Override
public void onFailure(Call call, IOException e) {
//更改畫面行為
}
});

重點在於第 4 行和第 9 行若有更改畫面的行為必須使用 runOnUiThread 去包含。否則會出現以下 Exception:
E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher  
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at android.app.Dialog.<init>(Dialog.java:119)
at android.app.AlertDialog.<init>(AlertDialog.java:200)
at android.app.AlertDialog$Builder.create(AlertDialog.java:1086)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:153)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)

 

Orignal From: OKHttp 使用注意

在裝置上產生 2 個相同的啟動圖示

情境:
安裝 App 後在裝置上產生 2 個該 App 的啟動圖示

原因:
通常是在 AndroidManifest.xml 中的 Activity,同時存在 2 個如下的 intent-filter 內容
      <intent-filter>  
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>

如下面的 SplashActivity 和 MainActivity 都有相同的 intent-filter
...    
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="2 launcher icon"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity android:name=".splash.SplashActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>

 

解法:
移除其中一個相同的 intent-filter

Orignal From: 在裝置上產生 2 個相同的啟動圖示

Android Studio 在專案中引用 AAR

使用 Android Studio 並在專案中引用 AAR,需要 2 個步驟

1.把要使用的 AAR 檔拷貝到 app/libs 目錄,完成後如下圖


2.右鍵點擊專案 -> Open Module Settings

2-1.選擇左方的 Modules 的 app -> 點擊左上方的 + 號,如下圖
2-2. 在 Create New Module 視窗選擇 Import .JAR/.AAR Package -> 點擊 next


2-3.在 import Module from Library 視窗的 File Name: 選擇第1步驟的 AAR 檔案位置 -> 點擊 OK -> Subproject name: 不必修改 -> 點擊 Finish -> Gradle 會自動更新,更新完成後點擊 OK

2-4.右鍵點擊專案 -> Open Module Settings -> 左方 Modules 選擇 app -> 點擊右上方 Dependencies -> 點擊最右上方的 + 號 -> 選擇 3 Module dependency


2-5.在 Choose Modules 視窗選擇 CommonTool-debug -> 點擊 OK -> 在 Project Structure 視窗點擊 OK -> 完成

 

Orignal From: Android Studio 在專案中引用 AAR

在Activity中判斷滑動手勢

在 Activity 判斷滑動手勢主要是透過 View.OnTouchListener 和 GestureDetector.OnGestureListener

透過讓 Activity 繼承 GestureDetector.OnGestureListener 並實作其中的 onFling 方法,在 onFling 方法中判斷第1個按下點的 X 座標和第2個按下點的 X 座標是否超過標準值來決定是否為滑動手勢。

經過測試即使是嵌入在 Activity 的 ListView 也可以成功偵測滑動手勢。
import android.support.constraint.ConstraintLayout;  
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

public class FlingGestureExampleMainActivity extends AppCompatActivity implements View.OnTouchListener,
GestureDetector.OnGestureListener{

private static final String TAG = FlingGestureExampleMainActivity.class.getSimpleName();

//main ui
private ConstraintLayout mBasicLayout;
private GestureDetector mGesetureDetector;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fling_gesture_example_main_activity);

mBasicLayout = findViewById(R.id.basic_layout_activity);
mBasicLayout.setOnTouchListener(this);
mBasicLayout.setLongClickable(true);
mGesetureDetector = new GestureDetector((GestureDetector.OnGestureListener) this);
}

@Override
public boolean onDown(MotionEvent e) {
return false;
}

@Override
public void onShowPress(MotionEvent e) {

}

@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}

@Override
public void onLongPress(MotionEvent e) {

}

@Override
public boolean onFling(MotionEvent firstMotion, MotionEvent secondMotion, float velocityX, float velocityY) {
final int FLING_MIN_DISTANCE = 100;
final int FLING_MIN_VELOCITY = 200;

boolean isFlingToLeft = firstMotion.getX() - secondMotion.getX() > FLING_MIN_DISTANCE && Math.abs(velocityX) > FLING_MIN_VELOCITY;
if (isFlingToLeft) {
Log.d(TAG, "to left");
}
boolean isFlingToRight = firstMotion.getX() - secondMotion.getX() < FLING_MIN_DISTANCE && Math.abs(velocityX) < FLING_MIN_VELOCITY;
if (isFlingToRight) {
Log.d(TAG, "to right");
}
return false;
}

@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
return mGesetureDetector.onTouchEvent(motionEvent);
}
}

主要的重點為讓 Activity實作 onDown, onShowPress, on SingleTapUp, onScroll, onLongPress, onFling, onTouch 方法,
並在 onFling 方法判斷滑動的邏輯。
以及第23~26行的手勢和 layout 物件的連結

layout檔(fling_gesture_example_main_activity.xml)
<?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"
android:id="@+id/basic_layout_activity"
tools:context=".FlingGestureExampleMainActivity">

<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
.../>

</android.support.constraint.ConstraintLayout>

重點為 ConstraintLayout 建立 id,以便在程式碼中參考。

以上便是讓 Activity 加入滑動手勢的判斷,若是想讓該 Activity中
的 ListView 也可偵測滑動,作法相當簡單,直接讓該 ListView 呼叫 setOnTouchListener 和 setLongClickable 即可。
        mPatrolPointListView = getListView();  
mPatrolPointListViewAdapter =
new PatrolPointListViewAdapter(this, mPatrolPointListPresenter.getPatrolPointItemLayout(firstPage));
setListAdapter(mPatrolPointListViewAdapter);
...

mPatrolPointListView.setOnTouchListener(this);
mPatrolPointListView.setLongClickable(true);

 

Orignal From: 在Activity中判斷滑動手勢

android.util.Log 列印訊息內容太多無法完全顯示

使用 android.util.Log 列印訊息時,若訊息內容太多就會出現無法完全顯示訊息內容的情況。

解決的方法就是分段列印,如下
    private void logLongMessage(String TAG, String message) {  
int maxMessageSize = 2000;
for (int i = 0; i <= message.length() / maxMessageSize; i++) {
int start = i * maxMessageSize;
int end = (i + 1) * maxMessageSize;
end = end > message.length() ? message.length() : end;
Log.d(TAG, message.substring(start, end));
}
}

 

Orignal From: android.util.Log 列印訊息內容太多無法完全顯示

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
Design by Free WordPress Themes | Bloggerized by Lasantha - Premium Blogger Themes | Affiliate Network Reviews