Android 进阶之了解源码——Activity启动

本文详细解析了Android中Activity启动的全过程,从客户端调用startActivity方法开始,经过AMS(Activity Manager Service)的调度,到目标Activity的创建及显示,涵盖了四大组件之一Activity的生命周期及其与系统服务交互的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们通常说Android有四大组件,也有说法是五大组件,将Intent也算一种,不管是五大还是四大,它们都是Android app开发中最经常打交道的东西,刚开始的时候也都会碰到一些坑,很多时候我们都是搜索资料,看其他人写的博客来学习和避坑,其实很多博客资料也就是对官方文档的翻译,加上一些自己避坑心得的记录,有些时候即使通过一些资料解决了某个问题,但其实自己也说不出个所以然来,也是云里雾里,只是记住结论就行了,有些时候身边一些同事会说,做应用层的,不用太深入底层,先把功能需求做好就行了。这话是有一定道理,但我想,他们说的底层到底是哪一层呢?这可能就是很多程序员自嘲为码农的原因吧,不管是做什么开发,如果只是停留在API的熟练使用,即使再干十年,也只是在搬砖吧,所以我个人认为,从初级工程师进阶,最关键的一步就是要了解源码,要学习源码,而不是掌握多少流行库的调用。Read the fucking source code!另外讨论一个问题,什么是底层呢?我认为涉及到Java的都不能算底层,只有到C/C++代码那一层才能算底层,因此Framework不能称为底层。

这一篇博客就从Activity的启动流程开始写吧,这里以Android 6.0源码为例,其他版本或许会有不同。这篇博客力求简单通俗,给应用的开发带来一些启示,因此不会深入底层,不会涉及C/C++层,更不会涉及Linux内核的一些原理,因为博主也不甚了解,掩面而逃……

Activity启动流程

通常启动Activity是有三种方式的

  • 点击桌面上的图标
  • 在我们当前应用中startActivity
  • Am命令启动,在进阶第一篇中有详细讲到

事实上第一种和第二种是差不多的,Android系统的桌面就是一个应用。那我们就从startActivity方法开始跟踪

Activity.java

Activity中最终调用的是
这里写图片描述

这里mParent应该为空,如不确定,继续查看mParent不为空的情况,则调用如下方法

mParent.startActivityFromChild(this, intent, requestCode);

这里写图片描述

Instrumentation.java

由此,可知,最后仍然是调用到Instrumentation类的execStartActivity方法,我们打开该类的源码,查看execStartActivity方法,内部有三个同名方法,实际上三个方法具体实现都是类似的,我们找到刚刚跟踪的那个方法

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent,
        int requestCode, Bundle options, UserHandle user) {
        //……
        try {
            //……
            //通过直观感受分析,排除一些遍历检查之类的代码,继续浏览,可以发现又一个冠以startActivity名称的方法
            int result = ActivityManagerNative.getDefault()
                .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);
        }
       //…………
 }
ActivityManagerNative.java

我们看到了ActivityManagerNative.getDefault().startActivity方法,从方法名看就能推测出来这是个关键方法,那么继续跟踪该方法,打开源码
这里写图片描述
这是个抽象类,那么具体实现肯定不是它,最关键的一点是看到他继承于Binder,回顾一下Android的IPC机制AIDL接口,我们知道编写的AIDL接口文件生成的对应Java类中,内部类Stub就是继承于Binder的,看到其内部的startActivity方法,可知这里已经在进行跨进程通信了,到此本进程的调用已经结束,也就是说真正的startActivity方法,是远程服务端实现的。基于C/S模型,我们可以把以上的这些调用看做是客户端实现,接下来就要跟踪到远程服务端具体实现了。

关于binder的跨进程通信就先不讨论了,这里只需知道ActivityManagerNative.getDefault()调用对应的系统服务是AMS(即ActivityManagerService)就行。

ActivityManagerService.java

这里写图片描述

可以看到,ActivityManagerService正是继承自ActivityManagerNative,是其具体实现。我们找到startActivity方法,发现内部调用的是startActivityAsUser方法,直接看到该方法实现
这里写图片描述
略过一些检查方法,可以发现,这里的关键方法是startActivityMayWait,该方法是在ActivityStackSupervisor类中调用的,该类是一个重要的类,我们看可以在声明处看到一行注释
这里写图片描述

ActivityStackSupervisor.java

由此注释可知,这个类是用来管理所有的Activity任务栈的,这里先继续跟踪,进入startActivityMayWait方法,该方法很长,先看参数声明

