導航:首頁 > 操作系統 > android定義棧

android定義棧

發布時間:2023-05-12 07:33:47

⑴ 堆棧中(不是棧頂的)的activity能否接收到動態注冊的廣播

當用戶在一個Activity中執行完任務,要向前切換到另一個Activity,並且不希望用戶點擊「返回」按鈕再回到當前的Activity時,可以調用Activity類的finish()方法,把當前的Activity從該應用的回退堆棧中刪除,finish()方法被調用,當前的Activity就會碰和被銷毀,用戶不能夠再按「返信吵配回」按鈕回到這個Activity中。示例代碼如下:
一.清單文件(androidManifest.xml)
<?xmlversion="1.0"encoding="utf-8"?>
<manifestxmlns:android="htt p:/ /schemas.android.c om/apk/res/android"
package="my.android.test"
android:versionCode="1"
android:versionName="1.0">
<applicationandroid:icon="@drawable/icon"android:label="@string/app_name">
<activity android:name=".Forwarding"
android:label="@string/app_name">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ForwardTarget">
</activity>
</application>
<uses-sdk android:minSdkVersion="滑指9"/>
</manifest>
二.字元資源定義(strings.xml)
<?xmlversion="1.0"encoding="utf-8"?>
<resources>
<string name="hello">Hello World, Forwarding!</string>
<string name="app_name">Forwarding</string>
<string name="activity_forwarding">App/Activity/Forwarding</string>
<string name="forwarding">Press the button to go forward to the next activity. This activity will stop, so you will no longer see it when going back.</string>
<string name="go">Go</string>
<string name="forward_target">Press back button and notice we don\'t see the previous activity.</string>
</resources>
三.布局定義(forwarding.xml、forward_target.xml)
Forwarding.xml
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="htt p:/ /schemas.android.c om/apk/res/android"android:orientation="vertical"android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent"android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"android:layout_height="wrap_content"
android:layout_weight="0"
android:paddingBottom="4dip"
android:text="@string/forwarding"/>
<Buttonandroid:id="@+id/go"
android:layout_width="wrap_content"android:layout_height="wrap_content"
android:text="@string/go">
<requestFocus />
</Button>

</LinearLayout>
forward_target.xml
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="htt p:/ /schemas.android.c om/apk/res/android"android:orientation="vertical"android:padding="4dip"
android:gravity="center_horizontal"
android:layout_width="match_parent"android:layout_height="match_parent">

<TextView
android:layout_width="match_parent"android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/forward_target"/>

</LinearLayout>
四.Activity類定義(Forwarding.java、ForwardTarget.java)
Frowarding.java
package my.android.test;
import android.app.Activity;
import android.os.Bundle;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class Forwarding extends Activity {
/** Activity被首次創建時調用這個方法*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.forwarding);
//從布局中查找go按鈕,並給它設置點擊事件監聽器
Button goButton = (Button)findViewById(R.id.go);
goButton.setOnClickListener(mGoListener);

}

/** 實現OnClickListener介面,監聽按鈕的點擊事件*/
private OnClickListener mGoListener = new OnClickListener(){
public void onClick(View v){
//聲明Intent對象,並啟動ForwardTarget Activity
Intent intent = new Intent();
intent.setClass(Forwarding.this, ForwardTarget.class);
startActivity(intent);
//從歷史堆棧中刪除當前Activity,用戶點擊「返回」按鈕鍵時,不會再返回到這個Activity。
finish();
}
};
}
ForwardTarget.java
package my.android.test;

import android.app.Activity;
import android.os.Bundle;

publicclass ForwardTargetextends Activity {

@Override
protectedvoid onCreate(Bundle saveInstanceState){
super.onCreate(saveInstanceState);

setContentView(R.layout.forward_target);
}
}

⑵ 在Android 中如何關閉應用以及所有的 Activity

Android程序有很多Activity,比如說主窗口A,調用了子喚判窗口B,陵灶如果在B中直接finish(), 接下里顯示的是A。在B中如何關閉整個Android應用程序呢?本人總結了幾種比較簡單的實現方法。

1. Dalvik VM的本地方法
android.os.Process.killProcess(android.os.Process.myPid()) //獲取PID
System.exit(0); //常規java、c#的標准退出法,返回值為0代表正常退出

2. 任務管理器方法
首先要說明該方法運行在Android 1.5 API Level為3以上才可以,同時需要許可權
ActivityManager am = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
am.restartPackage(getPackageName());
系統會將,該包下的 ,所有進程,服務,全部殺掉,就可以殺干凈了,要注意加上
<uses-permission android:name=\"android.permission.RESTART_PACKAGES\"></uses-permission>
3. 根據Activity的聲明周期

