Android深入四大組件(六)Android8.0 根Activity啟動過程(前篇)

2021-03-06 劉望舒
前言

在幾個月前我寫了Android深入四大組件(一)應用程式啟動過程這篇文章,它是基於Android 7.0的,當我開始閱讀Android 8.0源碼時發現應用程式(根Activity)啟動過程照Android 7.0有了一些變化,因此又寫下了本篇文章,本篇文章照此前的文章不僅流程發生變化,而且增加了一些分析,算是升級版本。由於篇幅較長,Android8.0 根Activity啟動過程仍舊分為前篇和後篇來進行講解。

1.概述

Activity的啟動過程分為兩種,一種是根Activity的啟動過程,另一種是普通Activity的啟動過程,根Activity指的是應用程式啟動的第一個Activity,因此根Activity的啟動過程一般情況下也可以理解為應用程式的啟動過程。普通Activity指的是除了應用程式啟動的第一個Activity之外的其他的Activity。這裡介紹的是根Activity的啟動過程,它和普通Activity的啟動過程是有重疊部分的,只不過根Activity的啟動過程一般情況下指的就是應用程式的啟動過程,更具有指導性意義。想要了解普通Activity的啟動過程的的同學可以參考根Activity的啟動過程去自行閱讀源碼。

根Activity的啟動過程比較複雜,因此這裡分為三個部分來講,分別是Launcher請求AMS過程、 AMS到ApplicationThread的調用過程和ActivityThread啟動Activity,本篇文章會介紹前兩個部分。

2.Launcher請求AMS過程

Launcher啟動後會將已安裝應用程式的快捷圖標顯示到桌面上,這些應用程式的快捷圖標就是啟動根Activity的入口,當我們點擊某個應用程式的快捷圖標時就會通過Launcher請求AMS來啟動該應用程式。時序圖如下圖所示。

當我們點擊應用程式的快捷圖標時,就會調用Launcher的startActivitySafely方法,如下所示。
packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

  public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
       ...
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       if (v != null) {
           intent.setSourceBounds(getViewBounds(v));
       }
       try {
           if (Utilities.ATLEAST_MARSHMALLOW
                   && (item instanceof ShortcutInfo)
                   && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
                    || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                   && !((ShortcutInfo) item).isPromise()) {
               startShortcutIntentSafely(intent, optsBundle, item);
           } else if (user == null || user.equals(Process.myUserHandle())) {
               startActivity(intent, optsBundle);
           } else {
               LauncherAppsCompat.getInstance(this).startActivityForProfile(
                       intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
           }
           return true;
       } catch (ActivityNotFoundException|SecurityException e) {
           Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
           Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e);
       }
       return false;
   }

在注釋1處設置Flag為Intent.FLAGACTIVITYNEW_TASK①,這樣根Activity會在新的任務棧中啟動。在注釋2處會調用startActivity方法,這個startActivity方法的實現在Activity中,如下所示。
frameworks/base/core/java/android/app/Activity.java

 @Override
   public void startActivity(Intent intent, @Nullable Bundle options) {
       if (options != null) {
           startActivityForResult(intent, -1, options);
       } else {
           startActivityForResult(intent, -1);
       }
   }

startActivity方法中會調用startActivityForResult方法,它的第二個參數為-1,表示Launcher不需要知道Activity啟動的結果,startActivityForResult方法的代碼如下所示。
frameworks/base/core/java/android/app/Activity.java

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
           @Nullable Bundle options) {
       if (mParent == null) {
           options = transferSpringboardActivityOptions(options);
           Instrumentation.ActivityResult ar =
               mInstrumentation.execStartActivity(
                   this, mMainThread.getApplicationThread(), mToken, this,
                   intent, requestCode, options);
          ...
       } else {
         ...
       }
   }