这里写图片描述
这里将对应的一些值做了标记,标黄的一些参数,就是传的null值,在这个方法中,我们看到下面一行代码和注释:

        // Don't modify the client's object!
        intent = new Intent(intent);

由此我们可以推断,这里面很多代码都是对Intent里面携带的一些数据的获取、转换、校验以及修改,所以这里重新拷贝了一份intent,就是为了不改变原始的Intent,依据我一些大量查看源码的经验,其实查看纷繁复杂的源码的时候,很多情况下要靠直觉猜测推断,先有了一个自己的思路,再往下看源码印证,否则就很容易被复杂的源码带歪了,找不到北了,还有重要的一点就是追踪关键方法,不要纠结细节,看不懂的细节先忽略,即使你自己一两年前写的代码,没有注释的情况下你回头看,也不一定能说出每行代码的作用,更何况Android这么庞大复杂的源码,所以看源码,千万别纠结,不要有强迫症,学会认方法名,大胆猜测,小心验证。

继续往下看,也确实是在做一些Intent数据的处理,其中有个地方可以稍微关注一下:

       // Collect information about the target of the Intent.
        ActivityInfo aInfo =
                resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);

可以进入resolveActivity方法中去看看,这里和我们后面要讲的一个重要地方其实是调用的同一个方法,而ActivityInfo类其实就包含了所有的关于Activity的信息,这里获取的aInfo实例其实只是为了做一个检查,至于是检查什么,注释也告诉我们了,可以跳过了
这里写图片描述

好了,接下来就看一看startActivityMayWait这个方法中做的最主要的一件事吧
这里写图片描述
从方法名看,就知道应该就是解析意图的,实际上此处正是用来处理意图匹配的,可以进入具体实现查看,这里的AppGlobals.getPackageManager()调用resolveIntent的真实实现其实是在远程服务PackageManagerService中,需要研究意图是如何匹配的时候,就可以找到地方去查看了,源码如下
这里写图片描述

往下查看,还可以看到一些熟悉的地方,比如我在第一篇博客命令工具中提到做activity启动时间测试的命令adb shell am start -W -S,当我们指定activity启动后,就会输出以下一些信息,如果是从应用启动的,这里outResult是传的null,不会走
这里写图片描述
这里写图片描述
好了,还是回到正题吧,接力继续下去,这个方法中,真正的关代码是startActivityLocked

int res = startActivityLocked(caller, intent, resolvedType, aInfo,
                    voiceSession, voiceInteractor, resultTo, resultWho,
                    requestCode, callingPid, callingUid, callingPackage,
                    realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
                    componentSpecified, null, container, inTask);

同样,还是从名称就能分辨,我们就认准了startActivity关键字

startActivityLocked方法又是比较蛋疼了,继续做各种检查,并且处理一些错误信息,方法体也不短,这些代码就先略过吧,其实这个方法对于activity启动流程来讲,最主要做的一件事就是为目标activity创建了一个ActivityRecord。什么是ActivityRecord呢?我们之前博客提到用adb shell dumpsys activity a命令查看任务栈的一些具体信息的时候,就会输出一些TaskRecord和ActivityRecord信息,这里ActivityRecord就包含了即将要被创建的目标Activityd的各种信息。

这里写图片描述

可以看到这里将各种信息都通过构造方法传入ActivityRecord中保存起来

继续找我们的关键方法,看到startActivityUncheckedLocked方法名我们可以舒一口气了,终于不检查了……
这里写图片描述

启动模式处理

就先不查看具体方法实现了,这个方法体非常长,但是极具价值,这个方法就是用来处理Activity的启动模式的,这里要着重标记一下,当我们要研究Activity四种启动模式的时候,就要到这里来查找研究源码,这里先继续主题,暂时略过

继续查找跟踪关键方法

//…………
ActivityStack targetStack;
//…………
targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);

我们看到,这里是调用的ActivityStackstartActivityLocked方法,那么ActivityStack又是什么呢?看声明

/**
 * State and management of a single stack of activities.
 */
final class ActivityStack

这里的ActivityStack也就是我们常常说的,管理Activity的栈。

ActivityStackstartActivityLocked方法不是特别长,它主要是用来配置任务栈的,将目标ActivityRecord放到适当的地方,这里我们仍然略过,不去研究具体逻辑,到研究Activity启动模式的时候再回来查看。

继续找关键方法
这里写图片描述

这次就不是start开头了,叫resumeTopActivitiesLocked。这里的doResume默认传的是true,它是在startActivityUncheckedLocked方法参数中设置的,可以回溯查看