3. 我們知尺鏈扮道Android的窗口類提供了歷史棧,我們可以通過stack的原理來巧妙的實現,這里我們在A窗口打開B窗口時在Intent中直接加入標志 Intent.FLAG_ACTIVITY_CLEAR_TOP,這樣開啟B時將會清除該進程空間的所有Activity。
在A窗口中使用下面的代碼調用B窗口
Intent intent = new Intent();
intent.setClass(Android123.this, CWJ.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //注意本行的FLAG設置
startActivity(intent);
接下來在B窗口中需要退出時直接使用finish方法即可全部退出。

4.自定義一個Actiivty 棧,道理同上,不過利用一個單例模式的Activity棧來管理所有Activity。

不過建議使用第一種方式來退出應用程序

⑶ 安卓手機的,系統版本和內核版本是指什麼

系統版本:安卓的版本,稱為軟體

定義如果對象集S滿足下列兩個條件

1、S中至少包含兩個不同元素

2、S中的元素按一定方式相互聯系

則稱S為一個系統,S的元素為系統的組分。

而版本就是一個序列號

內核版本:手機的硬體,稱為硬體

如主板,GPS,攝像頭,WiFi,藍牙等,驅動集合體的版本號。

(3)android定義棧擴展閱讀

系統內核

Android 是運行於Linux kernel之上,但並不是GNU/Linux。因為在一般GNU/Linux 里支持的功能,Android 大都沒有支持,包括Cairo、X11、Alsa、FFmpeg、GTK、Pango及Glibc等都被移除掉了。

Android又以Bionic 取代Glibc、以Skia 取代Cairo、再以opencore取代FFmpeg等等。

Android 為了達到商業應用,必須移除被GNU GPL授權證所約束的,例如Android將驅動程序移Userspace,使得Linux driver 與 Linux kernel徹底分開。

Bionic/Libc/Kernel/ 並非標準的Kernel header files。

Android 的 Kernel header 是利用工具由 Linux Kernel header 所產生的,這樣做是為了保留常數、數據結構與宏。

Android 的 Linux kernel控制包括安全(Security),存儲器管理(Memory Management),程序管理(Process Management),網路堆棧(Network Stack),驅動程序模(DriverModel)等。

⑷ android怎樣將activity放入全局棧

Activity是Android程序的表現層。程序的每一個顯示屏幕就是一個Activity。正在運行的Activity處在棧的最頂端,它是運行狀態的。

當有新的Activity進入屏幕最上端時,原來的Activity就會被壓入第二層。如果他的屏幕沒有被完 全遮蓋,那麼他處於Paused狀態,如果他被遮蓋那麼處於Stop狀態。
不管處於任何一層,都可能在系統覺得資源不足時被強行關閉,當然關閉時棧底的程序最先被關閉。
譬如:當你在程序中調用 Activity.finish()方法時,結果和用戶按下 BACK 鍵一樣:他告訴 Activity Manager該Activity實例可以被「回收」。隨後 Activity Manager 激活處於棧第二層的 Activity 並重 新入棧,把原 Activity 壓入到棧的第二層,從 Running 狀態轉到 Paused 狀態。

在BlackBerry中,提供了一個管理Screen的棧,用來從任何地方來關閉位於最上一層的Screen,使用UiApplication.getUiApplication().getActiveScreen()來得到位於最上一層的Screen的實例,並且使用UiApplication.getUiApplication().popScreen()來關閉一個Screen或關閉當前最上一層的Screen,但是Android卻未提供相應的功能,只能在一個Activity的對象裡面調用finish來關閉自己,不能關閉其他的Activity。比如我們想實現一個功能從屏幕A—>屏幕B—>屏幕C—>屏幕D,然後在在轉到屏幕D之前將屏幕B和C關閉,在屏幕B和屏幕C界面點擊會退按鈕都可以回退到上一個屏幕,但是在屏幕D上點擊會退按鈕讓其回退到A,此外在一些循環跳轉的界面上如果不在合適的地方將一些不需要的屏幕關閉,那麼經過多次跳轉後回導致內存溢出。對此我們可以設計一個全局的Activity棧,使用這個棧來管理Activity。管理Activity的類的定義如下:

import java.util.Stack;

import android.app.Activity;

public class ScreenManager {
private static Stack activityStack;
private static ScreenManager instance;
private ScreenManager(){
}
public static ScreenManager getScreenManager(){
if(instance==null){

⑸ 如何在程序異常退出前輸出當前進程的堆棧信息 Backtraces

列印堆棧是調試的常用方法,一般在系統異常時,我們可以將異常情況下的堆棧列印出來,這樣十分方便錯誤查找。實際上還有另外一個非常有用的功能:分析代碼的行為。android代碼太過龐大復雜了,完全的靜態分析經常是無從下手,因此通過列印堆棧的動態分析也十分必要。

Android列印堆棧的方法,簡單歸類一下
1. zygote的堆棧mp
實際上這個可以同時mp java線程及native線程的堆棧,對於java線程,java堆棧和native堆棧都可以得到。
使用方法很簡單,直接在adb shell或串口中輸入:
[plain] view plain
kill -3 <pid>
輸出的trace會保存在 /data/anr/traces.txt文件中。這個需要注意,如果沒有 /data/anr/這個目錄或/data/anr/traces.txt這個文件,需要手工創建一下,並設置好讀寫許可權。
如果需要在代碼中,更容易控制堆棧的輸出時機,可以用以下命令獲取zygote的core mp:
[java] view plain
Process.sendSignal(pid, Process.SIGNAL_QUIT);
原理和命令行是一樣的。
不過需要注意兩點:
adb shell可能會沒有許可權,需要root。
android 4.2中關閉了native thread的堆棧列印,詳見 dalvik/vm/Thread.cpp的mpNativeThread方法:
[cpp] view plain
dvmPrintDebugMessage(target,
"\"%s\" sysTid=%d nice=%d sched=%d/%d cgrp=%s\n",
name, tid, getpriority(PRIO_PROCESS, tid),
schedStats.policy, schedStats.priority, schedStats.group);
mpSchedStat(target, tid);
// Temporarily disabled collecting native stacks from non-Dalvik
// threads because sometimes they misbehave.
//dvmDumpNativeStack(target, tid);
Native堆棧的列印被關掉了!不過對於大多數情況,可以直接將這個注釋打開。

2. debuggerd的堆棧mp
debuggerd是android的一個daemon進程,負責在進程異常出錯時,將進程的運行時信息mp出來供分析。debuggerd生 成的coremp數據是以文本形式呈現,被保存在 /data/tombstone/ 目錄下(名字取的也很形象,tombstone是墓碑的意思),共可保存10個文件,當超過10個時,會覆蓋重寫最早生成的文件。從4.2版本開 始,debuggerd同時也是一個實用工具:可以在不中斷進程執行的情況下列印當前進程的native堆棧。使用方法是:
[plain] view plain
debuggerd -b <pid>
這可以協助我們分析進程執行行為,但最最有用的地方是:它可以非常簡單的定位到native進程中鎖死或錯誤邏輯引起的死循環的代碼位置。

3. java代碼中列印堆棧
Java代碼列印堆棧比較簡單, 堆棧信息獲取和輸出,都可以通過Throwable類的方法實現。目前通用的做法是在java進程出現需要注意的異常時,列印堆棧,然後再決定退出或挽救。通常的方法是使用exception的printStackTrace()方法:
[java] view plain
try {
...
} catch (RemoteException e) {
e.printStackTrace();
...
}
當然也可以只列印堆棧不退出,這樣就比較方便分析代碼的動態運行情況。Java代碼中插入堆棧列印的方法如下:
[java] view plain
Log.d(TAG,Log.getStackTraceString(new Throwable()));

4. C++代碼中列印堆棧
C++也是支持異常處理的,異常處理庫中,已經包含了獲取backtrace的介面,Android也是利用這個介面來列印堆棧信息的。在Android的C++中,已經集成了一個工具類CallStack,在libutils.so中。使用方法:
[cpp] view plain
#include <utils/CallStack.h>
...
CallStack stack;
stack.update();
stack.mp();
使用方式比較簡單。目前Andoid4.2版本已經將相關信息解析的很到位,符號表查找,demangle,偏移位置校正都做好了。
[plain] view plain

5. C代碼中列印堆棧
C代碼,尤其是底層C庫,想要看到調用的堆棧信息,還是比較麻煩的。 CallStack肯定是不能用,一是因為其實C++寫的,需要重新封裝才能在C中使用,二是底層庫反調上層庫的函數,會造成鏈接器循環依賴而無法鏈接。 不過也不是沒有辦法,可以通過android工具類CallStack實現中使用的unwind調用及符號解析函數來處理。
這里需要注意的是,為解決鏈接問題,最好使用dlopen方式,查找需要用到的介面再直接調用,這樣會比較簡單。如下為相關的實現代碼,只需要在要 列印的文件中插入此部分代碼,然後調用getCallStack()即可,無需包含太多的頭文件和修改Android.mk文件:
[cpp] view plain
#define MAX_DEPTH 31
#define MAX_BACKTRACE_LINE_LENGTH 800
#define PATH "/system/lib/libcorkscrew.so"

typedef ssize_t (*unwindFn)(backtrace_frame_t*, size_t, size_t);
typedef void (*unwindSymbFn)(const backtrace_frame_t*, size_t, backtrace_symbol_t*);
typedef void (*unwindSymbFreeFn)(backtrace_symbol_t*, size_t);

static void *gHandle = NULL;

static int getCallStack(void){
ssize_t i = 0;
ssize_t result = 0;
ssize_t count;
backtrace_frame_t mStack[MAX_DEPTH];
backtrace_symbol_t symbols[MAX_DEPTH];

unwindFn unwind_backtrace = NULL;
unwindSymbFn get_backtrace_symbols = NULL;
unwindSymbFreeFn free_backtrace_symbols = NULL;

// open the so.
if(gHandle == NULL) gHandle = dlopen(PATH, RTLD_NOW);

// get the interface for unwind and symbol analyse
if(gHandle != NULL) unwind_backtrace = (unwindFn)dlsym(gHandle, "unwind_backtrace");
if(gHandle != NULL) get_backtrace_symbols = (unwindSymbFn)dlsym(gHandle, "get_backtrace_symbols");
if(gHandle != NULL) free_backtrace_symbols = (unwindSymbFreeFn)dlsym(gHandle, "free_backtrace_symbols");

if(!gHandle ||!unwind_backtrace ||!get_backtrace_symbols || !free_backtrace_symbols ){
ALOGE("Error! cannot get unwind info: handle:%p %p %p %p",
gHandle, unwind_backtrace, get_backtrace_symbols, free_backtrace_symbols );
return result;
}

count= unwind_backtrace(mStack, 1, MAX_DEPTH);
get_backtrace_symbols(mStack, count, symbols);

for (i = 0; i < count; i++) {
char line[MAX_BACKTRACE_LINE_LENGTH];

const char* mapName = symbols[i].map_name ? symbols[i].map_name : "<unknown>";
const char* symbolName =symbols[i].demangled_name ? symbols[i].demangled_name : symbols[i].symbol_name;
size_t fieldWidth = (MAX_BACKTRACE_LINE_LENGTH - 80) / 2;

if (symbolName) {
uint32_t pc_offset = symbols[i].relative_pc - symbols[i].relative_symbol_addr;
if (pc_offset) {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s+%u)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName, pc_offset);
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s (%.*s)",
i, symbols[i].relative_pc, fieldWidth, mapName,
fieldWidth, symbolName);
}
} else {
snprintf(line, MAX_BACKTRACE_LINE_LENGTH, "#%02d pc %08x %.*s",
i, symbols[i].relative_pc, fieldWidth, mapName);
}

ALOGD("%s", line);
}

free_backtrace_symbols(symbols, count);

return result;
}
對sched_policy.c的堆棧調用分析如下,注意具體是否要列印,在哪裡列印,還可以通過pid、uid、property等來控制一下,這樣就不會被淹死在trace的汪洋大海中。
[plain] view plain
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00010e82 /system/lib/libutils.so (androidSetThreadPriority+61)
D/SchedPolicy( 1350): #03 pc 00068104 /system/lib/libandroid_runtime.so (android_os_Process_setThreadPriority(_JNIEnv*, _jobject*, int, int)+7)
D/SchedPolicy( 1350): #04 pc 0001e510 /system/lib/libdvm.so (dvmPlatformInvoke+112)
D/SchedPolicy( 1350): #05 pc 0004d6aa /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+417)
D/SchedPolicy( 1350): #06 pc 00027920 /system/lib/libdvm.so
D/SchedPolicy( 1350): #07 pc 0002b7fc /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
D/SchedPolicy( 1350): #08 pc 00060c30 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+271)
D/SchedPolicy( 1350): #09 pc 0004cd34 /system/lib/libdvm.so
D/SchedPolicy( 1350): #10 pc 00049382 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #11 pc 00065e52 /system/lib/libandroid_runtime.so
D/SchedPolicy( 1350): #12 pc 0001435e /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+57)
D/SchedPolicy( 1350): #13 pc 00016f5a /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+513)
D/SchedPolicy( 1350): #14 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #15 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #16 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #17 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #18 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #19 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #20 pc 0000dac4 /system/lib/libc.so (pthread_create+160)
D/SchedPolicy( 1350): #00 pc 0000676c /system/lib/libcutils.so
D/SchedPolicy( 1350): #01 pc 00006b3a /system/lib/libcutils.so (set_sched_policy+49)
D/SchedPolicy( 1350): #02 pc 00016f26 /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+461)
D/SchedPolicy( 1350): #03 pc 00017380 /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+183)
D/SchedPolicy( 1350): #04 pc 0001b160 /system/lib/libbinder.so
D/SchedPolicy( 1350): #05 pc 00011264 /system/lib/libutils.so (android::Thread::_threadLoop(void*)+111)
D/SchedPolicy( 1350): #06 pc 000469bc /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+63)
D/SchedPolicy( 1350): #07 pc 00010dca /system/lib/libutils.so
D/SchedPolicy( 1350): #08 pc 0000e3d8 /system/lib/libc.so (__thread_entry+72)
D/SchedPolicy( 1350): #09 pc 0000dac4 /system/lib/libc.so (pthread_create+160)

6. 其它堆棧信息查詢

⑹ activity(Android組件中最重要的四大組件之一)詳細資料大全

activity是Android組件中最基本也是最為常見用的四大組件之一。Android四大組件有Activity,Service服務,Content Provider內容提供,BroadcastReceiver廣播接收器。

基本介紹

概要說明,詳細說明,基本狀態,狀態轉換,方法通知,

概要說明

Activity是Android組件中最基本也是最為常見用的四大組件(Activity,Service服務,Content Provider內容提供者,備頌孫BroadcastReceiver廣播接收器)之一。 Activity是一個應用程式組件,提供一個螢幕,用戶可以用來互動為了完成某項任務。 Activity中所有操作都與用戶密切相關,是一個負責與 用戶互動 的組件,可以通過setContentView(View)來 顯示指定控制項 。 在一個android套用中,一個Activity通常就是一個單獨的螢幕,它上面可以顯示一些控制項也可以監聽並處理用戶的事件做出回響。Activity之間通過Intent進行通信。

詳細說明

基本狀態

在android 中,Activity 擁有四種基本狀態:
  1. Active/Running
一個新 Activity 啟動入棧後,它顯示在螢幕最前端,處理是處於棧的最頂端(Activity棧頂),此時它處於可見並可和用戶互動的激活狀態,叫做活動狀態或者運行狀態(active or running)。 2 . Paused 當 Activity失去焦點, 被一個新的非全螢幕的Activity 或者一個透明的Activity 被放置在棧頂,此時的狀態叫做暫停狀態(Paused)。此時它依然與視窗管理器保持連線,Activity依然保持活力(保持所有的狀態,成員信息,和視窗管理器保持連線)櫻空,但是在系統記憶體極端低下的時候將被強行終止掉。所以它仍然仿鏈可見,但已經失去了焦點故不可與用戶進行互動。 3 . Sped 如果一個Activity被另外的Activity完全覆蓋掉,叫做停止狀態(Sped)。它依然保持所有狀態和成員信息,但是它不再可見,所以它的視窗被隱藏,當系統記憶體需要被用在其他地方的時候,Sped的Activity將被強行終止掉。 4 . Killed 如果一個Activity是Paused或者Sped狀態,系統可以將該Activity從記憶體中刪除,Android系統採用兩種方式進行刪除,要麼要求該Activity結束,要麼直接終止它的進程。當該Activity再次顯示給用戶時,它必須重新開始和重置前面的狀態。

狀態轉換

當一個 Activity 實例被創建、銷毀或者啟動另外一個 Activity 時,它在這四種狀態之間進行轉換,這種轉換的發生依賴於用戶程式的動作。下圖說明了 Activity 在不同狀態間轉換的時機和條件: 圖1. Activity 的狀 態轉換 如上所示,Android 程式設計師可以決定一個 Activity 的「生」,但不能決定它的「死」,也就是說程式設計師可以啟動一個 Activity,但是卻不能手動的「結束」一個 Activity。當你調用 Activity.finish() 方法時,結果和用戶按下 BACK 鍵一樣:告訴 Activity Manager 該 Activity 實例完成了相應的工作,可以被「回收」。隨後 Activity Manager 激活處於棧第二層的 Activity 並重新入棧,同時原 Activity 被壓入到棧的第二層,從 Active 狀態轉到 Paused 狀態。例如:從 Activity1 中啟動了 Activity2,則當前處於棧頂端的是 Activity2,第二層是 Activity1,當我們調用 Activity2.finish() 方法時,Activity Manager 重新激活 Activity1 並入棧,Activity2 從 Active 狀態轉換 Sed 狀態, Activity1. onActivityResult(int requestCode, int resultCode, Intent data) 方法被執行,Activity2 返回的數據通過 data 參數返回給 Activity1。 Activity棧 Android 是通過一種 Activity 棧的方式來管理 Activity 的,一個 Activity 的實例的狀態決定它在棧中的位置。處於前台的 Activity 總是在棧的頂端,當前台的 Activity 因為異常或其它原因被銷毀時,處於棧第二層的 Activity 將被激活,上浮到棧頂。當新的 Activity 啟動入棧時,原 Activity 會被壓入到棧的第二層。一個 Activity 在棧中的位置變化反映了它在不同狀態間的轉換。Activity 的狀態與它在棧中的位置關系如下圖所示: 圖2. Activity 的狀 與它在 中的位置 如上所示,除了最頂層即處在 Active 狀態的 Activity 外,其它的 Activity 都有可能在系統記憶體不足時被回收,一個 Activity 的實例越是處在棧的底層,它被系統回收的可能性越大。系統負責管理棧中 Activity 的實例,它根據 Activity 所處的狀態來改變其在棧中的位置。

方法通知

下面的圖顯示了Activity的重要狀態轉換,矩形框表明Activity在狀態轉換之間的回調介面,開發人員可以重載實現以便執行相關代碼,帶有顏色的橢圓形表明Activity所處的狀態。 3 . Activity 的狀 轉換的方法和實現 在上圖中,Activity有三個關鍵的循環: 1. 整個的生命周期,從onCreate(Bundle)開始到onDestroy()結束。Activity在onCreate()設定所有的「全局」狀態,在onDestory()釋放所有的資源。例如:某個Activity有一個在後台運行的執行緒,用於從網路下載數據,則該Activity可以在onCreate()中創建執行緒,在onDestory()中停止執行緒。 2. 可見的生命周期,從onStart()開始到onS()結束。在這段時間,可以看到Activity在螢幕上,盡管有可能不在前台,不能和用戶互動。在這兩個介面之間,需要保持顯示給用戶的UI數據和資源等,例如:可以在onStart中注冊一個IntentReceiver來監聽數據變化導致UI的變動,當不再需要顯示時候,可以在onS()中注銷它。onStart(),onS()都可以被多次調用,因為Activity隨時可以在可見和隱藏之間轉換。 3. 前台的生命周期,從onResume()開始到onPause()結束。在這段時間里,該Activity處於所有 Activity的最前面,和用戶進行互動。Activity可以經常性地在resumed和paused狀態之間切換,例如:當設備准備休眠時,當一個 Activity處理結果被分發時,當一個新的Intent被分發時。所以在這些介面方法中的代碼應該屬於非常輕量級的。

⑺ Android知識點解析(1)

Activity生命周期

什麼是activity:提供給用戶交互的介面,實現點擊、滑動等操作的界面。

4種狀態:runningpausestoppedkilled

Activity啟動->onCreate()->onStart()->onResume() onResume()和onStart()均為前台可見

點擊Home按鍵純磨返回到主界面(Activity不可見)->onPause()->onStop()

當再次回到原Activity時->onRestart()->onStart()->onResume()

退出當前Activity時->onPause()->onStop()->onDestroy() Destroy:銷毀和資源回收

進程優先順序(優先順序:高----->低):前台/可見/服務/後台/空

任務棧(Task)

棧結構:後進先出 一個Task包含Activity的集合,通過Task管理每一個Activity,可以結合Activity的啟動模式去理解。實例:退出APP時,需要將Task種的Activity完全地移除,才能安全並且完全地退出應用。一個APP當中可能不止一個Task,但一個Activity可以獨享一個Task

啟動模式

1.Standard/標准模式:每啟動一個Activity,則不中塵會考慮當前Task中是否已經存在Activity的實例做培斗,直接創建並放置棧頂,缺點是比較消耗資源。

2.SingleTop棧頂復用模式:如果棧頂存在當前Activity的實例,則不去創建,直接復用棧頂的Activity。若不是,則創建。

3.SingleTask棧內復用模式:單例,檢測整個任務棧是否存在Activity的實例,如果存在則移除銷毀實例以上的Activity,並回調onNewIntent()方法。

4.SingleInstance:ActivityA獨享一個Task,且App中有且只有一個ActivityA的實例。

Schema跳轉協議

頁面內跳轉協議,通過定義自己的Schema協議,可以方便地跳轉到app的各個頁面。應用場景:服務端可以定製化告訴App跳轉到相應頁面;可以通過通知欄消息定製化跳轉頁面;通過H5頁面跳轉等

⑻ Android基礎之Activity 運行模式與回退棧

LaunchMode 定義的是activity實例與task之間的關系,可以通過下面的兩種方式來定義:

在Activity A中啟動B,可以利用Activity B在清單中的launchmode定義,也可以在A中調用startActivity()的時候通過intent的flag傳入,當兩種方式都有定義,intent的flag參數會覆蓋掉B原有的定義。

利用Activity 元素的launchMode屬性
launchMode屬性指定Activity如何被運行到一個task中。launchMode的值有四種:

默認, 每次啟動Activity系統都會產生一個新的實例,並且把intent發送給新產生的實例,這個Activity可以被實例化多次,每個實例可以屬於不同的task,每個task也可以保有多個此Activity的實例。

如果當前task 的回退棧棧裂畝頂已經存在一個此Activity的實例,系統通過調用這個實例的onNewIntent()方法把intent發送給這個Activity實例,而不是創建一個新的此Activity的實例。這個Activity也可以被實例化多次,每個實例可以屬於不同的task,每個task可一個保有多個實例(僅限於此Activity已存在的實例不在棧頂)
注意:
應用場合如下:不想出現2個同樣的activity在頂部。比如用戶正在一個activity閱讀信息,這時來了notification,用戶點擊後應該更新這些信息,而不是新建一個activity,這樣在點擊back時,就不會出現回到舊信息activity的情況了。這種情況正是下面這段英語提到的。
Note: When a new instance of an activity is created, the user can press the Back button to return to the previous activity. But when an existing instance of an activity handles a new intent, the user cannot press the Back button to return to the state of the activity before the new intent arrived in onNewIntent()
.
例如,當前回退棧中有A,B,C,D四個Activity,全部是Standard,在D中調用startActivity()去啟動B,intent的flag設置成FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_NEW_TASK,系統發現棧中有B,會先銷毀這個B,再原位置重建B,清空CD,而不是把這個新建的B的實例壓入棧頂,這里之所以會銷毀B再新建B,因為B的launchmode是Standard,無論什麼情況下啟動,都需要new一個B的實例,但如果此時B是SingleTop的,系統會把這個intent通過onNewIntent傳給已經在棧中的B的實例,不需要銷毀再創此昌建,仍需要清空CD。

系統會創建一個新的task並且把這個實例放在棧底( 此處有疑問,測試發現並不一定是棧底 ),但是,如果在一個單獨的task中已經存在一個此Activity的實例,系統會把intent通過onNewIntent()發送給這個實例( 測試發現如果在回退棧中,該Activity的上面還有其他Activity,啟動森源扒此Activity會清空棧中此Activity上面的其他Activity ),而不是創建一個新的實例。同一時間在只有一個此Activity的實例存在於系統中。

與SingleTask一樣,不同的是SingleTask的Activity所在的task中可以有其他的Activity,而SingleInstance的Activity獨佔一個task,並且在整個系統中只有唯一的一個實例。由這個Activity啟動的其他Activity都會在新的task中打開。

另一個例子,系統自帶瀏覽器APP把瀏覽器Activity聲明為SingleTask,通過在Activity標簽里的launchMode進行指定,這意味著如果你發送一個intent啟動瀏覽器,不管是為瀏覽器新開啟一個task還是從瀏覽器已經在後台保有的task中啟動瀏覽器,瀏覽器Activity與你的APP不在同一個task。

不管一個Activity是不是在一個新的task中啟動,點擊返回都會返回前一個Activity。不過,如果啟動一個LaunchMode為singleTask的Activity,如果該Activity此時在一個處於後台的task中,整個task會變成前台task,此時,回退棧會包含由這個後台task攜帶過來所有Activity,放在回退棧的棧頂,下圖說明這種情況。

在一個新的task里啟動Activity. 如果已經有Activity實例運行在某一task中,啟動這個Activity會把該實例所在的task帶到前台,由該實例的onNewIntent()來接收新的intent。

如果被啟動的Activity就是當前的Activity,這個已經存在的實例通過onNewIntent()接收intent,不會產生新的實例。

被啟動的Activity如果已經存運行於當前task,回退棧中所有在此Activity上面的Activity都將被銷毀,此Activity通過onNewIntent()接收新的intent。
例如,一個task中有A,B,C,D,四個Activity,如果D 調用startActivtiy()啟動Activity B,C和D會被銷毀,B接收這個intent,回退棧中有A,B。
上例中的Activity B的實例,或者通過onNewIntent()接收新的intent,或者銷毀新建來處理新的intent。如果B的launchmode是standard,並且沒有設置FLAG_ACTIVITY_SINGLE_TOP,那麼B會被銷毀重啟,如果是其他launchmode或者設置了FLAG_ACTIVITY_SINGLE_TOP,則會通過onNewIntent()接收。
FLAG_ACTIVITY_CLEAR_TOP 和FLAG_ACTIVITY_NEW_TASK結合使用會有個不錯的效果。
如果啟動的Activity位於task的底部,它會把所在task帶到前台,並且清理狀態至root狀態,當從通知欄里打開一個Activity的會非常有用。

Affinity指的是一個Activity偏向於從屬於哪個task,默認情況下,一個APP內的所有Activity互相之間共享一個affinity的值,所以,所有同一APP下的所有Activity都偏向於從屬於同一個task。但是,這個值是可以更改的,不同APP內的Activity可以共享一個affinity,同一個APP內的Activity也可以被分配不同的affinity的值。
affinity的值可以通過修改Activity標簽的taskAffinity屬性來修改。
這個屬性接收一個String的值,必須在manifest標簽范圍內是唯一的值,因為系統是通過名稱來標識APP的affinity的值的。
Affinity作用於以下兩種情況:

通過startActivity()啟動一個新的Activity時,默認情況下,新的Activity會被壓入與啟動者相同的回退棧中。但是,如果在啟動Activity的時候,使用了FLAG_ACTIVITY_NEW_TASK 這個標志,系統會為新的Activity尋找一個新的task。通常情況下,是一個新的task。但是也並不是必須的。如果系統中有一個task的affinity值與新的Activity的值相同,新的Activity會被分配到這個task中。如果沒有這樣的task,就啟動一個新的task。如果這個標志產生了一個新的task,當用戶點擊home鍵離開的時候,必須要有某種方式能夠使用戶返回到這個task來。有些實體(例如通知管理器)總是從一個外部task中啟動Activity,所以在通過startActivity()啟動新的Activity時總是需要傳遞FLAG_ACTIVITY_NEW_TASK 這個標志。如果你有一個Activity可以被外部實體可能這個標志啟動,注意用戶可以有一種獨立的方式回到啟動它的task,例如點擊啟動圖標。

這種情況下,一個Activity可以動啟動它的那個task移動到它的affinity值對應的task中,當那個task回到前台。例如,假設,一個報告指定城市天氣情況的Activity作為一個旅行APP的一部分,它跟其他處在同一APP的Activity一樣有一個相同的affinity值,並且允許通過這個屬性來調整目標task。當你的一個Activity啟動了這個天氣預報Activity,它默認跟你的Activity在一個task里,但是,當旅行APP進入到前台,這個天氣預報Activity又會被重新分配給旅行APP並且在旅行APP內展示。
提示:
如果一個APK文件從用戶的角度看是多款APP,可能需要這個屬性來設置不同的affinity來關聯不同的APP。

如果用戶離開一個task太長時間,系統會清除task中的所有Activity僅僅保留這下根Activity。當用戶返回到這個task的時候,只有這個根Activity會被恢復。系統通過這種方式來處理,是因為經過相當長的一段時間之後,用戶已經拋棄他們曾經正在做的事情,計劃再回來的時候做點新的事情。

你可以通過如下屬性來更改這種行為:

參考文獻:
Tasks and Back Stack
<activity>
Android 閱讀Tasks and Back Stack文章後的重點摘抄

⑼ Android中,Context,什麼是Context

Context對象是如此常見和傳遞使用,它可能會很容易產生並不是你預期的情形。載入資源、啟動一個新的Activity、獲取系統服務、獲取內部文件路徑以及創建view(其實還遠不止這些)統統都需要Context對象來完成。我(原文作者)想做的只是給大家提供一些Context是如何工作的見解,以及讓大家在應用中更有效的使用Context的技巧。

Context的類型
並不是所有的context實例都是等價的。根據Android應用的組件不同,你訪棗兄掘問的context推向有些細微的差別。

Application - 是一個運行在你的應用進程中的單例。在Activity或者Service中,它可以通過getApplication()函數獲得,或者人和繼承於context的對象中,通過getApplicationContext()方法獲得。不管你是通過何種方法在哪裡獲得的,在一個進程內,你總是獲得到同一個實例。

Activity/Service - 繼承於ContextWrapper,它實現了與context同樣API,但是代理這些方法調用到內部隱藏的Context實例,即我們所知道的基礎context。任何時候當系統創建一個新的Activity或者Service實例的時候,它也創建一個新的ContextImpl實例來做所有的繁重的工作。每一個Activity和Service以及其對應的基礎context,對每個實例來說都是唯一的。

BroadcastReciver - 它本身不是context,也沒有context在它裡面,但是每當一個新的廣播到達的時候,框架都傳遞一個context對象到onReceive()。這個context是一個ReceiverRestrictedContext實例,它有兩個主要函數被禁掉:registerReceiver()和bindService()。這兩個凳核函數在BroadcastReceiver.onReceive()不允許調用。每次Receiver處理一個廣播,傳遞進來的context都是一個新的實例。

ContentProvider - 它本身也不是一個Context,但是它可以通過getContext()函數給你一個Context對象。如果ContentProvider是在調用者的的本地(例如,在同一個應用進程),getContext()將返回的是Application單例。然而,如果調用這和ContentProvider在不同的進程的時候,它將返回一個新創建的實例代表這個Provider所運行的包。

保存引用
第一個我們需要解決問題是,在一個對象或者類內部保存一個context引用,而它生命周期卻超過其保存引用的對象的塵遲生命周期。例如,創建一個自定義的單例,它需要一個context來載入資源或者獲取ContentProvider,從而保存一個指向當前Activiy或者Service的引用在單例中。

糟糕的單例

[java] view plain
public class CustomManager {
private static CustomManager sInstance;

public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);
}