注釋1處的mParent是Activity類型的,表示當前Activity的父類。因為目前根Activity還沒有創建出來,因此,mParent == null成立。接著調用Instrumentation的execStartActivity方法,Instrumentation主要用來監控應用程式和系統的交互,execStartActivity方法的代碼如下所示。
frameworks/base/core/java/android/app/Instrumentation.java

 public ActivityResult execStartActivity(
           Context who, IBinder contextThread, IBinder token, Activity target,
           Intent intent, int requestCode, Bundle options) {
       ...
       try {
           intent.migrateExtraStreamToClipData();
           intent.prepareToLeaveProcess(who);
           int result = ActivityManager.getService()
               .startActivity(whoThread, who.getBasePackageName(), intent,
                       intent.resolveTypeIfNeeded(who.getContentResolver()),
                       token, target != null ? target.mEmbeddedID : null,
                       requestCode, 0, null, options);
           checkStartActivityResult(result, intent);
       } catch (RemoteException e) {
           throw new RuntimeException("Failure from system", e);
       }
       return null;
   }

首先會調用ActivityManager的getService方法來獲取AMS的代理對象,接著調用它的startActivity方法。這裡與Android 7.0代碼的邏輯有些不同,Android 7.0是通過ActivityManagerNative的getDefault來獲取AMS的代理對象,現在這個邏輯封裝到了ActivityManager中而不是ActivityManagerNative中。首先我們先來查看ActivityManager的getService方法做了什麼:

frameworks/base/core/java/android/app/ActivityManager.java

public static IActivityManager getService() {
       return IActivityManagerSingleton.get();
   }

   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;
               }
           };

getService方法調用了IActivityManagerSingleton的get方法,我們接著往下看,IActivityManagerSingleton 是一個Singleton類。
注釋1處得到名為」activity」的Service引用,也就是IBinder類型的AMS的引用。接著在注釋2處將它轉換成IActivityManager類型的對象,這段代碼採用的是AIDL,IActivityManager.java類是由AIDL工具在編譯時自動生成的,IActivityManager.aidl的文件路徑為:frameworks/base/core/java/android/app/IActivityManager.aidl 。要實現進程間通信,服務端也就是AMS只需要繼承IActivityManager.Stub類並實現相應的方法就可以了。
注意Android 8.0 之前並沒有採用AIDL,而是採用了類似AIDL的形式,用AMS的代理對象ActivityManagerProxy來與AMS進行進程間通信,Android 8.0 去除了ActivityManagerNative的內部類ActivityManagerProxy,代替它的則是IActivityManager,它是AMS在本地的代理。
回到Instrumentation類的execStartActivity方法中,從上面得知execStartActivity方法最終調用的是AMS的startActivity方法。

3.AMS到ApplicationThread的調用過程

Launcher請求AMS後,代碼邏輯已經走到了AMS中,接著是AMS到ApplicationThread的調用流程,時序圖如圖4-2所示。

AMS的startActivity方法如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

 @Override
   public final int startActivity(IApplicationThread caller, String callingPackage,
           Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
           int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
       return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
               resultWho, requestCode, startFlags, profilerInfo, bOptions,
               UserHandle.getCallingUserId());
   }

AMS的startActivity方法中return了startActivityAsUser方法,可以發現startActivityAsUser方法比startActivity方法多了一個參數UserHandle.getCallingUserId(),這個方法會獲得調用者的UserId,AMS會根據這個UserId來確定調用者的權限。
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

   @Override
   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) {
       
       enforceNotIsolatedCaller("startActivity");
       
       userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
               userId, false, ALLOW_FULL_ONLY, "startActivity", null);
       return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
               resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
               profilerInfo, null, null, bOptions, false, userId, null, null,
               "startActivityAsUser");
   }

注釋1處判斷調用者進程是否被隔離,如果被隔離則拋出SecurityException異常,注釋2處用於檢查調用者是否有權限,如果沒有權限也會拋出SecurityException異常。最後調用了ActivityStarter的startActivityLocked方法,startActivityLocked方法的參數要比startActivityAsUser多幾個,需要注意的是倒數第二個參數類型為TaskRecord,代表啟動的Activity所在的棧。最後一個參數"startActivityAsUser"代表啟動的理由。 代碼如下所示。

frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