这里写图片描述

ActivityStack.java

这个方法从名字看,是用来恢复Activity的,这里检查了目标栈是不是在前台的,如果是就执行两个参数的resumeTopActivityLocked方法

继续跟踪,最终都会调用到resumeTopActivityInnerLocked方法中,这个方法中的代码也是相当多了,先浏览一下,可以发现一行注释

        // Remember how we'll process this pause/resume situation, and ensure
        // that the state is reset however we wind up proceeding.

由此我们可以猜测到这个方法中该处理上个Activity的暂停状态了。我们知道生命周期方法中,当前Activity先进入onPause状态,然后被启动的Activity才开始走生命周期方法,当这个Activity展现到前台之后,前一个Activity的生命周期才继续走onStop

继续查看代码,果然是找到了处理Pause的地方
这里写图片描述

来到startPausingLocked方法查看,有如下代码

这里写图片描述

可以看到,如果调用者的进程存在,就会走里面的schedulePauseActivity方法,我们之前是假设从桌面点击启动Activity的,那么桌面进程肯定是存在的,就会走这里。那么该方法的具体实现是在哪里呢?这里基本可以看出来,是从调用者进程的主线程执行该方法的,那么我们可以去一个app的主线程类中去查找一下,实际上这里是一个IPC远程调用

我们都知道,Java程序是从一个静态的main方法开始执行的,它是程序的入口,那么在Android开发中,这个main方法在哪儿呢?这个其实很容易查找到,无论是在eclipse中还是as中,我们通过打断点调试的方法,可以很容易的查看方法调用栈,从中就可以找到程序的入口,我们这里就直接找到该类ActivityThread

ActivityThread.java

这里写图片描述
找到Handler中的具体实现
这里写图片描述

一路跟踪
handlePauseActivity –> performPauseActivity
这里写图片描述

这里有几个地方值得深入看一下,performUserLeavingActivity方法是用来处理回调onUserInteraction()onUserLeaveHint()方法的,这两个方法不知道大家有没有用过,使用也很简单,就和其他生命周期方法一样使用。在处理某些场景的时候是比常规的生命周期方法有用的,它表示的是用户的主动操作行为,比如按返回键,或者home键触发当前Activity的生命周期,而其他的生命周期方法比如onPause和onStop是不区分主动还是被动的,举个栗子,比如说正处在你的Activity界面,突然来了一个电话,incall界面就会出现到前台,你的Activity肯定就会触发生命周期的方法,但是这种就是典型的被动触发,不是用户主动去操作的,那么他们就不会回调。我们这里可以看出来,这两个方法的执行还在onPause之前。

好了,继续回到正题,看到performPauseActivity
这里写图片描述
这里实际上就是在执行生命周期方法了,分别处理了onSaveInstanceStateonPause 我们也可以穷根究底的进去看一下
这里写图片描述
在Activity中的具体实现
这里写图片描述

到这里完成了该Activity的暂停,我们返回到ActivityThread类继续查看handlePauseActivity方法
这里写图片描述
这里可以看到,Activity的暂停阶段的过程还并未完全结束,又通过远程调用,将Activity暂停状态返回给AMS,那么我们继续进入ActivityManagerService中,找到activityPaused
这里写图片描述
看到是调用的ActivityStackactivityPausedLocked方法,进入该方法

…………
 final ActivityRecord r = isInStackLocked(token);
        if (r != null) {
            mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
            if (mPausingActivity == r) {
                …………
                completePauseLocked(true);
            }
        …………

关键代码是completePauseLocked(true),调用完这个方法,才是真正结束了整个暂停阶段的一系列工作。进入该方法,查看到关键代码块

       if (resumeNext) {
            final ActivityStack topStack = mStackSupervisor.getFocusedStack();
            if (!mService.isSleepingOrShuttingDown()) {
                mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null);
            } else {
                mStackSupervisor.checkReadyForSleepLocked();
                ActivityRecord top = topStack.topRunningActivityLocked(null);
                if (top == null || (prev != null && top != prev)) {
                    // If there are no more activities available to run,
                    // do resume anyway to start something.  Also if the top
                    // activity on the stack is not the just paused activity,
                    // we need to go ahead and resume it to ensure we complete
                    // an in-flight app switch.
                    mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null);
                }
            }
        }

可以看到,最终再次调用了mStackSupervisor.resumeTopActivitiesLocked方法处理任务栈的问题,这次我们对它内部的调用就已经轻车路熟了,依次调用如下
resumeTopActivitiesLocked -> resumeTopActivityLocked -> resumeTopActivityInnerLocked