return sInstance;
}

private Context mContext;

private CustomManager(Context context) {
mContext = context;
}
}

這里的問題在於,我們不知道這個context是從哪裡來的,並且如果保存一個最終指向的是Activity或者Servece的引用是並不安全的。這是一個問題,是因為一個單例在類的內部維持一個唯一的靜態引用,這意味著我們的對象,以及所有其他它所引用的對象,將永遠不能被垃圾回收。假如這個Context是一個Activity,我們將保存與這個Activity相關的所有的view以及其他大的對象,從而造成內存泄漏。
為了解決這個問題,我們修改單例永遠只是保存Application context:

改善的單例:

[java] view plain
public class CustomManager {
private static CustomManager sInstance;

public static CustomManager getInstance(Context context) {
if (sInstance == null) {
//Always pass in the Application Context
sInstance = new CustomManager(context.getApplicationContext());
}

return sInstance;
}

private Context mContext;

private CustomManager(Context context) {
mContext = context;
}
}

現在這個例子中,我們的Context來自哪裡都沒有關系,因為我們這里保存引用是安全的。Application Context 本身就是一個單例,所以我們再創建另外一個static引用,不會造成任何內存泄漏。另外一個很好的例子是,在後台線程或者一個等待的Handler中保存Context的引用,也可以使用這樣的方法。