final int startActivityMayWait(IApplicationThread caller, int callingUid,
           String callingPackage, Intent intent, String resolvedType,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
           IBinder resultTo, String resultWho, int requestCode, int startFlags,
           ProfilerInfo profilerInfo, WaitResult outResult,
           Configuration globalConfig, Bundle bOptions, boolean ignoreTargetSecurity, int userId,
           IActivityContainer iContainer, TaskRecord inTask, String reason) {
        ...
       int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
                   aInfo, rInfo, voiceSession, voiceInteractor,
                   resultTo, resultWho, requestCode, callingPid,
                   callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                   options, ignoreTargetSecurity, componentSpecified, outRecord, container,
                   inTask, reason);
        ...
        return res;
    }
}

ActivityStarter是Android 7.0新加入的類,它是加載Activity的控制類,會收集所有的邏輯來決定如何將Intent和Flags轉換為Activity,並將Activity和Task以及Stack相關聯。ActivityStarter的startActivityMayWait方法調用了startActivityLocked方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

  int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
           String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
           IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
           String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
           ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
           ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
           TaskRecord inTask, String reason) {
       
       if (TextUtils.isEmpty(reason)) {
           throw new IllegalArgumentException("Need to specify a reason.");
       }
       mLastStartReason = reason;
       mLastStartActivityTimeMs = System.currentTimeMillis();
       mLastStartActivityRecord[0] = null;
       mLastStartActivityResult = startActivity(caller, intent, ephemeralIntent, resolvedType,
               aInfo, rInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode,
               callingPid, callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
               options, ignoreTargetSecurity, componentSpecified, mLastStartActivityRecord,
               container, inTask);
       if (outActivity != null) {
           outActivity[0] = mLastStartActivityRecord[0];
       }
       return mLastStartActivityResult;
   }

注釋1處判斷啟動的理由不為空,如果為空則拋出IllegalArgumentException異常。緊接著又調用了startActivity方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

 private int startActivity(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
           String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
           IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
           String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
           ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
           ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
           TaskRecord inTask) {
       int err = ActivityManager.START_SUCCESS;
       final Bundle verificationBundle
               = options != null ? options.popAppVerificationBundle() : null;
       ProcessRecord callerApp = null;
       if (caller != null) {
           
           callerApp = mService.getRecordForAppLocked(caller);
           if (callerApp != null) {
             
               callingPid = callerApp.pid;
               callingUid = callerApp.info.uid;
           } else {
               Slog.w(TAG, "Unable to find app for caller " + caller
                       + " (pid=" + callingPid + ") when starting: "
                       + intent.toString());
               err = ActivityManager.START_PERMISSION_DENIED;
           }
       }
       ...
       
       ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
               callingPackage, intent, resolvedType, aInfo, mService.getGlobalConfiguration(),
               resultRecord, resultWho, requestCode, componentSpecified, voiceSession != null,
               mSupervisor, container, options, sourceRecord);
       if (outActivity != null) {
           outActivity[0] = r;
       }
       ...
           doPendingActivityLaunchesLocked(false);
           return startActivity(r, sourceRecord, voiceSession, voiceInteractor, startFlags, true,
               options, inTask, outActivity);
   }

ActivityStarter的startActivity方法邏輯比較多,這裡列出部分我們需要關心的代碼。注釋1處判斷IApplicationThread類型的caller是否為null,這個caller是方法調用一路傳過來的,指向的是Launche進程的ApplicationThread對象,在注釋2處調用AMS的getRecordForAppLocked方法得到的是代表Launcher進程的callerApp對象,它是ProcessRecord類型的,ProcessRecord用於描述一個應用程式進程。同樣的,ActivityRecord用於描述一個Activity,用來記錄一個Activity的所有信息。在注釋2處創建ActivityRecord,這個ActivityRecord用於描述即將要啟動的Activity,並在注釋3處將創建的ActivityRecord賦值給ActivityRecord[]類型的outActivity,這個outActivity會作為注釋4處的startActivity方法的參數傳遞下去。
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
           int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
           ActivityRecord[] outActivity) {
       int result = START_CANCELED;
       try {
           mService.mWindowManager.deferSurfaceLayout();
           result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                   startFlags, doResume, options, inTask, outActivity);
       }
       ...
       return result;
   }