再次进入ActivityStack.java

我们再次来到resumeTopActivityInnerLocked方法,这次就有些不同了

…………
if (mResumedActivity != null) {
            if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Pausing " + mResumedActivity);
            pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause);
        }
…………

mResumedActivity已经没有了,此时应该为空了,因为刚刚暂停了上一个Activity,新的Activity却还未创建,那么继续往下浏览

…………
if (next.app != null && next.app.thread != null) {
    …………
    //此处新的应用还未启动,进程肯定是不存在的,就会走else逻辑
else{
    // Whoops, need to restart this activity!
    …………
    mStackSupervisor.startSpecificActivityLocked(next, true, true);

}

我们看到注释,即可确定,这里的startSpecificActivityLocked就是关键方法,进入到该方法,发现又一次判断了进程是否存在,我们进程仍然是不存在的,继续找到关键方法

…………
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
                "activity", r.intent.getComponent(), false, false, true);

startProcessLocked从方法名已经可以判断,这里就是去启动目标Activity的进程了,mService就直接是ActivityManagerService,我们进入源码继续,发现调用的是参数最多的那个startProcessLocked方法,其实里面仍然不是真正创建进程的地方
这里写图片描述
这里只是创建了一个ProcessRecord,它是记录进程的全部信息的,可以看到声明

/**
 * Full information about a particular process that
 * is currently running.
 */
final class ProcessRecord

下面继续调用重载的startProcessLocked方法
这里写图片描述

那么进程到底是如何被启动的呢?进入该方法中查看
这里写图片描述

这里是通过zygote去启动一个新进程,实际上就是利用Linux的fork机制创建新进程,并调用该进程的Java入口main函数。我们知道在Linux下,是可以调用fork函数创建新进程的。

到这里可以舒一口气了,目标app进程终于被创建出来了,接下来在次进入ActivityThread类中,这次我们从Java的main函数开始了,终于找到了一点Java程序的感觉,有木有……?

继续看关键代码

public static void main(String[] args) {
        ……………………
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        ……………………

这里new了一个ActivityThread,创建了我们的主线程,也就是通常说的UI线程,这里还有一个重要的东西,Looper.prepareMainLooper(),这次就不讨论了,下次分析Handler机制的时候再说,这里的关键代码其实是thread.attach(false)

在Android中,创建一个Activity并不是自己去new出来,Activity的创建都交给系统去统一管理调度的,也是说我们接下来的创建阶段,肯定不是自己玩自己的就行了,而是需要交给AMS去统一管理,因此,摆在眼前的事实就是我们要到attach方法中找到远程调用的相关逻辑,有了这个猜测就好看代码了

 private void attach(boolean system) {
           ……………………
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);
            } catch (RemoteException ex) {
                // Ignore
            }
………………
}

果然,这里正好有一个远程调用,将一个ApplicationThread引用交给了远程服务端,我们进入AMS中看看具体实现

    public final void attachApplication(IApplicationThread thread) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid);
            Binder.restoreCallingIdentity(origId);
        }
    }
应用Application的创建

继续进入到attachApplicationLocked方法中查看,该方法体代码较多,但是结合注释和一些猜测,并不难理解,里面其实是做了一些预处理,继续浏览看到如下代码

 thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());

这里有一个叫bindApplication的方法,看名字,应该进去看一下,这里的thread就是我们之前的attach方法参数中传入的ApplicationThread对象,那么我们找到具体实现看一下

…………
sendMessage(H.BIND_APPLICATION, data);

找到handler中对于的具体实现

         case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
进入ActivityThread.java

继续找到真正的实现handleBindApplication方法,这个方法代码很多,快速浏览一下

private void handleBindApplication(AppBindData data) {
      …………
      final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
      …………
      if (data.instrumentationName !=null) {
          …………
          ApplicationInfo instrApp = new ApplicationInfo();
          …………
          try {
                java.lang.ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)cl.loadClass(data.instrumentationName.getClassName()).newInstance();
           } 
         …………
      } else {
         mInstrumentation =new Instrumentation();
      }
      …………
       // If the app is being launched for full backup or restore, bring it up in
       // a restricted environment with the base application class.
       Application app = data.info.makeApplication(data.restrictedBackupMode, null);
       mInitialApplication = app;
       …………
       try {
            mInstrumentation.onCreate(data.instrumentationArgs);
       }
       …………

       try {
            mInstrumentation.callApplicationOnCreate(app);
       }