為什麼我們不能總是引用Application context呢?正如前面說的,引用Application context永遠不用擔心內存泄漏的問題。問題的答案,就像我在開始的介紹中說的,是因為不同context並不是等價的。

Context的能力
Conext能做的通用操作決定於這個context最初來源於哪裡。下表所列的是,在應用中常見的會收到context對象的,以及對應的每種情況,它可以用於哪些地方:

Application
Activity
Service
ContentProvider
BroadcastReceiver

Show a Dialog NO YES NO NO NO
Start an Activity NO1 YES NO1 NO1 NO1
Layout Inflation NO2 YES NO2 NO2 NO2
Start a Service YES YES YES YES YES
Bind to a Service YES YES YES YES NO
Send a Broadcast YES YES YES YES YES
Register BroadcastReceiver YES YES YES YES NO3
Load Resource Values YES YES YES YES YES

註:NO1 表示Application context的確可以開始一個Activity,但是它需要創建一個新的task。這可能會滿足一些特定的需求,但是在你的應用中會創建一個不標準的回退棧(back stack),這通常是不推薦的或者不是是好的實踐。
NO2 表示這是非法的,但是這個填充(inflation)的確可以完成,但是是使用所運行的系統默認的主題(theme),而不是你app定義的主題。
NO3 在Android4.2以上,如果Receiver是null的話(這是用來獲取一個sticky broadcast的當前 值的),這是允許的。