startActivity方法緊接著調用了startActivityUnchecked方法:
frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java

 private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
           IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
           int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
           ActivityRecord[] outActivity) {
...
if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
               && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
           newTask = true;
           
           result = setTaskFromReuseOrCreateNewTask(
                   taskToAffiliate, preferredLaunchStackId, topStack);
       } else if (mSourceRecord != null) {
           result = setTaskFromSourceRecord();
       } else if (mInTask != null) {
           result = setTaskFromInTask();
       } else {
           setTaskToCurrentTopOrCreateNewTask();
       }
      ...
if (mDoResume) {
           final ActivityRecord topTaskActivity =
                   mStartActivity.getTask().topRunningActivityLocked();
           if (!mTargetStack.isFocusable()
                   || (topTaskActivity != null && topTaskActivity.mTaskOverlay
                   && mStartActivity != topTaskActivity)) {
              ...
           } else {
               if (mTargetStack.isFocusable() && !mSupervisor.isFocusedStack(mTargetStack)) {
                   mTargetStack.moveToFront("startActivityUnchecked");
               }
               mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                       mOptions);
           }
       } else {
           mTargetStack.addRecentActivityLocked(mStartActivity);
       }
       ...

}

startActivityUnchecked方法主要處理棧管理相關的邏輯。在標註①處我們得知,啟動根Activity時會將Intent的Flag設置為FLAGACTIVITYNEW_TASK,這樣注釋1處的條件判斷就會滿足,接著執行注釋2處的setTaskFromReuseOrCreateNewTask方法,其內部會創建一個新的TaskRecord,TaskRecord用來描述一個Activity任務棧,也就是說setTaskFromReuseOrCreateNewTask方法內部會創建一個新的Activity任務棧。Activity任務棧其實是一個假想的模型,並不真實的存在,關於Activity任務棧可以閱讀Android解析ActivityManagerService(二)ActivityTask和Activity棧管理這篇文章。在注釋3處會調用ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

boolean resumeFocusedStackTopActivityLocked(
       ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
   if (targetStack != null && isFocusedStack(targetStack)) {
       return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);
   }
   
   final ActivityRecord r = mFocusedStack.topRunningActivityLocked();
   if (r == null || r.state != RESUMED) {
       mFocusedStack.resumeTopActivityUncheckedLocked(null, null);
   } else if (r.state == RESUMED) {
       mFocusedStack.executeAppTransition(targetOptions);
   }
   return false;
}

注釋1處調用ActivityStack的topRunningActivityLocked方法獲取要啟動的Activity所在棧的棧頂的不是處於停止狀態的ActivityRecord。注釋2處如果ActivityRecord不為null,或者要啟動的Activity的狀態不是RESUMED狀態,就會調用注釋3處的ActivityStack的resumeTopActivityUncheckedLocked方法,對於即將要啟動的Activity,注釋2的條件判斷是肯定滿足,因此我們來查看ActivityStack的resumeTopActivityUncheckedLocked方法,如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

 boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
       if (mStackSupervisor.inResumeTopActivity) {
           return false;
       }
       boolean result = false;
       try {
           mStackSupervisor.inResumeTopActivity = true;
           result = resumeTopActivityInnerLocked(prev, options);
       } finally {
           mStackSupervisor.inResumeTopActivity = false;
       }
       mStackSupervisor.checkReadyForSleepLocked();
       return result;
   }

緊接著查看注釋1處ActivityStack的resumeTopActivityInnerLocked方法:
frameworks/base/services/core/java/com/android/server/am/ActivityStack.java

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
     ...
          mStackSupervisor.startSpecificActivityLocked(next, true, true);
      }
       if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
      return true;
}

resumeTopActivityInnerLocked方法代碼非常多,我們只需要關注調用了ActivityStackSupervisor的startSpecificActivityLocked方法,代碼如下所示。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

void startSpecificActivityLocked(ActivityRecord r,
           boolean andResume, boolean checkConfig) {
       
       ProcessRecord app = mService.getProcessRecordLocked(r.processName,
               r.info.applicationInfo.uid, true);
       r.getStack().setLaunchTime(r);

       if (app != null && app.thread != null) {
           try {
               if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
                       || !"android".equals(r.info.packageName)) {
                   app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
                           mService.mProcessStats);
               }
               realStartActivityLocked(r, app, andResume, checkConfig);
               return;
           } catch (RemoteException e) {
               Slog.w(TAG, "Exception when starting activity "
                       + r.intent.getComponent().flattenToShortString(), e);
           }
       }
       mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
               "activity", r.intent.getComponent(), false, false, true);
   }