…………
}

发现在这个方法中相关的处理还不少,程序的Context、Application以及Instrumentation都是在这里创建的,连Application的onCreate方法都是在这里回调的,这个地方要记住。现在我们回到刚刚的attachApplicationLocked方法,继续往下看

…………
      // See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                if (mStackSupervisor.attachApplicationLocked(app)) {
                    didSomething = true;
                }
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }
…………

从注释很容易发现mStackSupervisor.attachApplicationLocked是关键代码,我们进入到ActivityStackSupervisorattachApplicationLocked方法
这里写图片描述

这里遍历是要找到位于顶端的栈,并取出目标ActivityRecord对象,可以看到这里的app为空,因为我们很早之前就创建了ActivityRecord,但当时进程并没被创建出来,后续也还未关联到进程,所以一直是空,看到这里的关键方法realStartActivityLocked,看到这个名字,感觉长征快要结束了,代码都要看吐了,有木有……?

进到realStartActivityLocked里面,代码又是又臭又长,不过先别急,还是继续找关键代码,快速浏览

…………
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                    new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
                    task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                    newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
…………

这里的关键代码是scheduleLaunchActivity,调用再次回到了应用的进程,我们进入ActivityThread中查看具体实现

…………
ActivityClientRecord r = new ActivityClientRecord();
…………
sendMessage(H.LAUNCH_ACTIVITY, r);

这里创建了一个客户端的ActivityRecord,真正的实现仍然是在Handler里,我们继续跟踪

…………
case LAUNCH_ACTIVITY: {
     …………
     handleLaunchActivity(r, null);
}

handleLaunchActivity -> performLaunchActivity

performLaunchActivity方法就是真正的实现了,在这个方法里,终于看到了我们想要看到的,整个流程到此基本结束了,西天取经不容易啊!

…………
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        …………
        }

原来我们的Activity是反射创建的,往下就是生命周期的方法了,没什么好说了
这里写图片描述

至于Activity的显示,则要回到handleLaunchActivity方法往下看,找到handleResumeActivity方法,这里就不细看了,只有一个地方多说一下,那就是上图中调用的activityattach方法,正是在这个方法中将全局ContextPhoneWindowFragmentManager等等关联到自己的。

启动流程归纳总结

相关概念

ActivityRecord:记录着Activity信息,相当于一个Activity
TaskRecord:记录着task信息
ActivityStack:管理Activity的栈

盗一张图描述它们之间的关系
这里写图片描述

启动流程时序图

  • 在客户端应用中发起
Created with Raphaël 2.1.2ActivityActivityIntrumentationIntrumentationActivityManagerProxyActivityManagerProxyActivityManagerServiceActivityManagerServicestartActivitystartActivityForResultexecStartActivitystartActivity
  • 在服务端控制逻辑
    1.准备阶段
Created with Raphaël 2.1.2ActivityManagerServiceActivityManagerServiceActivityStackSupervisorActivityStackSupervisorActivityStackActivityStackstartActivitystartActivityAsUserstartActivityMayWaitstartActivityLockedstartActivityUncheckedLockedresumeTopActivitiesLockedresumeTopActivityLockedresumeTopActivityInnerLocked

2.暂停上一个Activity

Created with Raphaël 2.1.2ActivityStackActivityStackActivityThreadActivityThreadInstrumentationInstrumentationActivityActivityActivityManagerServiceActivityManagerServicestartPausingLockedschedulePauseActivityhandlePauseActivityperformPauseActivitycallActivityOnPauseperformPauseactivityPausedactivityPausedLockedcompletePauseLocked

3.启动目标进程

Created with Raphaël 2.1.2ActivityStackActivityStackActivityStackSupervisorActivityStackSupervisorActivityManagerServiceActivityManagerServiceActivityThreadActivityThreadresumeTopActivityInnerLockedstartSpecificActivityLockedstartProcessLockedmain

4.创建目标Activity

Created with Raphaël 2.1.2ActivityThreadActivityThreadActivityManagerServiceActivityManagerServiceActivityStackSupervisorActivityStackSupervisormainattachattachApplicationattachApplicationLockedrealStartActivityLockedscheduleLaunchActivityhandleLaunchActivityperformLaunchActivity

5.显示

Created with Raphaël 2.1.2ActivityThreadActivityThreadActivityActivityInstrumentationInstrumentationperformLaunchActivityattachsetThemecallActivityOnCreateperformCreateonCreate
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程之路从0到1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值