用戶界面UI
從前面的表格中可以看到,application context有很多功能並不是合適去做,而這些功能都與UI相關。實際上,只有Activity能夠處理所有與UI相關的任務。其他類別的context實例功能都差不多。

幸運的是,在應用中這三種操作基本上都不需要在Activity范圍之外進行,這很可能是android框架故意這么設計的。嘗試顯示一個使用Aplication context創建的Dialog,或者使用Application context開始一個Activity,系統會拋出一個異常,讓你的application崩潰,非常強的告訴你某些地方出了問題。

一個並不明顯的問題是填充布局(inflating layout)。如果你已經讀過了我(原文作者)的上一篇文章Layout inflation,你就已經知道它可能是一個非常神秘過程,伴隨一些隱藏的行為。使用正確的context關繫到其中的一個行為。當你使用Application context來inflate一個布局的時候,框架並不會報錯,並返回一個使用系統默認的主題創建一個完美的view給你,而沒有考慮你的applicaiton自定義的theme和style。這是因為Acitivity是唯一的綁定了在manifast文件種定義主題的Context。其他的Context實例將會使用系統默認的主題來inflater你的view。導致顯示的結果並不是你所希望的。

規則的路口
可能有些讀者已經得出兩個規則互相矛盾的結論。可能有些情況下,在某些Application的設計中,我們可能既必須長期保存一個的引用,並且為了完成與UI相關的工作又必須保存一個Activity。如果出現這種情況,我將會強烈建議你重新考慮你的設計,它將是一個很好的「反框架」教材。