注釋1處獲取即將要啟動的Activity的所在的應用程式進程,注釋2處判斷要啟動的Activity的所在應用程式進程已經運行的話,就會調用注釋3處的realStartActivityLocked方法,需要注意的是,這個方法的第二個參數是代表要啟動的Activity的所在的應用程式進程的ProcessRecord。
frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
         boolean andResume, boolean checkConfig) throws RemoteException {
  ...
         app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                 System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                 new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
                 task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                 newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
 ...      
     return true;
 }

這裡的 app.thread指的是IApplicationThread,它的實現是ActivityThread的內部類ApplicationThread,其中ApplicationThread繼承了IApplicationThread.Stub。app指的是傳入的要啟動的Activity的所在的應用程式進程,因此,注釋1處的代碼指的就是要在目標應用程式進程啟動Activity。當前代碼邏輯運行在AMS所在的進程(SyetemServer進程),通過ApplicationThread來與應用程式進程進行Binder通信,換句話說,ApplicationThread是AMS所在進程(SyetemServer進程)和應用程式進程的通信橋梁,如下圖所示。

結語

本文我們學習了根Activity的啟動過程的前兩個部分,分別是Launcher請求AMS過程、 AMS到ApplicationThread的調用過程,完成第二個部分後代碼邏輯就運行在了應用程式進程中,後篇會接著介紹ActivityThread啟動Activity的過程以及根Activity啟動過程中涉及的進程。

