二:Activity啟動
1. 入口(Activity)
android/platform/frameworks/base/master/./core/java/android/app/Activity.java
簡述:啟動一個活動,在
Activity$startActivity
--->Activity$startActivityForResult
--->Instrumentation$execStartActivity
. 得到一個ActivityResult
。
我們經常調用startActivity(intent)
來開啟一個活動。(註:為保證流程的連貫性,擴展可以略過)
1.1 startActivity
startActivity: 啟動一個新的活動。當活動退出,你不會收到任何信息。該實現重寫了基類(
Context$startActivity
)版本,提供了啟動活動相關的信息。由於這些增加的信息,不再需要FLAG_ACTIVITY_NEW_TASK
啟動標記了。如果未指定,新的活動被添加到調用者的任務棧中。
@Override
public void startActivity(Intent intent, @Nullable Bundle options){
if(options != null){
startActivityForResult(intent, -1, options);
}else{
startActivityForResult(intent, -1);
}
}
1.1.1 擴展 :ActivityOptions
先來看一個方法:
public void overridePendingTransition(int enterAnim, int exitAnim){
try {
ActivityManager.getService().overridePendingTransition(mToken, enterAnim, exitAnim);
}catch (RemoteException e){
}
}
應用開發中,上述方法用來設置Activity的轉場動畫。如果沒有指定,系統默認如下:
ActivityOptions$makeCustomAnimation
public static makeCustomAnimation(Context context,
int enterResId, int exitId, Handler handler, OnAnimationStartedListener listener) {
ActivityOptions opts = new ActivityOptions();
opts.mPackageName = context.getPackageName();
opts.mAnimationType = ANIM_CUSTOM;
opts.mCustomEnterResId = enterResId;
opts.mCustomExitResId = exitResId;
opts.setOnAnimationStartedListener(listener);
return opts;
}
在材料設計中,還可看到更多漂亮的轉場動畫,比如聯繫人列表進入詳情的頭像伸縮變換轉場動畫。點擊一個條目,頭像會擴大為大圖到詳情的活動頁面,從詳情返回列表,大圖又縮小為小圖。系統實現如下:
private static ActivityOptions makeThumbnailAnimation(View source, Bitmap thumbnail,
int startX, int startY, OnAnimationStartedListener listener, boolean scaleUp) {
ActivityOptions opts = new ActivityOptions();
opts.mPackageName = source.getContext().getPackageName();
opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
opts.mThumbnail = thumbnail;
int[] pts = new int[2];
source.getLocationOnScreen(pts);
opts.mStartX = pts[0] + startX;
opts.mStartY = pts[1] + startY;
opts.setOnAnimationStartedListener(source.getHandler(), listener);
return opts;
}
注意,這是private
方法,配合ActivityOptionsCompat
使用。
官方教程
1.2 startActivityForResult
pbulic void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options){
// app冷啟動的首個Activity,mParent為null
if(mParent == null){
// options 為null的時候,使用系統默認,如1.1.1所述
options = transferSpringboardActivityOptions(options);
// * 關鍵方法,這裡返回一個ActivityResult(描述了活動執行的結果,並返回給原始活動)
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this,mMainThread.getApplicationThread(),mToken,this,
intent,requestCode,options);
if(ar != null){
// mMainThread 是ActivityThread的一個實例,在ActivityThread中調用performLaunchActivity時候,反射生成Activity的實例,然後調用Activity$attach方法,把MMainThread實例傳入Activity
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
}
// 請求碼不小於0的話,在活動退出時候,會被返回給`onActivityResult()`方法
if(requestCode >= 0){
// 如果這次啟動正在請求一個響應結果,系統可以阻止活動可見,直到收到結果。
// 在`onCreate`, `onResume`方法中`startActivityForResult` 設置這個`requestCode`,會保證活動不可見並且避免頁面閃爍。
// 只有在請求到結果時才能完成這些,因為,這樣保證了無論發生什麼都可以在活動結束後獲取到信息。
mStartedActivity = true;
}
// 取消輸入事件,開始轉場動畫
cancelInputsAndStartExitTransition(options);
}else{
if(options != null){
mParent.startActivityFromChild(this, intent, requestCode, options);
}else{
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
1.2.1 擴展:mMainThread的附著流程
mMainThread是主線程的實例,在一個新的活動實例創建後附著。
系統在ActivityThread$PerformLaunchActivity
中,使用Instrumentation$newActivity
反射生成Activity
實例,然後調用Activity$attach
把當前線程實例傳給生成的最新Activity
。
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null ? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
Activity$newActivity
---> AppcomponentFactory$instantiateActivity
如下:
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, @NonNull Intent intent) throws InstaniationException, IllegalAccessException, ClassNotFoundExcption {
return (Activity)cl.loadClass(cl).newInstance();
}
instantiateActivity()
允許應用程序重寫活動的創建。可用來生成依賴注入或類載入器修改這些類。
1.2.2 擴展:在DecorView上cancelInput
取消輸入,指的是取消DecorView
上的點擊和長按事件。
a) Activity
// Activity$cancelPendingInputsAndStartExitTransition
private void cancelPendingInputsAndStartExitTransition(Bundle options) {
// I: mWidow.peekDecorView() 通過mWindow(PhoneWindow的實例)獲取View的一個實例DecorView
final View decor = mWindow != null ? mWindow.peekDecorView() : null;
if (decor != null){
decor.cancelPendingInputEvents();
}
if (options != null && !isTopOfTask()) {
mActivityTransitionState.startExitOutTransition(this, options);
}
}
cancelPendingInputsAndStartExitTransition()
方法中出現了比較重要的角色:mWindow
.
在attach()
方法中有:mWindow = new PhoneWindow(this, window, activityConfigCallback);
attach()
又是在哪裡調用的?在ActivityThread$performLaunchActivity()
.
在Activity$onCreate()
中,我們經常setContentView()
,具體的實現就是在PhoneWindow$setContentView
中
PhoneWindow
會在setContentView
的時候檢測DecorView
的實例是否存在,如果否,則使用installDecor
創建。
b) View(DecorView)
// View$onCancelPendingInputEvents
public void onCancelPendingInputEvents() {
removePerformClickCallback();
cancelLongPress();
mPrivateFlags3 |= PFLAG3_CALLED_SUPER;
}
DecorView
是FrameLayout
的子類。
DecorView
初始化調用過程:
Activity$setContentView
或Activity$addContentView
PhoneWindow$installDecor
--->PhoneWindow$generateDecor
--->PhoneWindow$generateLayout()
--->DecorView$onResourcesLoaded
2.啟動前監控(Instrumentation)
android/platform/frameworks/base/master/./core/java/android/app/Instrumentation.java
從Activity
到Instrumentation
。
2.1 execStartActivity
/**
*
* @param who 開啟當前活動的上下文
* @param contextThread 開啟當前活動的上下文的主線程
* @param token 開啟當前活動的內部令牌,由系統識別,可以為空
* @param target 開啟從而獲取`ActivityResult`的活動,如果當前不是從活動調用的該方法,可以為空
* @param intent 開啟活動的具體意圖
* @param requestCode 此次請求結果的識別碼,小於0說明調用者不期望請求結果
* @param option 附加選項,見1.1.1
*
* @return 返回包含想要的數據的`ActivityResult`,默認返回空
*
*/
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
// 來源,應用當前展示內容的來源。默認返回`null`,即來源為:當前活動的包名。如果不為空,配合`Intent.EXTRA_REFERRER`使用
Uri referrer = target != null ? target.onProvideReferrer() : null;
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
// ActivityMonitor 配合Instrumentation測試框架使用
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i = 0; i < N; i++){
final ActivityMonitor am = mActivityMonitor.get(i);
ActivityResult result = null;
if (am.ignoreMatchingSpecificIntents()) {
result = am.onStartActivity(intent);
}
if (result != null) {
am.mHits++;
return result;
} else if (am.mathch(who, null, intent)) {
am.mHits++;
if (am.isBlocking()) {
return requestCode >= 0 ? am.getResult : null;
}
break;
}
}
}
}
try {
// 當`ACTION_SEND`,`ACTION_SEND_MULTIPLE`,`ACTION_CHOOSER`時,遷移`EXTRA_STREAM`到`ClipData`。ClipData作用是在剪貼板展示剪切的數據
intent.migrateExtraStreamToClipData();
// 準備打開文件選擇器或者剪貼板,這是7.0之後的FileProvide功能。比如啟用打開相冊的意圖時。
intent.papreToLeaveProcess(who);
// 進入ActivityManagerService進行下一步工作
int result = ActivityManager.getService()
.startActivityAsUser(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho, requestCode, 0, null, options, user.getIndentifier());
// 檢查ActivityResult的返回代碼和ActivityManager定義的那些錯誤常量是否匹配,並拋出響應的異常
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
2.1.1 擴展:android.util.Singleton
在execStartActivity()
,看到ActivityManager.getService()
,獲取ActivityManagerService
的單例實例。代碼:
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this){
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
3. 初始化ActivityStarter(ActivityManagerService)
從Instrumentation$execStartActivity
到ActivityManagerService$startActivity
3.1startActivityAsUser
:
/**
*
* @param caller
* @param callingPackage
* @param intent
* @param resolveType
* @param resultTo
* @param resultWho
* @param requestCode
* @param startFlags
* @param profilerInfo
* @param bOptions
* @param userId
* @param validateIncomingUser
*
*/
public final int startActivityAsUser (IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
// 執行非隔離調用。
enforceNotIsolatedCaller("startActivity");
userId = mActivityStartController.checkTargetUser(userId, validateIncomingUser, Binder.getCallingPid(), Binder.getCallingUid, "startActivityAsUser");
return mActivityStartController.obtainStarter(intent, "startActivityAsUser")
// 設置ApplicationThread
.setCaller(caller)
.setCallingPackage(callingPackage)
.setResolvedType(resolvedType)
.setResultTo(resultTo)
.setResultWho(resultWho)
.setRequestCode(requestCode)
// 這裡是0,默認是0
.setStartFlags(startFlags)
// 探查app時的設置,此時為空,默認為空
.setProfilerInfo(profilerInfo)
// 見1.1.1
.setActivityOptions(bOptions)
// 應該等待開啟活動的請求的結果
.setMayWait(userId)
.execute();
}
3.1.1 擴展:應用沙盒
Android
平台利用基於用戶的Linux
保護機制來識別和隔離應用資源。Android``為每個應用分配獨一無二的用戶ID(UID)
,並在各自的進程中運行。可將應用分開,保護應用和系統免受惡意應用的攻擊。
Android
利用UID
設置一個內核級應用沙盒。默認情況下,應用不能彼此交互,而且對操作系統的訪問許可權會受到限制。
由於應用沙盒位於內核層面,因此該安全模型擴展到了原生代碼和系統應用。
3.1.2 擴展:UserHandler
在設備上表示用戶。3.1.1
說了,應用默認是隔離的,可以使用shareUid
,來共享資源。UserHandle
主要涉及android
中讓人眼花的各種id
管理:
- UserId:android設備上的多用戶的用戶id
- UID:跟應用進程相關,一旦安裝到設備就不會改變,每個用戶的每個應用的uid都不一樣
- APPID:跟包名有關,包名相同appid不同。即使用戶不同,同一個應用appid是一樣的
public final class UserHandle implements Parcelable {
/**
* @hide Range of uids allocated for a user.
* 每個用戶可以有十萬個uid
*/
public static final int PER_USER_RANGE = 100000;
// 根據Userid 生成uid。加入A用戶userId = 0; B為1.appId為10080
public static int getUid(@UserIdInt int userId, @AppIdInt int appId) {
if (MU_ENABLE) {
// A --> 10080
// B --> 100000 + 10080
return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
} else {
return appId;
}
}
// 傳入uid獲取userid。例如A用戶的UID是10080,B用戶的UID就是100000 + 10080。
// 每個用戶有十萬個uid。即兩個用戶直接相差100000.
public static @UserIdInt int getUserId(int uid) {
if(MU_ENABLE) {
// A / PER_USER_RANGE = 10080 / 100000 = 0
// B / PER_USER_RANGE = (100000 + 10080) / 100000 = 1
return uid / PER_USER_RANGE;
} else {
return UserHandle.USER_SYSTEM;
}
}
// 例如A用戶的UID是10080,B用戶的UID就是100000 + 10080
public static @AppIdInt int getAppId(int uid) {
// A % PER_USER_RANGE = 10080 % 100000 = 10080
// B % PER_USER_RANGE = (100000 + 10080) % 100000 = 10080
// 因此說包名相同,appid必定相同,跟用戶無關。
return uid % PER_USER_RANGE;
}
}
4.啟動模式&任務棧ActivityStarter
ActivityStarter$execu
--> startActivityMayWait
--> startActivty
--> startActivityUnchecked
概述:計算啟動模式的標記;創建或復用任務棧
4.1 startActivityUnchecked
這個方法接近220行,我們看下重點:
/**
* @parmas 這些參數在前面的調用過程中大都有說明
* @return 返回ActivityManager定義的err code,如果START_SUCCESS = 0
*/
private int startActivityUnchecked(...) {
// 初始化一些狀態
setInitialState(...);
// 計算啟動模式
computeLaunchingTaskFlags();
// 計算源stack,如果源正在結束,和它關聯的task也可能為空,就需要用NEW_TASK再建新的task
compupteSourceStack();
// 決定這個新活動是否應該插入已存在的任務棧中。不應該的話,返回空。應該的話返回這個任務棧相關的新活動的ActivityRecord。
ActivityRecord reusedActivity = getReusableIntentActivity();
// ......
// 有可復用的Activity
if (resuedActivity != null) {
// 根據啟動模式,復用或清除活動
}
// 開啟活動的活動不存在了,重點啟動
if (mStartActivity.packageName == null) {
// ......
return START_CLASS_NOT_FOUND;
}
// 如果要啟動的活動和當前棧頂的一樣,根據啟動模式檢測是否需要啟動。
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.realActivity.equals(mStartActivity.realActivity)
&& top.userId == mStartActivity.userId
&& top.app != null && top.app.thread != null
&& ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
|| isLaunchModeOneOf(LAUNCH_SINGLE_TOP, LAUNCH_SINGLE_TASK));
if (dontStart) {
// ......
//不需要重新啟動,傳一個new intent 過去
deliverNewIntent(top);
}
// Should this be considered a new task?
int result = START_SUCCESS;
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
&& (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
// 新的任務棧
result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
} else if (mSourceRecord != null) {
// 使用源活動的任務棧
result = setTaskFromSourceRecord();
} else if (mInTask != null) {
// 使用目標活動的任務棧
result = setTaskFromInTask();
} else {
// This not being started from an existing activity, and not part of a new task...
// just put it in the top task, though these days this case should never happen.
// 這不是被存在的活動啟動的,也不是新任務棧的一部分,把活動放到棧頂吧,儘管這永遠不應該發生的
setTaskToCurrentTopOrCreateNewTask();
}
if (result != START_SUCCESS) {
return result;
}
// ActivityStack$startActivityLocked 創建預覽窗口
mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
mOptions);
if (mDoResume) {
final ActivityRecord topTaskActivity =
mStartActivity.getTask().topRunningActivityLocked();
if (!mTargetStack.isFocusable()
|| (topTaskActivity != null && topTaskActivity.mTaskOverlay
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we cant resume it, but still would like to
// make sure it becomes visible as it starts (this will also trigger entry
// animation). An example of this are PIP activities.
// Also, we dont want to resume activities in a task that currently has an overlay
// as the starting activity just needs to be in the visible paused state until the
// over is removed.
// 如果活動不可聚焦就不能恢復焦點了。這仍然可以保證活動可見。比如畫中畫的活動。同時,不要恢復焦點的活動上現在有覆蓋層,保持活動可見和暫停狀態,知道覆蓋層移除,比如非全屏的提示框。
mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
// Go ahead and tell window manager to execute app transition for this activity
// since the app transition will not be triggered through the resume channel.
// 直接通知窗口管理器執行活動間的動畫,因為通過恢復焦點通道去通知執行轉場動畫已經不會被觸發了。
mService.mWindowManager.executeAppTransition();
} else {
// If the target stack was not previously focusable (previous top running activity
// on that stack was not visible) then any prior calls to move the stack to the
// will not update the focused stack. If starting the new activity now allows the
// task stack to be focusable, then ensure that we now update the focused stack
// accordingly.
// 如果目標活動棧不是上一個獲取焦點的(棧中前一個棧頂正在運行的活動不可見),前一個移動活動棧的調用不會更新這個已經獲取焦點的活動棧。
if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) {
mTargetStack.moveToFront("startActivityUnchecked");
}
mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
mOptions);
}
} else if (mStartActivity != null) {
mSupervisor.mRecentTasks.add(mStartActivity.getTask());
}
return START_SUCCESS;
}
4.1.1 擴展:ActivityStack、TaskRecord、ActivityRecord、ActivityStackSupervisor關係
※十幾塊能買到哪些好用的包月服務?
※2017 我所分享的技術文章總結(下)
※請問(2018.8),買iphone6s和同價位的安卓哪個更好一些?
※從CarPlay到YunOS,主流車載系統有哪些?
※Google Flutter From Scratch:使用小部件構建應用程序
TAG:Android |