經驗法則
絕大多數情況下,使用在你的所工作的組建內部能夠直接獲取的Context。只要這個引用沒有超過這個組建的生命周期,你可以安全的保存這個引用。一旦你要保存一個context的引用,它超過了你的Activity或者Service的生命周期范圍,甚至是暫時的,你就需要轉換你的引用為Application context。

⑽ Android 多返回棧技術詳解

用戶通過系統返回按鈕導航回去的一組頁面,在開發中被稱為返回棧 (back stack)。多返回棧即一堆 "返回棧",對多返回棧的支持是在 Navigation 2.4.0-alpha01 和 Fragment 1.4.0-alpha01 中開始的。本文將為您展開多返回棧的技術詳解。

無論您在使用 Android 全新的 手勢導航 還是傳統的導航欄,用戶的 "返回" 操作是 Android 用戶體驗中關鍵的一環,把握好返回功能的設計可以使應用更加貼近整個生態系統。

在最簡單的應用場景中,系統返回按鈕僅僅 finish 您的 Activity。在過去您可能需要覆寫 Activity 的 onBackPressed() 方法來自定義返回操作,而在 2021 年您無需再這樣操作。我們已經在 OnBackPressedDispatcher 中提供了 針對自定義返回導航的 API。實際上這與 FragmentManager 和 NavController 中 已經 添加的 API 相同。

