Android9.0 Launcher启动Activity详解(三)

一.应用进程绑定到AMS

前文我们知道Zygote进程执行了ActivityThread的main方法,我们看下main方法的实现

 public static void main(String[] args) {
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        Looper.loop();
    }

这里启动了UI线程中的Looper,这也是为什么UI线程中可直接使用Handler的原因,不过这里主要关注的是attach方法,看其实现

 private void attach(boolean system, long startSeq) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            //非系统应用
            final IActivityManager mgr = ActivityManager.getService();
            try {
                //AMS绑定Application
                mgr.attachApplication(mAppThread, startSeq);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            //开启垃圾回收机制
            BinderInternal.addGcWatcher(new Runnable() {
                @Override public void run() {
                    if (!mSomeActivitiesChanged) {
                        return;
                    }
                    Runtime runtime = Runtime.getRuntime();
                    long dalvikMax = runtime.maxMemory();
                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                    if (dalvikUsed > ((3*dalvikMax)/4)) {
                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                                + " total=" + (runtime.totalMemory()/1024)
                                + " used=" + (dalvikUsed/1024));
                        mSomeActivitiesChanged = false;
                        try {
                            ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                        } catch (RemoteException e) {
                            throw e.rethrowFromSystemServer();
                        }
                    }
                }
            });
        } else {
            //系统应用直接通过包信息去绑定Applciation,调用onCreate方法
            android.ddm.DdmHandleAppName.setAppName("system_process",
                    UserHandle.myUserId());
            try {
                mInstrumentation = new Instrumentation();
                mInstrumentation.basicInit(this);
                ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
                mInitialApplication.onCreate();
            } catch (Exception e) {
                throw new RuntimeException(
                        "Unable to instantiate Application():" + e.toString(), e);
            }
        }

这里看到系统应用和非系统应用绑定Application的实现方式是不一样的,这里只讨论非系统应用的绑定实现, mgr.attachApplication(mAppThread, startSeq)为非系统应用的绑定实现,mgr为代理类,其真正实现就是ActivityManagerService,看其实现

 @Override
    public final void attachApplication(IApplicationThread thread, long startSeq) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);
            Binder.restoreCallingIdentity(origId);
        }
    }
 private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
                                            int pid, int callingUid, long startSeq) {
        ...
        //AMS绑定ApplicationThread
        thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
                null, null, null, testMode,
                mBinderTransactionTrackingEnabled, enableTrackAllocation,
                isRestrictedBackupMode || !normalMode, app.isPersistent(),
                new Configuration(app.getWindowProcessController().getConfiguration()),
                app.compat, getCommonServicesLocked(app.isolated),
                mCoreSettingsObserver.getCoreSettingsLocked(),
                buildSerial, autofillOptions, contentCaptureOptions,
                app.mDisabledCompatChanges);
        ...
        if (normalMode) {
            try {
                //这个方法最终会启动Activity
                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }

        return true;
    }

thread的真正实现是ActivityThread,ActivityThread的bindApplication会让AMS绑定进程,然后mAtmInternal.attachApplication这个方法会去启动Activity,这里我们先看AMS是怎么绑定进程的

public final void bindApplication(String processName, ApplicationInfo appInfo,
                                      ProviderInfoList providerList, ComponentName instrumentationName,
                                      ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                                      IInstrumentationWatcher instrumentationWatcher,
                                      IUiAutomationConnection instrumentationUiConnection, int debugMode,
                                      boolean enableBinderTracking, boolean trackAllocation,
                                      boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                                      CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                                      String buildSerial, AutofillOptions autofillOptions,
                                      ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges) {
        ...
        AppBindData data = new AppBindData();
        data.processName = processName;
        data.appInfo = appInfo;
        data.providers = providerList.getList();
        data.instrumentationName = instrumentationName;
        data.instrumentationArgs = instrumentationArgs;
        data.instrumentationWatcher = instrumentationWatcher;
        data.instrumentationUiAutomationConnection = instrumentationUiConnection;
        data.debugMode = debugMode;
        data.enableBinderTracking = enableBinderTracking;
        data.trackAllocation = trackAllocation;
        data.restrictedBackupMode = isRestrictedBackupMode;
        data.persistent = persistent;
        data.config = config;
        data.compatInfo = compatInfo;
        data.initProfilerInfo = profilerInfo;
        data.buildSerial = buildSerial;
        data.autofillOptions = autofillOptions;
        data.contentCaptureOptions = contentCaptureOptions;
        data.disabledCompatChanges = disabledCompatChanges;
        sendMessage(H.BIND_APPLICATION, data);
    }