相關焦點

  • Android深入四大組件(二)Service的啟動過程
    建議閱讀此篇文章前,請先閱讀Android深入四大組件(一)應用程式啟動過程這篇文章。1.ContextImpl到ActivityManageService的調用過程要啟動Service,我們會調用startService方法,它的實現在ContextWrapper中,代碼如下所示。
  • Android學習(四) — 組件(一)
    xml version="1.0" encoding="utf-8"?xml version="1.0" encoding="utf-8"?xml version="1.0" encoding="utf-8"?
  • Android插件化系列三:技術流派和四大組件支持
    AMS自不用說,四大組件各種操作都需要跟它打交道,PMS也十分重要,完成了諸如權限校驗(checkPermission,checkUidPermission),Apk meta信息獲取(getApplicationInfo等),四大組件信息獲取(query系列方法)等重要功能。
  • 深入講解Android中Activity launchMode
    Android系統中的Activity可以說一件很贊的設計,它在內存管理上良好的設計,使得多任務管理在Android系統中運行遊刃有餘。而ComposeMailActivity則是用來撰寫郵件,可以實例化多個此Activity對象。合理地設計Activity對象是否使用已有的實例還是多次創建,會使得互動設計更加良好,也能避免很多問題。至於想要達到前面的目標,就需要使用今天的Activity啟動模式。如
  • 聊聊Android應用Preference組件那點事
    (1)常用的組件PreferenceCategory :類似於LinearLayout、RelativeLayout,用於組合一組Preference,使布局更具備層次感 。PreferenceScreen  : 所有Preference元素的根節點。
  • Android四大組件知識點總結
    IntentIntent是信使,負責完成Android四大組件之間的信息傳遞,同類、不同類的組件無法直接傳遞對象,一旦需要溝通只能通過Intent(不建議通過靜態變量或靜態方法傳遞數據,容易造成數據異常、內存洩露等問題)。2.
  • Android架構之App組件化方案詳細實踐與總結
    解決思路就是:每個組件維護兩張表,一張用於組件單獨開發時使用,另一張用於合併到主工程的註冊表中,每當增加一個Android系統的四大組件時都要同時給兩張表中添加。文件夾中的註冊表指定QueryActivity為MAIN Activity,也就是要啟動的 Activity,而release文件夾中的則沒有;3)第三步:解決組件和主工程的Application衝突問題以及組件單獨開發初始化(共享)數據問題當android程序啟動時,android系統會為每個程序創建一個Application類的對象,並且只創建一個
  • Android中的窗口——Activity
    現在來準備3個Activity(Activity1、Activity2和Activity3),這3個Activity使用的布局文件分別為activity1.xml、activity2.xml和activity3.xml,代碼如下:activity1.xml &
  • 我以為我理解Android的四大啟動模式,直到被打臉
    讓我們看一個dumpsys的圖:使用命令:adb shell dumpsys activity使用命令:adb shell dumpsys activity activities | sed -En -e '/Stack #/p' -e '/Running activities/,/Run #0/p'這兩幅圖是同一個Activity棧的場景,但是可以看出來TaskRecord
  • Activity 使用詳解
    六、Activity結束方法七、Activity狀態保存,恢復的方法八、面試中經常問到題型一、Activity 概覽Activity是Android最基本的四大組件之一(Activity 活動,Service 服務,ContentProvider
  • android是什麼意思 Android組件和布局介紹
    四大組件介紹①activity---活動:它是一種可以包含用戶界面的組件,主要用於和用戶進行交互。通常一個Activity是一個單獨的窗口,彼此通過Intent進行通信,並且需要在AndroidManifest.xml中配置,不然系統無法識別。
  • Activity 使用方法詳解
    六、Activity結束方法七、Activity狀態保存,恢復的方法八、面試中經常問到題型一、Activity 概覽Activity是Android最基本的四大組件之一(Activity 活動,Service 服務,ContentProvider內容提供者,BroadcastReceiver 廣播),Activity主要負責與用戶進行交互,是每位Android開發必須掌握的知識點
  • Android解析WindowManager(三)Window的添加過程
    ViewRootImpl身負了很多職責:View樹的根並管理View樹觸發View的測量、布局和繪製輸入事件的中轉站管理Surface負責與WMS進行進程間通信frameworks/base/core/java/android/view/ViewRootImpl.java
  • android啟動頁設計專題及常見問題 - CSDN
    目前啟動頁的廣告都有倒計時的功能,那麼我們在倒計時的過程中能做些什麼呢?這篇文章主要包括以下兩方面內容 集成騰訊廣告聯盟的SDK 啟動頁加載過程中,後臺初始化數據 我們在設計啟動頁時的常規做法是建立一個Activity來加載開屏圖片或者廣告,作為程序的入口,那麼在這個三到五秒時間內如果進行數據下載,當用戶點擊了跳過按鈕或者計時結束了數據還沒初始化完成,已經進入了主頁面,而主界面剛好需要那些基礎數據該如何?
  • Android系統啟動流程(三)解析SyetemServer進程啟動過程
    前言上一篇我們學習了Zygote進程,並且知道Zygote進程啟動了SyetemServer進程,那麼這一篇我們就來學習Android7.0版本的
  • android關閉開機啟動 - CSDN
    = null && a.activity.mFinished)); if (a.activity != null && !方法,會報告啟動時間結束,還有就是檢查是否是還在開機階段然後結束開機流程。
  • android 從後臺啟動頁面專題及常見問題 - CSDN
    這三個Tesseract語言包合起來約有70M左右,APK文件中拷貝語言包到手機存儲中需要幾秒時間,所以我們做了一個啟動頁面,在為用戶展示App第一印象的同時,後臺拷貝這三個語言包。經過比較,知乎日報的啟動頁面有從中心點展開逼進用戶的效果,我們決定利用此效果來設計啟動頁面。最終效果如圖所示:
  • InjuredAndroid 1-5
    Permission: null  b3nac.injuredandroid.QXV0aA    Permission: null  b3nac.injuredandroid.DeepLinkActivity    Permission: null  b3nac.injuredandroid.MainActivity    Permission
  • Android自定義View入門及實戰案例分析
    xml version="1.0" encoding="utf-8"?xml version="1.0" encoding="utf-8"? 3.把myView加入到activity_main.xml布局裡面 <?xml version="1.0" encoding="utf-8"?
  • Android MotionLayout動畫:續寫ConstraintLayout新篇章
    :constraint-layout:2.0.0-beta8'學習MotionLayout動畫可能需要點Transition和ConstraintLayout知識點,不了解可以看看文末連結哦。xml version="1.0" encoding="utf-8"?