這意味著當您使用 Fragments 或 Navigation 時,它們會通過 OnBackPressedDispatcher 來確保您調用了它們返回棧的 API,系統的返回按鈕會將您推入返回棧的頁面逐層返回。

多返回棧不會改變這個基本邏輯。系統的返回按鈕仍然是一個單向指令 —— "返回"。這對多返回棧 API 的實現機制有深遠影響。

在 surface 層級,對於 多返回棧的支持 貌似很直接,但其實需要額外解釋一下 "Fragment 返回棧" 到底是什麼。FragmentManager 的返回棧其實包含的不是 Fragment,而是由 Fragment 事務組成的。更准確地說,是由那些調用了 addToBackStack(String name) API 的事務組成的。

這就意味著當您調用 commit() 提交了一個調用過 addToBackStack() 方法的 Fragment 事務時, FragmentManager 會執行所有您在事務中所指定的操作 (比如 替換操作 ),從而將每個 Fragment 轉換為預期的狀態。然後 FragmentManager 會將該事務作為它返回棧的一部分。

當您調用 popBackStack() 方法時 (無論是直接調用,還是通過系統返回鍵以 FragmentManager 內部機制調用),Fragment 返回棧的最上層事務會從棧中彈出 -- 比如新添加的 Fragment 會被移除,隱藏的 Fragment 會顯示。這會使得 FragmentManager 恢復到最初提交 Fragment 事務之前的狀態。

也就是說 popBackStack() 變成了銷毀操作: 任何已添加的 Fragment 在事務被彈出的時候都會丟失它的狀態。換言之,您會失去視圖的狀態,任何所保存的實例狀態 (Saved Instance State),並且任何綁定到該 Fragment 的 ViewModel 實例都會被清除。這也是該 API 和新的 saveBackStack() 方法之間的主要區別。 saveBackStack() 可以實現彈出事務所實現的返回效果,此外它還可以確保視圖狀態、已保存的實例狀態,以及 ViewModel 實例能夠在銷毀時被保存。這使得 restoreBackStack() API 後續可以通過已保存的狀態重建這些事務和它們的 Fragment,並且高效 "重現" 已保存的全部細節。太神奇了!

而實現這個目的必須要解決大量技術上的問題。

雖然 Fragment 總是會保存 Fragment 的視圖狀態,但是 Fragment 的 onSaveInstanceState() 方法只有在 Activity 的 onSaveInstanceState() 被調用時才會被調用。為了能夠保證調用 saveBackStack() 時 SavedInstanceState 會被保存,我們 需要在 Fragment 生命周期切換 的正確時機注入對 onSaveInstanceState() 的調用。我們不能調用得太早 (您的 Fragment 不應該在 STARTED 狀態下保存狀態),也不能調用得太晚 (您需要在 Fragment 被銷毀之前保存狀態)。

這樣的前提條件就開啟了需要 解決 FragmentManager 轉換到對應狀態的問題,以此來保障有一個地方能夠將 Fragment 轉換為所需狀態,並且處理可重入行為和 Fragment 內部的狀態轉換。

在 Fragment 的重構工作進行了 6 個月,進行了 35 次修改時,發現 Postponed Fragment 功能已經嚴重損壞,這一問題使得被推遲的事務處於一個中間狀態 —— 既沒有被提交也並不是未被提交。之後的 65 個修改和 5 個月的時間里,我們幾乎重寫了 FragmentManager 管理狀態、延遲狀態切換和動畫的內部代碼,具體請參見我們之前的文章《全新的 Fragment: 使用新的狀態管理器》。

隨著技術問題的逐步解決,包括更加可靠和更易理解的 FragmentManager ,我們新增加了兩個 API: saveBackStack() 和 restoreBackStack() 。