sendMessage把进程的数据data传出,看下其Handler的实现

class H extends Handler {
        ...
        public static final int BIND_APPLICATION = 110;
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case BIND_APPLICATION:
                    AppBindData data = (AppBindData) msg.obj;
                    handleBindApplication(data);
                    break;
                    ...
            }
        }

    }

这里Handler收到Message,调用了handleBindApplication方法,我们看其实现

private void handleBindApplication(AppBindData data) {
        ...
        Application app;
        try {
            app = data.info.makeApplication(data.restrictedBackupMode, null);

            app.setAutofillOptions(data.autofillOptions);
            app.setContentCaptureOptions(data.contentCaptureOptions);

            mInitialApplication = app;
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!mInstrumentation.onException(app, e)) {
                throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                                + ": " + e.toString(), e);
            }
        }
    }

最终Instrumentation调用了callApplicationOnCreate,我们看下实现

public void callApplicationOnCreate(Application app) {
        app.onCreate();
    }
    

这里就启动了进程Application的onCreate方法。

二.AMS启动Activity

上面实现了AMS和进程的绑定,绑定完后,我们知道会去调用mAtmInternal.attachApplication来真正启动Activity,mAtmInternal为ActivityTaskManagerInternal,其实现为ActivityTaskManagerService,我们看下其实现

public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
        synchronized (mGlobalLockWithoutBoost) {
            try {
                return mRootWindowContainer.attachApplication(wpc);
            } finally {
            }
        }
    }
 boolean attachApplication(WindowProcessController app) throws RemoteException {
        boolean didSomething = false;
        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {

            final DisplayContent display = getChildAt(displayNdx);
            for (int areaNdx = display.getTaskDisplayAreaCount() - 1; areaNdx >= 0; --areaNdx) {
                final TaskDisplayArea taskDisplayArea = display.getTaskDisplayAreaAt(areaNdx);
                for (int taskNdx = taskDisplayArea.getStackCount() - 1; taskNdx >= 0; --taskNdx) {
                    final ActivityStack rootTask = taskDisplayArea.getStackAt(taskNdx);
                    if (rootTask.getVisibility(null /*starting*/) == STACK_VISIBILITY_INVISIBLE) {
                        break;
                    }
                    final PooledFunction c = PooledLambda.obtainFunction(
                            RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
                            PooledLambda.__(ActivityRecord.class), app,
                            rootTask.topRunningActivity());
                    rootTask.forAllActivities(c);
                    c.recycle();
                    if (mTmpRemoteException != null) {
                        throw mTmpRemoteException;
                    }
                }
            }
        }
        return didSomething;
    }

RootWindowContainer调用startActivityForAttachedApplicationIfNeeded判断是否需要启动Acitvity

private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r,
                                                                WindowProcessController app, ActivityRecord top) {
        if (r.finishing || !r.okToShowLocked() || !r.visibleIgnoringKeyguard || r.app != null
                || app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
            return false;
        }

        if (mStackSupervisor.realStartActivityLocked(r, app,
                top == r && r.isFocusable() /*andResume*/, true /*checkConfig*/)) {
            mTmpBoolean = true;
        }
        return false;
    }

这里调用了ActivityStackSupervisor的realStartActivityLocked方法,真正的来启动Activiy的流程了,我们还记得第一篇文章讲到Launcher启动应用Activity和应用本身启动Activity的分叉点吗?我们回顾下

   void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, r.info.applicationInfo.uid);
 
        boolean knownToBeDead = false;
        if (wpc != null && wpc.hasThread()) {
            try {
                realStartActivityLocked(r, wpc, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }
 
            // If a dead object exception was thrown -- fall through to
            // restart the application.
            knownToBeDead = true;
        }
 
        r.notifyUnknownVisibilityLaunchedForKeyguardTransition();
 
        final boolean isTop = andResume && r.isTopRunningActivity();
        mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
    }

