請點贊兰迫,你的點贊對我意義重大,滿足下我的虛榮心炬称。
??常在河邊走汁果,哪有不濕鞋×崆或許面試過程中你遇到的問題就在這呢须鼎?
??關(guān)注我個人簡介鲸伴,面試不迷路~
一、Acitvity的生命周期晋控,如何摧毀一個Activity? (美團)
這道題想考察什么
- 是否了解AMS汞窗、Activity的相關(guān)知識?
考察的知識點
- AMS的基本知識
- Activity的啟動和銷毀流程
考生應(yīng)該如何回答
注意事項
本節(jié)內(nèi)容會涉及到AMS的內(nèi)容赡译,如果不了解建議先學(xué)習相關(guān)章節(jié)
Activity銷毀的相關(guān)流程圖
ActivityFinish流程以及結(jié)果接收的流程
在執(zhí)行完setResult
以及finish
之后仲吏,開始啟動Activity銷毀以及結(jié)果返回的流程。
setResult以及finish(用戶進程)
保存resultCode
以及data
蝌焚,并且通過finishActivity
通知AMS開始銷毀當前Activity裹唆,并且攜帶參數(shù)
public final void setResult(int resultCode, Intent data) {
synchronized (this) {
mResultCode = resultCode;
mResultData = data;
}
}
private void finish(int finishTask) {
if (mParent == null) {
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
if (false) Log.v(TAG, "Finishing self: token=" + mToken);
try {
if (resultData != null) {
resultData.prepareToLeaveProcess(this);
}
if (ActivityManager.getService()
.finishActivity(mToken, resultCode, resultData, finishTask)) {
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}
ActivityManagerService.finishActivity(System_Server進程)
在ActivityManagerService
中,會判斷是否要終止整個Task只洒,如果是的話许帐,則直接把Task從ActivityStackSupervisor中移除,如果不是的話毕谴,則調(diào)用requestFinishActivityLoacked
銷毀Activity成畦,并且將resultData
傳入。
final boolean finishWithRootActivity =
finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
|| (finishWithRootActivity && r == rootR)) {
// 判斷是否要結(jié)束整個Task
res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
if (!res) {
Slog.i(TAG, "Removing task failed to finish activity");
}
} else {
// 如果不清除Task的話涝开,則只清理單獨的Activity
res = tr.getStack().requestFinishActivityLocked(token, resultCode,resultData, "app-request", true);
if (!res) {
Slog.i(TAG, "Failed to finish by app-request");
}
}
ActivityStack.finishActivityLocked(System_Server進程)
ActivityStack.requestFinishActivityLocked
最終會調(diào)用到finishActivityLocked
函數(shù)中循帐。
- 修改
ActivityRecord
中的finishing
標識位為true
- 停止接收Key事件
- 將Result結(jié)果保存到接收者中
- 開始暫停當前Activity
...
try {
// 將ActivityRecord中的finishing置成true,表示正在處于Finishing狀態(tài)
r.makeFinishingLocked();
...
// 停止接收Key事件的分發(fā)
r.pauseKeyDispatchingLocked();
// 重新調(diào)整FocusedActivity棧
adjustFocusedActivityStackLocked(r, "finishActivity");
// 將resultData也就是返回的數(shù)據(jù)保存到r.resultTo的ActivityRecord中
finishActivityResultsLocked(r, resultCode, resultData);
// 如果當前的Activity是該Task最后一個Activity的話舀武,那么就需要銷毀Task
final boolean endTask = index <= 0;
if (mResumedActivity == r) {
...
// 準備Activity切換的窗口動畫
mWindowManager.prepareAppTransition(transit, false);
// 告訴WindowManager準備當前的Activity準備移除
r.setVisibility(false);
// 如果當前沒有Pausing的Activity的話
if (mPausingActivity == null) {
// 開始Pausing當前的Activity
startPausingLocked(false, false, null, pauseImmediately);
}
// 如果需要銷毀Task的話拄养,那么就開始銷毀
if (endTask) {
mStackSupervisor.removeLockedTaskLocked(task);
}
} else if (r.state != ActivityState.PAUSING) {
// 如果當前的Activity正處于Pausing 的狀態(tài)的話,那么就會等它Pausing完成之后開始銷毀银舱,否則在這個地方直接銷毀
if (r.visible) {
prepareActivityHideTransitionAnimation(r, transit);
}
final int finishMode = (r.visible || r.nowVisible) ? FINISH_AFTER_VISIBLE
: FINISH_AFTER_PAUSE;
// 直接調(diào)用finishCurrentActivityLocked銷毀當前的Activity
final boolean removedActivity = finishCurrentActivityLocked(r, finishMode, oomAdj)
== null;
...
}
...
ActivityStack. startPausingLocked(System_Server進程)
- 更新
mResumedActivity
瘪匿、prev.state
- 執(zhí)行
schedulePauseActivity
暫停Activity - 延遲發(fā)送消息,檢測Pause超時
...
ActivityRecord prev = mResumedActivity;
if (prev == null) {
if (resuming == null) {
// 如果沒有要暫停的Activity的話寻馏,就直接resume棧頂?shù)腁ctivity
mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
return false;
}
...
// 更新當前保存的ResumedActivity棋弥,因為即將進入Pausing狀態(tài)
mResumedActivity = null;
// 將正在Pausing的Activity賦值為即將進入Pausing的Activity
mPausingActivity = prev;
mLastPausedActivity = prev;
mLastNoHistoryActivity = (prev.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
|| (prev.info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0 ? prev : null;
// 將當前Activity狀態(tài)置成Pausing
prev.state = ActivityState.PAUSING;
...
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
if (prev.app != null && prev.app.thread != null) {
...
// 回調(diào)Activity的Pause函數(shù),pauseImmediately為false
prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
userLeaving, prev.configChangeFlags, pauseImmediately);
...
} else {
mPausingActivity = null;
mLastPausedActivity = null;
mLastNoHistoryActivity = null;
}
}
...
if (mPausingActivity != null) {
// 判斷是否是立即暫停操软,如果是的話嘁锯,則立即調(diào)用completePauseLocked
if (pauseImmediately) {
// If the caller said they don't want to wait for the pause, then complete
// the pause now.
completePauseLocked(false, resuming);
return false;
} else {
// 如果不是立即暫停的話宪祥,那么就會等待Activity的onPause完畢聂薪,并且檢測超時
// 如果pause超時的話,也會繼續(xù)執(zhí)行下面的流程
schedulePauseTimeout(prev);
return true;
}
} else {
// This activity failed to schedule the
// pause, so just treat it as being paused now.
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next.");
if (resuming == null) {
mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
return false;
}
ActivityStack.activityPausedLocked(System_Server進程)
當Activity進入Pause的狀態(tài)后蝗羊,會通過Binder回調(diào)到activityPausedLocked
該接口藏澳。
// 根據(jù)mToken找到ActivityRecord
final ActivityRecord r = isInStackLocked(token);
if (r != null) {
// 接收到ActivityPause完成的回調(diào)后,清理PAUSE_TIMEOUT_MSG
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
}
// 判斷當前Pausing的Activity是否是已經(jīng)回調(diào)過Pause的Activity
if (mPausingActivity == r) {
...
// 如果是的話耀找,則調(diào)用該函數(shù)
completePauseLocked(true /* resumeNext */, null /* resumingActivity */)翔悠;
} else {
...
if (r.state == ActivityState.PAUSING) {
r.state = ActivityState.PAUSED;
if (r.finishing) {
if (DEBUG_PAUSE) Slog.v(TAG,
"Executing finish of failed to pause activity: " + r);
finishCurrentActivityLocked(r, FINISH_AFTER_VISIBLE, false);
}
}
}
ActivityStack.completePauseLocked(System_Server進程)
如果Pause完畢之后业崖,則開始處理Stop的流程。
- 如果
prev.finishing
為true的話蓄愁,則會執(zhí)行finishCurrentActivityLocked
開始銷毀 - 接著調(diào)用
resumeFocusedStackTopActivityLocked
開始Resume接下來的Activity
ActivityRecord prev = mPausingActivity;
if (prev != null) {
// 判斷當前是否是STOPPING的狀態(tài)
final boolean wasStopping = prev.state == STOPPING;
// 將當前狀態(tài)該成PAUSED
prev.state = ActivityState.PAUSED;
// 如果是finishing的話双炕,則調(diào)用finishCurrentActivityLocked開始銷毀
if (prev.finishing) {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev);
prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
}
...
// 將PausingActivity置空
mPausingActivity = null;
}
// 開始resume下一個Activity
if (resumeNext) {
final ActivityStack topStack = mStackSupervisor.getFocusedStack();
// 如果當前沒有關(guān)機或者休眠的話
if (!mService.isSleepingOrShuttingDownLocked()) {
// 調(diào)用該函數(shù)開始resumeActivity
mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);
} else {
mStackSupervisor.checkReadyForSleepLocked();
ActivityRecord top = topStack.topRunningActivityLocked();
if (top == null || (prev != null && top != prev)) {
mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
}
}
ActivityStack. resumeTopActivityInnerLocked(System_Server進程)
當Pausing完畢后,會執(zhí)行ActivityStackSupervisor.resumeFocusedStackTopActivityLocked
來開始Resume下一個要顯示的Activity撮抓。最終會執(zhí)行到resumeTopActivityInnerLocked
該函數(shù)妇斤。
- 回調(diào)ActivityResult的結(jié)果,即回調(diào)
onActivityResult
處理返回數(shù)據(jù) - 回調(diào)newIntent丹拯,即回調(diào)
onNewIntent
- 最后開始處理resume流程
// Deliver all pending results.
ArrayList<ResultInfo> a = next.results;
if (a != null) {
final int N = a.size();
if (!next.finishing && N > 0) {
if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
"Delivering results to " + next + ": " + a);
// 如果有ActivityResult需要回調(diào)的話站超,先回調(diào)onActivityResult
next.app.thread.scheduleSendResult(next.appToken, a);
}
}
if (next.newIntents != null) {
// 如果有newIntent的話,先回調(diào)onNewIntent
next.app.thread.scheduleNewIntent(
next.newIntents, next.appToken, false /* andPause */);
}
...
// 開始回調(diào)onResume
next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
mService.isNextTransitionForward(), resumeAnimOptions);
ActivityStack.finishCurrentActivityLocked(System_Server進程)
該函數(shù)主要用來完成銷毀當前Activity乖酬。
// 獲取下一個要Resume的Activity
final ActivityRecord next = mStackSupervisor.topRunningActivityLocked()
if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
&& next != null && !next.nowVisible) {
// 如果要銷毀的Activity目前還是可見的死相,而要Resume的Activity是不可見的
// 那么就先把要銷毀的Activity放到Stopping隊列中,先把要展示的Activity
// 先進行Resume操作咬像,等Resume完后算撮,回頭再銷毀這個Activity
if (!mStackSupervisor.mStoppingActivities.contains(r)) {
addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
}
...
// 將要銷毀的Activity改狀態(tài)為STOPPING
r.state = STOPPING;
...
return r;
}
// 如果mode不是FINISH_AFTER_VISIBLE的話,則從各種隊列中先清除
mStackSupervisor.mStoppingActivities.remove(r);
mStackSupervisor.mGoingToSleepActivities.remove(r);
mStackSupervisor.mActivitiesWaitingForVisibleActivity.remove(r);
if (mResumedActivity == r) {
mResumedActivity = null;
}
// 將狀態(tài)從STOPPING變成FINISHING
final ActivityState prevState = r.state;
r.state = ActivityState.FINISHING;
if (mode == FINISH_IMMEDIATELY
|| (prevState == ActivityState.PAUSED
&& (mode == FINISH_AFTER_PAUSE || mStackId == PINNED_STACK_ID))
|| finishingActivityInNonFocusedStack
|| prevState == STOPPING
|| prevState == STOPPED
|| prevState == ActivityState.INITIALIZING) {
...
// 開始銷毀Activity
boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
if (finishingActivityInNonFocusedStack) {
// 如果Activity不在Focus的Stack中的話施掏,則確保Resume的Activity窗口是可見的
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
}
if (activityRemoved) {
mStackSupervisor.resumeFocusedStackTopActivityLocked();
}
return activityRemoved ? null : r;
}
ActivityThread.handleResumeActivity(用戶進程)
待Activity處于Resume狀態(tài)時钮惠,ActivityThread會調(diào)用該函數(shù)。
- 回調(diào)Activity的
onRestart
七芭、onStart
素挽、onResume
函數(shù) - 處理Activity Window相關(guān)的數(shù)據(jù)保存
- 如果是ActivityManagerService來的請求,則會將
Idler
加到主線程隊列中狸驳,等待主線程空閑時预明,回調(diào)ActivityManagerService.activityIdle
- 回調(diào)
ActivityManagerService. activityResumed
告知已經(jīng)處于Resume狀態(tài)
// 獲取Activity在用戶進程的ActivityClientRecord
ActivityClientRecord r = mActivities.get(token);
...
// 回調(diào)Activity的onResume
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
// 處理Window相關(guān)的事情
...
// 如果是從ActivityManagerService要求的Resume操作的話
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
// 將Idler添加到Looper的隊列中,而在Idler中
// 當主線程隊列空閑的時候會回調(diào)am.activityIdle
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
// 告知ActivityManagerService當前的Activity處于Resume狀態(tài)了
ActivityManager.getService().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
ActivityStackSupervisor.activityIdleInternal(System_Server進程)
當用戶進程處于空閑狀態(tài)時耙箍,就會回調(diào)ActivityManagerService.activityIdle
撰糠。而在該接口中,就會調(diào)用activityIdleInternalLocked
函數(shù)辩昆。
// 根據(jù)token找到已經(jīng)Resume的ActivityRecord
ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r != null) {
// 先移除IDLE_TIMEOUT_MSG的消息
mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
...
}
// 從mStoppingActivities列表中將要stop的Activity選出來
final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,true /* remove */, processPausingActivities);
// 獲取StopActivity的數(shù)量
NS = stops != null ? stops.size() : 0;
// 判斷當前是否有Finishing的Activity
if ((NF = mFinishingActivities.size()) > 0) {
finishes = new ArrayList<>(mFinishingActivities);
mFinishingActivities.clear();
}
...
// 遍歷所有的StopActivity
for (int i = 0; i < NS; i++) {
r = stops.get(i);
final ActivityStack stack = r.getStack();
if (stack != null) {
// 判斷是否要finish阅酪,之前有標記過該位
if (r.finishing) {
// 如果finishing的話,則調(diào)用該函數(shù)立即finish
stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
} else {
// 否則只是調(diào)用onStop
stack.stopActivityLocked(r);
}
}
}
// 銷毀finishing隊列中的Activity
for (int i = 0; i < NF; i++) {
r = finishes.get(i);
final ActivityStack stack = r.getStack();
if (stack != null) {
activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle");
}
}
ActivityStack.destroyActivityLocked(System_Server進程)
// 清理ActivityRecord相關(guān)的隊列與窗口
cleanUpActivityLocked(r, false, false);
// 判斷Activity進程是否存在
final boolean hadApp = r.app != null;
if (hadApp) {
// 如果是finishing的話汁针,則是從Activities中移除
if (removeFromApp) {
// 移除該ActivityRecord
r.app.activities.remove(r);
}
...
boolean skipDestroy = false;
try {
// 回調(diào)用戶進程的scheduleDestroyActivity术辐,開始銷毀Activity
r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
r.configChangeFlags);
} catch (Exception e) {
if (r.finishing) {
removeActivityFromHistoryLocked(r, reason + " exceptionInScheduleDestroy");
removedFromHistory = true;
// 如果銷毀失敗的話,置標識位
skipDestroy = true;
}
}
...
if (r.finishing && !skipDestroy) {
// 如果執(zhí)行了Activity用戶進程的onDestroy的話施无,
r.state = ActivityState.DESTROYING;
// 則需要定時檢測用戶進程將銷毀成功的消息發(fā)送回來
Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
} else {
// 如果出現(xiàn)意外了辉词,則要清理app了
r.state = ActivityState.DESTROYED;
r.app = null;
}
二、Activity的4大啟動模式猾骡,與開發(fā)中需要注意的問題瑞躺,如onNewIntent() 的調(diào)用
這道題想考察什么敷搪?
- 是否了解Activity的啟動模式?
考察的知識點
- Activity的啟動模式
- 不同啟動模式運用場景
- 開發(fā)中的注意事項
考生應(yīng)該如何回答
一. Android啟動模式詳解
1. Standard 標準模式
說明: Android創(chuàng)建Activity時的默認模式幢哨,如果沒有為Activity設(shè)置啟動模式的話赡勘,默認為標準模式。每次啟動一個Activity都會重新創(chuàng)建一個新的實例入棧捞镰,不管這個實例是否存在狮含。
生命周期: 如上所示,每次被創(chuàng)建的實例Activity 的生命周期符合典型情況曼振,它的onCreate几迄、onStart、onResume都會被調(diào)用冰评。
舉例: 此時Activity 棧中以此有A映胁、B、C三個Activity甲雅,此時C處于棧頂解孙,啟動模式為Standard 模式。若在C Activity中添加點擊事件抛人,需要跳轉(zhuǎn)到另一個同類型的C Activity弛姜。結(jié)果是另一個C Activity進入棧中,成為棧頂妖枚。
2. SingleTop 棧頂復(fù)用模式
說明:分兩種處理情況:需要創(chuàng)建的Activity已經(jīng)處于棧頂時廷臼,此時會直接復(fù)用棧頂?shù)腁ctivity,不會再創(chuàng)建新的Activity绝页;若需要創(chuàng)建的Activity不處于棧頂荠商,此時會重新創(chuàng)建一個新的Activity入棧,同Standard模式一樣续誉。
生命周期:若情況一中棧頂?shù)腁ctivity被直接復(fù)用時莱没,它的onCreate、onStart不會被系統(tǒng)調(diào)用酷鸦,因為它并沒有發(fā)生改變饰躲,但是一個新的方法 onNewIntent會被回調(diào)(Activity被正常創(chuàng)建時不會回調(diào)此方法)。
舉例:此時Activity 棧中以此有A臼隔、B嘹裂、C三個Activity,此時C處于棧頂躬翁,啟動模式為SingleTop 模式焦蘑。情況一:在C Activity中添加點擊事件盯拱,需要跳轉(zhuǎn)到另一個同類型的C Activity盒发。結(jié)果是直接復(fù)用棧頂?shù)腃 Activity例嘱。情況二:在C Activity中添加點擊事件,需要跳轉(zhuǎn)到另一個A Activity宁舰。結(jié)果是創(chuàng)建一個新的Activity入棧拼卵,成為棧頂。
[圖片上傳失敗...(image-6e3769-1685949643113)]
3. SingleTask 棧內(nèi)復(fù)用模式
說明:若需要創(chuàng)建的Activity已經(jīng)處于棧中時蛮艰,此時不會創(chuàng)建新的Activity腋腮,而是將存在棧中的Activity上面的其它Activity全部銷毀,使它成為棧頂壤蚜。
生命周期:同SingleTop 模式中的情況一相同即寡,只會重新回調(diào)Activity中的 onNewIntent方法
舉例:此時Activity 棧中以此有A、B袜刷、C三個Activity聪富,此時C處于棧頂,啟動模式為SingleTask 模式著蟹。情況一:在C Activity中添加點擊事件墩蔓,需要跳轉(zhuǎn)到另一個同類型的C Activity。結(jié)果是直接用棧頂?shù)腃 Activity萧豆。情況二:在C Activity中添加點擊事件奸披,需要跳轉(zhuǎn)到另一個A Activity。結(jié)果是將A Activity上面的B涮雷、C全部銷毀阵面,使A Activity成為棧頂。
[圖片上傳失敗...(image-bf892f-1686034647957)]
4. SingleInstance 單實例模式
說明: SingleInstance比較特殊洪鸭,是全局單例模式膜钓,是一種加強的SingleTask模式,它除了具有它所有特性外卿嘲,還加強了一點:具有此模式的Activity只能單獨位于一個任務(wù)棧中颂斜。這個常用于系統(tǒng)中的應(yīng)用,例如Launch拾枣、鎖屏鍵的應(yīng)用等等沃疮,整個系統(tǒng)中只有一個!所以在我們的應(yīng)用中一般不會用到梅肤,了解即可司蔬。
舉例: 比如 A Activity是該模式,啟動A后姨蝴,系統(tǒng)會為它創(chuàng)建一個單獨的任務(wù)棧俊啼,由于棧內(nèi)復(fù)用的特性,后續(xù)的請求均不會創(chuàng)建新的Activity左医,除非這個獨特的任務(wù)棧被系統(tǒng)銷毀授帕。
二.啟動模式的使用方法
1. 在 Manifest.xml中指定Activity啟動模式
一種靜態(tài)注冊同木,在Manifest.xml文件中聲明Activity的同時指定它的啟動模式,這樣在代碼中跳轉(zhuǎn)時會按照指定的模式來創(chuàng)建Activity跛十。例子如下:
<activity android:name="..activity.MultiportActivity" android:launchMode="singleTask"/>
2. 啟動Activity時彤路,在Intent中指定啟動模式去創(chuàng)建Activity
一種動態(tài)的啟動模式,在new 一個Intent后芥映,通過Intent的addFlags方法去動態(tài)指定一個啟動模式洲尊。例子如下:
Intent intent = new Intent();
intent.setClass(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
注意: 以上兩種方式都可以為Activity指定啟動模式,但是二者還是有不一樣的奈偏。
(1)優(yōu)先級:動態(tài)指定方式即第二種比第一種優(yōu)先級要高坞嘀,若兩者同時存在,是以第二種方式為準惊来。 (2)限定范圍:第一種方式無法為Activity直接指定 FLAG_ACTIVITY_CLEAR_TOP 標識姆吭,第二種方式無法為Activity指定 singleInstance 模式。
三. Activity 的 Flags
標記位既可以設(shè)定Activity的啟動模式唁盏,如同上面介紹的内狸,在動態(tài)指定啟動模式,比如 FLAG_ACTIVITY_NEW_TASK 和 FLAG_ACTIVITY_SINGLE_TOP 等厘擂。它還可以影響Activity 的運行狀態(tài) 昆淡,比如 FLAG_ACTIVITY_CLEAN_TOP 和 FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS 等。下面介紹幾個主要的標記位刽严,不要死記昂灵,理解幾個即可,需要時再查官方文檔舞萄。
1. FLAG_ACTIVITY_NEW_TASK
作用是為Activity指定 “SingleTask”啟動模式眨补,跟在AndroidMainfest.xml指定效果相同。
2. FLAG_ACTIVITY_SINGLE_TOP
作用是為Activity指定 “SingleTop”啟動模式倒脓,跟在AndroidMainfest.xml指定效果相同撑螺。
3. FLAG_ACTIVITY_CLEAN_TOP
具有此標記位的Activity,啟動時會將與該Activity在同一任務(wù)棧的其它Activity出棧崎弃,一般與SingleTask啟動模式一起出現(xiàn)甘晤。它會完成SingleTask的作用,但其實SingleTask啟動模式默認具有此標記位的作用
4.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
具有此標記位的Activity不會出現(xiàn)在歷史Activity的列表中饲做,使用場景:當某些情況下我們不希望用戶通過歷史列表回到Activity時线婚,此標記位便體現(xiàn)了它的效果。它等同于在xml中指定Activity的屬性:
android:excludeFromRecents="trure"
四. 啟動模式的實際應(yīng)用場景
這四種模式中的Standard模式是最常見的一種盆均,沒有什么特別注意塞弊,而SingleInstance模式是整個系統(tǒng)的單例模式,在我們的應(yīng)用中一般不會應(yīng)用到,所以游沿,這里就具體講解 SingleTop 和 SingleTask模式的運用場景:
- SingleTask模式的運用場景 最常見的應(yīng)用場景就是保持我們應(yīng)用開啟后只有一個Activity的實例饰抒,最典型的例子就是應(yīng)用中展示的主頁(Home頁)。假設(shè)用戶在主頁跳轉(zhuǎn)到其它頁面奏候,執(zhí)行多次操作后想返回到主頁,如果不使用SingleTask模式唇敞,在點擊返回的過程中會多次看到主頁蔗草,這明顯就是設(shè)計不合理了。
- SingleTop模式的運用場景 如果你在當前的Activity中又要啟動同類型的Activity疆柔,此時建議將此類型Activity的啟動模式指定為SingleTop咒精,可以減少Activity的創(chuàng)建,節(jié)省內(nèi)存旷档!
- 注意:復(fù)用Activity時的生命周期回調(diào) 這里還需要考慮一個Activity跳轉(zhuǎn)時攜帶頁面參數(shù)的問題模叙。
因為當一個Activity設(shè)置了SingleTop或者SingleTask模式后,跳轉(zhuǎn)此Activity出現(xiàn)復(fù)用原有Activity的情況時鞋屈,此Activity的onCreate方法將不會再次執(zhí)行范咨!onCreate方法只會在第一次創(chuàng)建Activity時被執(zhí)行。
而一般onCreate方法中會進行該頁面的數(shù)據(jù)初始化厂庇、UI初始化渠啊,如果頁面的展示數(shù)據(jù)無關(guān)頁面跳轉(zhuǎn)傳遞的參數(shù),則不必擔心此問題权旷,若頁面展示的數(shù)據(jù)就是通過getInten() 方法來得到替蛉,那么問題就會出現(xiàn):getInten()獲取的一直都是舊數(shù)據(jù),根本無法接收跳轉(zhuǎn)時傳送的新數(shù)據(jù)拄氯!下面躲查,通過一個例子來詳解:
Manifest.xml
<activity
android:name=".activity.CourseDetailActivity"
android:launchMode="singleTop"
android:screenOrientation="portrait" />
public class CourseDetailActivity extends BaseActivity{
......
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_course_detail_layout);
initData();
initView();
}
//初始化數(shù)據(jù)
private void initData() {
Intent intent = getIntent();
mCourseID = intent.getStringExtra(COURSE_ID);
}
//初始化UI
private void initView() {
......
}
......
}
以上代碼中的CourseDetailActivity在配置文件中配置了啟動模式是SingleTop模式,根據(jù)上面啟動模式的介紹可得知译柏,當CourseDetailActivity處于棧頂時镣煮,再次跳轉(zhuǎn)頁面到CourseDetailActivity時會直接復(fù)用原有的Activity,而且此頁面需要展示的數(shù)據(jù)是從getIntent()方法得來鄙麦,可是initData()方法不會再次被調(diào)用怎静,此時頁面就無法顯示新的數(shù)據(jù)。
當然這種情況系統(tǒng)早就為我們想過了黔衡,這時我們需要另外一個回調(diào) onNewIntent(Intent intent)方法蚓聘,此方法會傳入最新的intent,這樣我們就可以解決上述問題盟劫。這里建議的方法是重新去setIntent夜牡,然后重新去初始化數(shù)據(jù)和UI,代碼如下所示:
/*
* 復(fù)用Activity時的生命周期回調(diào)
*/
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
initData();
initView();
}
這樣,在一個頁面中可以重復(fù)跳轉(zhuǎn)并顯示不同的內(nèi)容塘装。
三急迂、Intent顯示跳轉(zhuǎn)與隱式跳轉(zhuǎn),如何使用蹦肴?(美團)
這道題想考察什么僚碎?
- 是否了解Intent跳轉(zhuǎn)的真實場景使用,是否熟悉Intent使用場景阴幌?
考察的知識點
- Intent跳轉(zhuǎn)處理在項目中使用與基本知識
考生應(yīng)該如何回答
1.顯示意圖與隱式意圖的區(qū)別勺阐,說說你的簡單理解?
答:
在Activity的跳轉(zhuǎn)中
Activity的跳轉(zhuǎn)需要創(chuàng)建Intent對象矛双,通過設(shè)置intent對象的參數(shù)指定要跳轉(zhuǎn)Activity 通過設(shè)置Activity的包名和類名實現(xiàn)跳轉(zhuǎn)渊抽,稱為顯式意圖 通過指定動作實現(xiàn)跳轉(zhuǎn),稱為隱式意圖
顯式意圖
- 跳轉(zhuǎn)到同一項目下的另一個Activity议忽,直接指定該Activity的字節(jié)碼即可 Intent intent = new Intent(); intent.setClass(this, SecondActivity.class); startActivity(intent);
- 跳轉(zhuǎn)至其他App中的Activity懒闷,需要指定該應(yīng)用的包名和該Activity的類名 Intent intent = new Intent(); //啟動系統(tǒng)自帶的撥號器應(yīng)用 intent.setClassName("com.android.dialer", "com.android.dialer.DialtactsActivity"); startActivity(intent);
隱式意圖
- 隱式意圖跳轉(zhuǎn)至指定Activity Intent intent = new Intent(); //啟動系統(tǒng)自帶的撥號器應(yīng)用 intent.setAction(Intent.ACTION_DIAL); startActivity(intent);
- 要讓一個Activity可以被隱式啟動,需要在清單文件的activity節(jié)點中設(shè)置intent-filter子節(jié)點 <intent-filter > <action android:name="com.itheima.second"/> <data android:scheme="asd" android:mimeType="aa/bb"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter>
- action 指定動作(可以自定義栈幸,可以使用系統(tǒng)自帶的)
- data 指定數(shù)據(jù)(操作什么內(nèi)容)
- category 類別 (默認類別愤估,機頂盒,車載電腦)
- 隱式意圖啟動Activity速址,需要為intent設(shè)置以上三個屬性灵疮,且值必須與該Activity在清單文件中對三個屬性的定義匹配
- intent-filter節(jié)點及其子節(jié)點都可以同時定義多個,隱式啟動時只需與任意一個匹配即可
獲取通過setData傳遞的數(shù)據(jù)
/ /獲取啟動此Activity的intent對象 Intent intent = getIntent(); Uri uri = intent.getData();
顯式意圖和隱式意圖的應(yīng)用場景
- 顯式意圖用于啟動同一應(yīng)用中的Activity
- 隱式意圖用于啟動不同應(yīng)用中的Activity
- 如果系統(tǒng)中存在多個Activity的intent-filter同時與你的intent匹配壳繁,那么系統(tǒng)會顯示一個對話框震捣,列出所有匹配的Activity,由用戶選擇啟動哪一個
2.你在工作中闹炉,留意過在Android中Intent顯示跳轉(zhuǎn)和隱式跳轉(zhuǎn)蒿赢,如何使用?
答:
顯式 Intent 調(diào)用:
// 創(chuàng)建一個顯式的 Intent 對象(方法一:在構(gòu)造函數(shù)中指定)
Intent intent = new Intent(Intent_Demo1.this, Intent_Demo1_Result1.class);
Bundle bundle = new Bundle();
bundle.putString("id", strID);
intent.putExtras(bundle);
intent.putExtra("name", "derry");
intent.putExtra("userInfo", new UserInfo(1, "name"));
startActivity(intent);
// 創(chuàng)建一個顯式的 Intent 對象(方法二:用 setClass 方法)
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("id", strID);
intent.setClass(Intent_Demo1.this, Intent_Demo1_Result1.class);
intent.putExtras(bundle);
startActivity(intent);
// 創(chuàng)建一個顯式的 Intent 對象(方法三:用 setClass 方法)
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("id", strID);
intent.setClassName(Intent_Demo1.this, "com.great.activity_intent.Intent_Demo1_Result1");
intent.putExtras(bundle);
startActivity(intent);
//創(chuàng)建一個顯式的 Intent 對象(方法四:用 setComponent 方法)
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString("id", strID);
//setComponent方法的參數(shù):ComponentName
intent.setComponent(new ComponentName(Intent_Demo1.this,
Intent_Demo1_Result1.class));
intent.putExtras(bundle);
startActivity(intent);
Intent隱式跳轉(zhuǎn) Action:
// 創(chuàng)建一個隱式的 Intent 對象:Action 動作
/**
* 這里指定的是 AndroidManifest.xml 文件中配置的
* <intent-filter>標簽中的<action android:name="com.great.activity_intent
.Intent_Demo1_Result3" />
* 所在的 Activity渣触,注意這里都要設(shè)置 <category android:name="android.intent.
category.DEFAULT" />
*/
Intent intent = new Intent();
// 設(shè)置 Intent 的動作
intent.setAction("com.great.activity_intent.Intent_Demo1_Result3");
Bundle bundle = new Bundle();
bundle.putString("id", strID);
intent.putExtras(bundle);
startActivity(intent);
AndroidManifest.xml:
<activity android:name="Intent_Demo1_Result3"
android:label="Intent_Demo1_Result3">
<intent-filter>
<action android:name="com.great.activity_intent.Intent_Demo1_Result3" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Category 類別:
// 創(chuàng)建一個隱式的 Intent 對象:Category 類別
Intent intent = new Intent();
intent.setAction("com.great.activity_intent.Intent_Demo1_Result33");
/**
* 不指定 Category 或 只指定 AndroidManifest.xml 文件中 <intent-filter>
* 標簽中配置的任意一個 Category
* <category android:name="android.intent.category.DEFAULT" /> 除外羡棵,就可以訪問該 Activity,
*/
intent.addCategory(Intent.CATEGORY_INFO);
intent.addCategory(Intent.CATEGORY_DEFAULT);
Bundle bundle = new Bundle();
bundle.putString("id", strID);
intent.putExtras(bundle);
startActivity(intent);
AndroidManifest.xml:
<activity android:name="Intent_Demo1_Result2"
android:label="Intent_Demo1_Result2">
<intent-filter>
<category android:name="android.intent.category.INFO" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Date 數(shù)據(jù) 跳轉(zhuǎn):
// 創(chuàng)建一個隱式的 Intent 對象嗅钻,方法四:Date 數(shù)據(jù)
Intent intent = new Intent();
Uri uri = Uri.parse("http://www.great.org:8080/folder/subfolder/etc/abc.pdf");
// 注:setData皂冰、setDataAndType、setType 這三種方法只能單獨使用养篓,不可共用
// 要么單獨以 setData 方法設(shè)置 URI
// intent.setData(uri);
// 要么單獨以 setDataAndType 方法設(shè)置 URI 及 mime type
intent.setDataAndType(uri, "text/plain");
// 要么單獨以 setDataAndType 方法設(shè)置 Type
//intent.setType("text/plain");
/**
* 不指定 Category 或 只指定 AndroidManifest.xml 文件中 <intent-filter> 標簽中配置的任意一個 Category
* <category android:name="android.intent.category.DEFAULT" /> 除外秃流,就可以訪問該 Activity
*/
Bundle bundle = new Bundle();
bundle.putString("id", strID);
intent.putExtras(bundle);
startActivity(intent);
AndroidManifest.xml:
<activity android:name="Intent_Demo1_Result2"
android:label="Intent_Demo1_Result2">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
<data
android:scheme="http"
android:host="www.great.org"
android:port="8080"
android:pathPattern=".*pdf"
android:mimeType="text/plain"/>
</intent-filter>
</activity>
四、Activity A跳轉(zhuǎn)B柳弄,B跳轉(zhuǎn)C舶胀,A不能直接跳轉(zhuǎn)到C,A如何傳遞消息給C?(美團)
這道題想考察什么嚣伐?
- 是否了解Activity間信息傳遞糖赔?
考察的知識點
- 消息傳遞的機制
- 事件總線
- 本地數(shù)據(jù)存儲
考生應(yīng)該如何回答
消息通信機制
Android 開發(fā)之中我們常常需要應(yīng)用到消息傳遞,消息的傳遞有多種方式轩端。消息傳遞的作用不必多說放典,主要是在不同的組件之間傳播消息或者共享某些狀態(tài)等,以下是幾種常用的消息傳遞機制:
- 靜態(tài)變量
- 全局變量 及Application
- Android系統(tǒng)剪切板
- 本地化存儲方式
- Android組件
- EventBus
- LiveDataBus
靜態(tài)變量 和 全局變量基茵、Application && Android 系統(tǒng)剪切板
這三種方式其實非常類似奋构,靜態(tài)變量和全局變量都可以采用static的方式來聲明,如果采用這種方式還是推薦用一個專門的類結(jié)合單體模式進行管理耿导,盡量減少對內(nèi)存的消耗声怔。 而使用系統(tǒng)剪切板的方式一般也很少用态贤,比較多限制舱呻,容易丟失數(shù)據(jù),幾乎沒有看到有這樣用的悠汽。
Application: 可以通過在Application 中的全局靜態(tài)變量來實現(xiàn)
這里還有利用Application進行共享Handler來消息傳遞箱吕,方法很簡單,就是在Application中定義一個全局的Handler柿冲,雖然這種方法可以實現(xiàn)茬高,但是卻保留了在整個App中保留了全局的Handler,如果在Handler的設(shè)置中引用了某個Activity假抄,就容易造成內(nèi)存泄露了怎栽。
本地化存儲方式
本地存儲方式有如下三種:
SharedPreference SQLite File
這三種方式的好處就是他們是持久存儲的,只要不卸載APP或者不刪除文件就可以一直保存下去宿饱,而且也幾乎沒有大小的限制熏瞄,可以做一些統(tǒng)計。不過缺點也比較明顯谬以,這三種方式最好是采用多線程來進行讀寫强饮,尤其是數(shù)據(jù)量大的時候,我們知道为黎,IO的操作是非常耗費時間的邮丰,所以盡量不要在UI線程中使用這三種方式讀寫。
示例代碼:
SharedPreference:
// 發(fā)送消息
SharedPreferences.Editor editor = MainActivity.this.getSharedPreferences("SEND", Context.MODE_PRIVATE).edit();
editor.putString("SEND","SharedPreferences的消息");
editor.apply();
startActivity(new Intent(MainActivity.this,ReceiveActivity.class));
// 接收消息
SharedPreferences sharedPreferences = getSharedPreferences("SEND", MODE_PRIVATE);
textView.setText(sharedPreferences.getString("SEND", ""));
附支持的數(shù)據(jù)類型如下:
SQLite
SQLite需要先創(chuàng)建數(shù)據(jù)庫铭乾,后面向數(shù)據(jù)庫中插入和讀取數(shù)據(jù)實現(xiàn)信息共享剪廉。 首先要繼承SQLiteOpenHelper并在onCreate方法中創(chuàng)建數(shù)據(jù)庫:
sqLiteDatabase.execSQL("CREATE TABLE Teacher(teacherId INTEGER PRIMARY KEY" +
" AUTOINCREMENT,userId VARCHAR(20) UNIQUE,name VARCHAR(20),password VARCHAR(20))");
向數(shù)據(jù)庫中插入:
String type = "Teacher";
sqLiteDatabase.execSQL("INSERT INTO " + type + "(userId,name,password) values (?,?,?)",
new String[]{userId, userName, userPassword});
從數(shù)據(jù)庫中讀取信息:
String type = "Teacher";
Cursor cursor = sqLiteDatabase.rawQuery("SELECT * FROM " + type + " WHERE userId = ?",
new String[]{userId.getText().toString()});
if(cursor.moveToFirst()){
String userId = cursor.getString(cursor.getColumnIndex("userId"));
String name = cursor.getString(cursor.getColumnIndex("name"));
}
cursor.close();
File
文件方式要記得申請權(quán)限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
寫入數(shù)據(jù):
try {
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/data/temp.txt");
FileOutputStream out = new FileOutputStream(file);
out.write("message".getBytes(Charset.forName("UTF-8")));
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
讀取數(shù)據(jù):
try {
FileInputStream in = new FileInputStream(Environment.getExternalStorageDirectory().getAbsolutePath() + "/temp.txt");
byte[] reader = new byte[256];
int read = in.read(reader);
String content = "";
if (read > 0)
content = new String(reader, 0, read, Charset.forName("UTF-8"));
Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
Broadcast方式
使用組件也就是說利用Broadcast進行消息傳遞。 優(yōu)選LocalBroadcast:
LocalBroadcastManager.getInstance(context).registerReceiver(@NonNull BroadcastReceiver receiver, @NonNull IntentFilter filter) LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 如果說使用Android 進行消息傳遞的話炕檩,Broadcast是最好的了妈经,顧名思義的我們清楚,廣播就是有一個發(fā)送消息和接受消息的過程,所以可以用于消息傳遞吹泡。 示例:
注冊廣播和接受消息
// 定義廣播
final Button broadButton = findViewById(R.id.broadcast);
final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
broadButton.setText("" + intent.getStringExtra("data"));
}
};
broadButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
registerReceiver(receiver,new IntentFilter("broadsend.action"));
startActivity(new Intent(MainActivity.this,ReceiveActivity.class));
}
});
發(fā)送廣播:
Intent intent = new Intent("broadsend.action");
intent.putExtra("data","send");
sendBroadcast(intent);
這種方式有一些限制骤星,因為接收事件要比發(fā)送事件先定義好,所以只能在當前Activity中注冊廣播爆哑,在跳轉(zhuǎn)的Activity發(fā)送洞难,所以嚴格說這不能算是消息傳遞,因為是單向傳遞的揭朝。
Service方式
Service可以結(jié)合Broadcast進行消息傳遞队贱,不過這樣子就不能算是Service了。 使用Service進行消息傳遞潭袱,我們可以定義接口柱嫌,并利用接口進行消息傳遞。 定義消息接收的接口:
public static MessageCallback messageCallback = new MessageCallback() {
@Override
public void onMessage(String message) {
Log.d("tag","" + message);
}
};
public interface MessageCallback{
public void onMessage(String message);
}
進行消息發(fā)送:
MainActivity.messageCallback.onMessage("message"); 有的人講這種方式不久和共享變量一樣了嗎屯换,不不不编丘,這是完全不一樣的,如果是共享變量的話彤悔,當變量被改變了是不是還得程序員或者用戶去響應(yīng)這種改變呢嘉抓,這就很不好了,而采用這種靜態(tài)接口的方法晕窑,只要函數(shù)被調(diào)用抑片,就立刻可以進行響應(yīng)并處理,不是很方便嗎杨赤。當然也可以想辦法將接口的對象進行傳遞敞斋,例如利用Broadcast來進行傳遞。
EventBus
EventBus 是一款針對Android的發(fā)布以及訂閱事件總線疾牲,使用它可以很方便的進行信息傳遞植捎,而且使用起來很方便。 首先是定義一個消息:
public class Event {
private String message;
public Event(){
message = "EventBus message";
}
public void setMessage(String message){
this.message = message;
}
public String getMessage(){
return message;
}
}
發(fā)送消息:
這里使用了postSticky说敏,這是發(fā)送的粘性廣播鸥跟,使用這個發(fā)送就可以先發(fā)送信息再進行注冊,后注冊的也能接收到前面發(fā)送的廣播盔沫。當然還有其他的使用方式医咨。
EventBus.getDefault().postSticky(new Event());
startActivity(new Intent(MainActivity.this,ReceiveActivity.class));
注冊事件的訂閱者:
EventBus.getDefault().register(this); 接受粘性廣播:
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onEventThread(Event event) {
textView.setText(event.getMessage());
}
LiveDataBus
LiveDataBus是通過包裝LiveData實現(xiàn)的消息總線組件,如果同學(xué)對LiveData不熟悉的同學(xué)架诞,可以找到相對應(yīng)的章節(jié)先學(xué)習一下拟淮。
LiveDataBus 的封裝
- 通過 map 維護一個消息事件和 MutableLiveData 的映射關(guān)系,MutableLiveData 的類型默認為 Object谴忧,接收任意類型很泊,實現(xiàn)總線通信將 LiveDataBus 封裝為一個單例類角虫。
- 消息注冊時,如果當前 map 中不存在委造,則先將消息和對應(yīng)的 MutableLiveData 對象放入維護的 map 中戳鹅,添加映射關(guān)系,返回當前 map 中緩存的 MutableLiveData 對象
LiveDataBus 的組成
- 消息 消息可以是任何的Object昏兆,可以定義不同類型的消息枫虏,如 Boolean、String爬虱。也可以定義自定義類型的消息隶债。
- 消息總線 消息總線通過單例實現(xiàn),不同的消息通道存放在一個 HashMap中跑筝。
- 訂閱 訂閱者通過 with 方式獲取消息通道死讹,然后調(diào)用 observe 訂閱這個通道的消息。
- 發(fā)布 發(fā)布者通過 with 獲取消息通道曲梗,然后調(diào)用 setValue 或者 postValue 發(fā)布消息赞警。
LiveDataBus 的實現(xiàn)
public final class LiveDataBus {
private final Map<String, MutableLiveData<Object>> bus;
private LiveDataBus() {
bus = new HashMap<>();
}
private static class SingletonHolder {
private static final LiveDataBus DATA_BUS = new LiveDataBus();
}
public static LiveDataBus get() {
return SingletonHolder.DATA_BUS;
}
public <T> MutableLiveData<T> with(String target, Class<T> type) {
if (!bus.containsKey(target)) {
bus.put(target, new MutableLiveData<>());
}
return (MutableLiveData<T>) bus.get(target);
}
public MutableLiveData<Object> with(String target) {
return with(target, Object.class);
}
}
注冊訂閱
LiveDataBus.get().with("key_test", Boolean.class)
.observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean aBoolean) {
}
});
發(fā)送消息
LiveDataBus.get().with("key_test").setValue(true);
今天的面試分享到此結(jié)束拉~下期在見