如果您不使用這些新增 API,則一切照舊: 單個 FragmentManager 返回棧和之前的功能相同。現有的 addToBackStack() 保持不變 —— 您可以將 name 賦值為 null 或者任意 name 。然而,當您使用多返回棧時, name 的作用就非常重要了: 在您調用 saveBackStack() 和之後的 restoreBackStack() 方法時,它將作為 Fragment 事務的唯一的 key。

舉個例子,會更容易理解。比如您已經添加了一個初始的 Fragment 到 Activity,然後提交了兩個事務,每個事務中包含一個單獨的 replace 操作:

也就是說我們的 FragmentManager 會變成這樣:

比如說我們希望將 profile 頁換出返回棧,然後切換到通知 Fragment。這就需要調用 saveBackStack() 並且緊跟一個新的事務:

現在我們添加 ProfileFragment 的事務和添加 EditProfileFragment 的事務都保存在 "profile" 關鍵字下。這些 Fragment 已經完全將狀態保存,並且 FragmentManager 會隨同事務狀態一起保持它們的狀態。很重要的一點: 這些 Fragment 的實例並不在內存中或者在 FragmentManager 中 —— 存在的僅僅只有狀態 (以及任何以 ViewModel 實例形式存在的非配置狀態)。

替換回來非常簡單: 我們可以在 "notifications" 事務中同樣調用 saveBackStack() 操作,然後調用 restoreBackStack() :

這兩個堆棧項高效地交換了位置:

維持一個單獨且活躍的返回棧並且將事務在其中交換,這保證了當返回按鈕被點擊時, FragmentManager 和系統的其他部分可以保持一致的響應。實際上,整個邏輯並未改變,同之前一樣,仍然彈出 Fragment 返回棧的最後一個事務。

這些 API 都特意按照最小化設計,盡管它們會產生潛在的影響。這使得開發者可以基於這些介面設計自己的結構,而無需通過任何非常規的方式保存 Fragment 的視圖狀態、已保存的實例狀態、非配置的狀態。

當然了,如果您不希望在這些 API 之上構建您的框架,那麼可以使用我們所提供的框架進行開發。

Navigation Component 最初 是作為通用運行時組件進行開發的,其中不涉及 View、Fragment、Composable 或者其他屏幕顯示相關類型及您可能會在 Activity 中實現的 "目的地界面"。然而,NavHost 介面 的實現中需要考慮這些內容,通過它添加一個或者多個 Navigator 實例時,這些實例 確實 清楚如何與特定類型的目的地進行交互。

這也就意味著與 Fragment 的交互邏輯全部封裝在了 navigation-fragment 開發庫和它其中的 FragmentNavigator 與 DialogFragmentNavigator 中。類似的,與 Composable 的交互邏輯被封裝在完全獨立的 navigation-compose 開發庫和它的 ComposeNavigator 中。這里的抽象設計意味著如果您希望僅僅通過 Composable 構建您的應用,那麼當您使用 Navigation Compose 時無需任何涉及到 Fragment 的依賴。

該級別的分離意味著 Navigation 中有兩個層次來實現多返回棧:

仍需特別注意那些 尚未 更新的 Navigator ,它們無法支持保存自身狀態。底層的 Navigator API 已經整體重寫來支持狀態保存 (您需要覆寫新增的 navigate() 和 popBackStack() API 的重載方法,而不是覆寫之前的版本),即使 Navigator 並未更新, NavController 仍會保存 NavBackStackEntry 的狀態 (在 Jetpack 世界中向後兼容是非常重要的)。

如果您僅僅在應用中使用 Navigation,那麼 Navigator 這個層面更多的是實現細節,而不是您需要直接與之交互的內容。可以這么說,我們已經完成了將 FragmentNavigator 和 ComposeNavigator 遷移到新的 Navigator API 的工作,使其能夠正確地保存和恢復它們的狀態,在這個層面上您無需再做任何額外工作。

如果您正在使用 NavigationUI,它是用於連接您的 NavController 到 Material 視圖組件的一系列專用助手,您會發現對於菜單項、 BottomNavigationView (現在叫 NavigationRailView ) 和 NavigationView ,多返回棧是 默認啟用 的。這就意味著結合 navigation-fragment 和 navigation-ui 使用就可以。

NavigationUI API 是基於 Navigation 的其他公共 API 構建的,確保您可以准確地為自定義組件構建您自己的版本。保證您可以構建所需的自定義組件。啟用保存和恢復返回棧的 API 也不例外,在 Navigation XML 中通過 NavOptions 上的新 API,也就是 navOptions Kotlin DSL,以及 popBackStack() 的重載方法可以幫助您指定 pop 操作保存狀態或者指定 navigate 操作來恢復之前已保存的狀態。

比如,在 Compose 中,任何全局的導航模式 (無論是底部導航欄、導航邊欄、抽屜式導航欄或者任何您能想到的形式) 都可以使用我們在與 底部導航欄集成 所介紹的相同的技術,並且結合 saveState 和 restoreState 屬性一起調用 navigate() :

對用戶來說,最令人沮喪的事情之一便是丟失之前的狀態。這也是為什麼 Fragment 用一整頁來講解 保存與 Fragment 相關的狀態,而且也是我非常樂於更新每個層級來支持多返回棧的原因之一:

如果您希望了解 更多使用該 API 的示例,請參考 NavigationAdvancedSample (它是最新更新的,且不包含任何用於支持多返回棧的 NavigationExtensions 代碼)。

對於 Navigation Compose 的示例,請參考 Tivi。

如果您遇到任何問題,請使用官方的問題追蹤頁面提交關於 Fragment 或者 Navigation 的 bug,我們會盡快處理。

閱讀全文

與android定義棧相關的資料

熱點內容
安卓抖音顯示沒網路是怎麼回事 瀏覽:817
2d我的世界源碼 瀏覽:618
怎樣製作貼天花板的解壓球 瀏覽:337
伺服器如何打開蘋果 瀏覽:96
高響應比演算法的實現 瀏覽:848
windows寫命令行 瀏覽:61
騰訊天津數據中心伺服器雲空間 瀏覽:974
單片機掃描按鍵 瀏覽:386
如何設置google伺服器 瀏覽:695
linuxtrace工具源碼 瀏覽:178
源碼第二次開發 瀏覽:784
如何獲取網頁php源碼 瀏覽:729
還用飛那麼源碼 瀏覽:204
雲伺服器鏡像可以隨時更換嗎 瀏覽:92
r600a壓縮機多少錢 瀏覽:237
程序員那麼可愛第幾集在重慶相遇 瀏覽:674
上班兩公里源碼 瀏覽:819
南寧溯源碼燕窩訂制 瀏覽:934
在個人文件夾中新建文件 瀏覽:445
中國國家地理pdf下載 瀏覽:109