这里就是判断如果进程已启动,我们就走realStartActivityLocked的方法,如果进程没有启动,我们就走启动进程的流程,也就是我们在启动进程,走了这么多步后,终于来到了一般Activity到启动流程了。我们看下其实现

 boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
                                    boolean andResume, boolean checkConfig) throws RemoteException {
        final ClientTransaction clientTransaction = ClientTransaction.obtain(
                proc.getThread(), r.appToken);

        final DisplayContent dc = r.getDisplay().mDisplayContent;
        clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                System.identityHashCode(r), r.info,
                mergedConfiguration.getGlobalConfiguration(),
                mergedConfiguration.getOverrideConfiguration(), r.compat,
                r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
                r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
                dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
                r.assistToken, r.createFixedRotationAdjustmentsIfNeeded()));

        final ActivityLifecycleItem lifecycleItem;
        if (andResume) {
            lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward());
        } else {
            lifecycleItem = PauseActivityItem.obtain();
        }
        clientTransaction.setLifecycleStateRequest(lifecycleItem);

        mService.getLifecycleManager().scheduleTransaction(clientTransaction);
        return true;
    }

建立ClientTransaction,设置CallBack,这里CallBack为LaunchActivityItem,lifecycleItem为生命周期,因为这里是建立Activity,所以是ResumeActivityItem,mService.getLifecycleManager获取到ClientLifecycleManager,我们看下其scheduleTransaction方法实现

 void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        final IApplicationThread client = transaction.getClient();
        transaction.schedule();
        if (!(client instanceof Binder)) {
            // If client is not an instance of Binder - it's a remote call and at this point it is
            // safe to recycle the object. All objects used for local calls will be recycled after
            // the transaction is executed on client in ActivityThread.
            transaction.recycle();
        }
    }

transaction为ClientTransaction,看其schedule的实现

 public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);
    }

mClient为IApplicationThread,其实现是ActivityThread

 @Override
        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            ActivityThread.this.scheduleTransaction(transaction);
        }

其实现是其父类ClientTransactionHandler,看其父类实现

  void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

sendMessage为ActivityThread.H.EXECUTE_TRANSACTION,其Handler实现是

 class H extends Handler {
        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case EXECUTE_TRANSACTION:
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    mTransactionExecutor.execute(transaction);
                    if (isSystem()) {
                        transaction.recycle();
                    }
                    break;
            }
    }

我们看到ClientTransaction调用了execute方法,看其实现

 public void execute(ClientTransaction transaction) {
        ...
        executeCallbacks(transaction); //最终执行onCreate方法

        executeLifecycleState(transaction); //最终执行onStart,onResume方法
    }

这两个方法executeCallbacks最终会启动onCreate方法,executeLifecycleState最终会启动onStart,onResume方法,我们先看executeCallbacks的实现

public void executeCallbacks(ClientTransaction transaction) {
            ...
        item.execute(mTransactionHandler, token, mPendingActions);
        item.postExecute(mTransactionHandler, token, mPendingActions);

    }

item即为LaunchActivityItem,看其实现

  public void execute(ClientTransactionHandler client, IBinder token,
                        PendingTransactionActions pendingActions) {
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
    }

client为ActivityThread,所以会调用其handleLaunchActivity,看其实现

 @Override
    public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
        ...
        final Activity a = performLaunchActivity(r, customIntent);
        ...

        return a;
    }

这里看到其调用了performLaunchActivity,看其实现

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            //获取APK文件的包信息
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                    mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

        //创建上下文
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            //类加载器创建Activity的实例
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (activity != null) {
                //加载应用名
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                appContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

                appContext.setOuterContext(activity);
                //Activity的attach方法,创建Window对象和Activity关联
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
                checkAndBlockForNetworkAccess();
                activity.mStartedActivity = false;
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    //设置主题
                    activity.setTheme(theme);
                }

                activity.mCalled = false;
                if (r.isPersistable()) {
                    //启动onCreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
                r.activity = activity;
                mLastReportedWindowingMode.put(activity.getActivityToken(),
                        config.windowConfiguration.getWindowingMode());
            }
            r.setState(ON_CREATE);
            
        } catch (SuperNotCalledException e) {
            throw e;
        }
        return activity;
    }

这段逻辑很重要,其有Activity的各种初始化信息,我们这看下mInstrumentation.callActivityOnCreate方法,看下其实现

public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

这里最重要的是activity.performCreate方法,看其实现

  final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        ...
        if (persistentState != null) {
            onCreate(icicle, persistentState);
        } else {
            onCreate(icicle);
        }
        ...
    }
  public void onCreate(@Nullable Bundle savedInstanceState,
            @Nullable PersistableBundle persistentState) {
        onCreate(savedInstanceState);
    }

可以看到,这就是我们熟悉的onCreate方法了,至此启动Activity的功能算整体完成。不过还有Activity还走了onStart,onResume的生命周期的,我们继续看下它们是怎么实现的。

三.Activity中onStart和onResume的实现

我们看到第二节分析的,TransactionExecutor中的executeLifecycleState就是走的实现onStart和onResume方法,我们看下其源码

  private void executeLifecycleState(ClientTransaction transaction) {
        final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
        ...
        final ActivityClientRecord r =mTransactionHandler.getActivityClient(token);

        cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */, transaction);

        lifecycleItem.execute(mTransactionHandler, token, mPendingActions);
        lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);
    }

这里主要是调用了cycleToPath,其中lifecycleItem.getTargetState()中lifecycleItem为ResumeActivityItem,lifecycleItem.getTargetState得到ON_RESUME状态,看下其实现

 private void cycleToPath(ActivityClientRecord r, int finish, boolean excludeLastState,
                             ClientTransaction transaction) {
        final int start = r.getLifecycleState();
        final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState);
        performLifecycleSequence(r, path, transaction);
    }

因为刚刚启动了Activity,所以start是ON_CREATE, finish为ON_RESUME,我们看下TransactionExecutorHelper的getLifeCyclePath实现

  public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {
        mLifecycleSequence.clear();
        if (finish >= start)
            for (int i = start + 1; i <= finish; i++) {
                mLifecycleSequence.add(i);
            }
        ...
        if (excludeLastState && mLifecycleSequence.size() != 0) {
            mLifecycleSequence.remove(mLifecycleSequence.size() - 1);
        }
        return mLifecycleSequence;
    }

这里会把ON_CREATE和ON_RESUME中间值加入mLifecycleSequence,并且把finish值也即ON_RESUME也加入mLifecycleSequence,但是excludeLastState为ture,会执行 mLifecycleSequence.remove(mLifecycleSequence.size() - 1)把ON_RESUME去掉,这样就只有ON_START值加入了,这样我们看下后面调用的方法

 private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
                                          ClientTransaction transaction) {
        final int size = path.size();
        for (int i = 0, state; i < size; i++) {
            state = path.get(i);
            switch (state) {
                case ON_CREATE:
                    mTransactionHandler.handleLaunchActivity(r, mPendingActions,
                            null /* customIntent */);
                    break;
                case ON_START:
                    mTransactionHandler.handleStartActivity(r.token, mPendingActions);
                    break;
                case ON_RESUME:
                    mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
                            r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
                    break;
                    ...
            }
        }
    }

这里就只会执行ON_START状态,mTransactionHandler为ActivityThread,看其handleStartActivity的实现

public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
        final ActivityClientRecord r = mActivities.get(token);
        final Activity activity = r.activity;
        ...
        activity.performStart("handleStartActivity");
    }

    final void performStart(String reason) {
            ...
        mInstrumentation.callActivityOnStart(this);
    }
 public void callActivityOnStart(Activity activity) {
        activity.onStart();
    }

这里整个流程走下来,我们就看到其调用来Activity的onStart方法了。执行完cycleToPath方法后,启动了onStart方法,我们接着看cycleToPath方法后,调用了    lifecycleItem.execute(mTransactionHandler, token, mPendingActions),lifecycleItem为ResumeActivityItem,我们看下其方法实现

public void execute(ClientTransactionHandler client, IBinder token,
                        PendingTransactionActions pendingActions) {
        client.handleResumeActivity(token, true /* finalStateRequest */, mIsForward,
                "RESUME_ACTIVITY");
    }

client为ActivityThread,看下实现

 public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                     String reason) {
        ...
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);

    }
 public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
                                                      String reason) {
        final ActivityClientRecord r = mActivities.get(token);
        ...
        r.activity.performResume(r.startsNotResumed, reason);

    }

    final void performResume(boolean followedByPause, String reason) {
        ...
        mInstrumentation.callActivityOnResume(this);
    }
 public void callActivityOnResume(Activity activity) {
        ...
        activity.onResume();
    }

执行这些流程后,我们看到其执行了Activity的onResume方法,这样从Activity的onCreate,onStart,onResume整个调用流程我们就清楚了。
Android9.0 Launcher启动Activity详解(二)

Android9.0 Launcher启动Activity详解(一)

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值