Android 深入研究之 ✨ Activity启动流程+Activity生命周期✨

简介: Activity分析目录前言Activity生命周期1.activity的四个状态2.activity的生命周期3.activity优先级Activity启动流程Activity的启动流程分析

Activity分析目录

前言

Activity生命周期

1.activity的四个状态

2.activity的生命周期

3.activity优先级

Activity启动流程

Activity的启动流程分析

第一步:准备启动

1. Activity.startActivity

2. Activity. startActivityForResult

3. Instrumentation. execStartActivity

4. AMS.startActivity

5. AMS. startActivityAsUser

6. startActivityMayWait

7. startActivityLocked

8. startActivityUncheckedLocked

9. ActivityStack . startActivityLocked

10. ActivityStackSuperVisor. resumeTopActivitiesLocked

第二步:暂停Activity A

12. ActivityStack.startPausingLocked

13. ApplicationThread. schedulePauseActivity

14. H.handleMessage

15. ActivityThread.handPauseActivity

16. AMS.activityPaused

17. ActivityStack.activityPauseLocked

18. ActivityStack. completePauseLocked

19. ActivityStackSuperVisor. resumeTopActivitiesLocked

20. ActivityStack. resumeTopActivityInnerLocked

第三步:启动新进程

21. ActivityStackSuperVisor. startSpecificActivityLocked

22. AMS. startProcessLocked

23. ActivityThread.main

24. ActivityThread.attach

25. AMS.attachApplication

26. ActivityStackSuperVisor. attachApplicationLocked

第四步:启动Activity B

27. ActivityStackSuperVisor. realStartActivityLocked

28. ApplicationThread. scheduleLaunchActivity

29. ActivityThread.handleLaunchActivity

30. ActivityThread.performLaunchActivity

前言

最近在网上学习安卓,想研究下安卓应用的实现原理。就先从最直观体验的Activity的流程开始研究吧


Activity生命周期

activity从开始到结束的四个状态。

activity的生命周期

activity的进程优先级

1.activity的四个状态

activity四个状态和所在的生命周期


关于activity的四个状态:

running-paused-stopped-killed


running->当前显示在屏幕的activity(位于任务栈的顶部),用户可见状态。

poused->依旧在用户可见状态,但是界面焦点已经失去,此Activity无法与用户进行交互。

stopped->用户看不到当前界面,也无法与用户进行交互 完全被覆盖.

killed->当前界面被销毁,等待这系统被回收

由上图我们得知:


Starting ——–>Running 所执行的生命周期顺序 onCreate()->onstart()->onResume()

当前称为活动状态(Running),此activity所处于任务栈的top中,可以与用户进行交互。


Running ——>Paused 所执行Activity生命周期中的onPause()

当前称为暂停状态(Paused),该Activity已失去了焦点但仍然是可见的状态(包括部分可见)。


Paused ——>Running所执行的生命周期为:OnResume()

当前重新回到活动状态(Running),此情况用户操作home键,然后重新回到当前activity界面发生。


Paused ——>Stoped所执行的生命周期为:onStop()

该Activity被另一个Activity完全覆盖的状态,该Activity变得不可见,所以系统经常会由于内存不足而将该Activity强行结束。


Stoped——>killed所执行的生命周期为:onDestroy()

该Activity被系统销毁。当一个Activity处于暂停状态或停止状态时就随处可能进入死亡状态,因为系统可能因内存不足而强行结束该Activity。


2.activity的生命周期



onCreate():

当我们点击activity的时候,系统会调用activity的oncreate()方法,在这个方法中我们会初始化当前布局setContentLayout()方法。


onStart():

onCreate()方法完成后,此时activity进入onStart()方法,当前activity是用户可见状态,但没有焦点,与用户不能交互,一般可在当前方法做一些动画的初始化操作。


onResume():

onStart()方法完成之后,此时activity进入onResume()方法中,当前activity状态属于运行状态

(Running),可与用户进行交互。


onPause()

当另外一个activity覆盖当前的acitivty时,此时当前activity会进入到onPause()方法中,当前activity是可见的,但不能与用户交互状态。


onStop()

onPause()方法完成之后,此时activity进入onStop()方法,此时activity对用户是不可见的,在系统内存紧张的情况下,有可能会被系统进行回收。所以一般在当前方法可做资源回收。


onDestory()

onStop()方法完成之后,此时activity进入到onDestory()方法中,结束当前activity。


onRestart()

onRestart()方法在用户按下home()之后,再次进入到当前activity的时候调用。调用顺序onPause()->onStop()->onRestart()->onStart()->onResume().


3.activity优先级

前台进程>可见进程>service进程>后台进程>空进程


前台进程(Foreground process)

前台进程是用户当前做的事所必须的进程,如果满足下面各种情况中的一种,一个进程被认为是在前台:


1.当前进程activity正在与用户进行交互。

2.进程持有一个Service,这个Service处于这几种状态:①Service与用户正在交互的Activity绑定。②Service是在前台运行的,即它调用了 startForeground()。③Service正在执行它的生命周期回调函数(onCreate(), onStart(), or onDestroy())。

3.进程持有一个BroadcostReceiver,这个BroadcostReceiver正在执行onReceive()方法


可见进程(Visible process)

如果一个进程不含有任何前台的组件,但仍可被用户在屏幕上所见。当满足如下任一条件时,进程被认为是可见的:


进程持有一个activity,这个activity不再前台,处于onPouse()状态下,当前覆盖的activity是以dialog形式存在的。

进程有一个service,这个service和一个可见的Activity进行绑定。

服务进程 (Service process)

服务进程没有和用户可以看到的东西绑定,但是它们一般在做的事情是用户关心的,比如后台播放音乐,后台下载数据等。所以系统会尽量维持它们的运行,除非系统内存不足以维持前台进程和可见进程的运行需要


1.如果一个进程中运行着一个service,这个service是通过 startService() 开启的,并且不属于上面两种较高优先级的情况,这个进程就是一个服务进程


后台进程(Background process)

如果进程不属于上面三种情况,但是进程持有一个用户不可见的activity,后台进程不直接影响用户体验,系统会为了前台进程、可见进程、服务进程而任意杀死后台进程


activity的onStop()被调用,但是onDestroy()没有调用的状态。该进程属于后台进程。


空进程

如果一个进程不包含任何活跃的应用组件,则认为是空进程。


该进程没有任何运行的数据了,且保留在内存空间,并没有被系统killed,属于空进程,该进程很容易被杀死。


Activity启动流程

在开始解析Activity之前先来看网上介绍Activity流程的几张图,方便对Activity流程有个直观印象









看完图就从代码来进行分析一下了


Activity的启动流程分析

Activity是Android应用程序的四大组件之中的一个,负责管理Android应用程序的用户界面,一般一个应用程序中包括非常多个Activity,他们可能执行在一个进程中。也可能执行在不同的进程中。

我们主要通过启动在不同进程中的Activity,来分析Activity的启动流程及AMS对Activity的管理逻辑。

有两个应用程序App1和App2。在App1的Activity A中点击button 启动 App2中的Activity B。

通过分析以上ActivityB的启动过程来了解AMS对Activity的管理逻辑。


步骤1:Activity A告诉AMS服务准备启动Activity B


步骤2:AMS服务处理并通知Activity A所在的进程pause Activity A,Activity A所在的进程处理完毕之后。通知AMS服务Activity A已经完毕pause工作。


步骤3:Activity B所在的进程没有启动,AMS服务首先启动一个新的进程。新的进程启动完毕之后。通知AMS服务进程启动完毕。


步骤4:AMS服务通知新的进程来启动Activity B。新的进程启动Activity B完毕之后。通知AMS服务Activity B启动完毕。


在分析Activity启动流程之前我们先简介下应用进程和AMS服务通信方法。AMS服务和应用进程间属于不同的进程。两者之间通信肯定和Binder有关。我们知道系统的服务都是实现了Binder的服务端。应用进程要想和它通信须要获取它的代理端。


然而AMS服务是怎样和应用进程通信的呢?在创建一个新的应用进程之后,系统首先会启动ActivityThread,ActivityThread是应用进程的主线程,在ActivityThread创建的时候会创建一个ApplicationThread的对象。这个ApplicationThread实现了一个Binder的服务端。新的进程创建完毕之后通知AMS服务的之后同一时候把自己进程的ApplicationThread的代理端送给AMS服务。AMS服务中保存了全部应用进程的ApplicationThread的代理对象。


所以AMS要想给应用进程发送消息,仅仅须要得到目标应景进程的ApplicationThread的代理端对象就可以。


因为Activity的启动流程比較复杂,一步一步来分析,这样比較easy理解。


第一步:准备启动

1. Activity.startActivity

在activity A中点击button启动activity B调用了 Activity的startActivity方法。


public void startActivity(Intent intent, @Nullable Bundle options) {

if (options != null) {

startActivityForResult(intent, -1, options);

……

}

在该方法中调用了activity的startActivityForResult方法来启动Intent描写叙述的Activity B,第二个參数-1表示不须要返回结果


2. Activity. startActivityForResult

public void startActivityForResult(

String who, Intent intent, int requestCode, @Nullable Bundle options) {

    Instrumentation.ActivityResult ar =
        mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, who,
            intent, requestCode, options);
   ……
}

在这种方法中终于调用了Instrumentation的execStartActivity方法来启动Activity。应用程序和系统之间的交互都集中交给Instrumentation来做,方便系统对这些活动的监视。


以下我们看下參数mMainThread.getApplicationThread(),mMainThread就是应用app1所在进程的主线程ActivityThread,getApplicationThread返回的是一个ActivityThread的内部类ApplicationThread,该类是一个Binder对象。实现了Binder的服务端,通过參数传入到AMS服务中。AMS服务中则会保存它的client。这样AMS就能够通过它和应用进程通信了。


參数mToken是一个Binder代理对象,它指向了AMS中一个保存的ActivityRecord信息,mToken代表了Activity A,它通过參数传递给AMS后。AMS依据它就能够得到Activity A的信息了。


3. Instrumentation. execStartActivity

public ActivityResult execStartActivity(

Context who, IBinder contextThread, IBinder token, Activity target,

Intent intent, int requestCode, Bundle options) {

IApplicationThread whoThread = (IApplicationThread) contextThread;

    try {
        //intent做进程间传输的准备工作
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess();
        //进程间传输,终于调用到AMS服务中
        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);
    }
  ……
}

该方法中通过ActivityManagerNative的getDefault方法来获得AMS的代理对象,然后调用它的startActivity方法通过进程间传输调用到AMS服务startActivity方法,进程间的通信此处不再具体介绍。


到此处为止,以上的操作都是在应用程序App1的进程中运行的。


下面代码就进入了SystemServer进程的AMS服务中


4. AMS.startActivity

public final int startActivity(IApplicationThread caller, String callingPackage,

Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, Bundle options) {

return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,

resultWho, requestCode, startFlags, profilerInfo, options,

UserHandle.getCallingUserId());

}

该方法的參数中caller就是App1进程的ApplicationThread的binder对象,IBinder就是指向Activity A的ActivityRecord的Binder对象,紧接着这种方法就调用了startActivityAsUser方法


5. AMS. startActivityAsUser

public final int startActivityAsUser(IApplicationThread caller, String callingPackage,

Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,

int startFlags, ProfilerInfo profilerInfo, Bundle options, int userId) {

……

return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,

resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,

profilerInfo, null, null, options, false, userId, null, null);

}

调用了activityStackSupervisor的startActivityMayWait方法。ActivityStackSupervisor是Activity调度的核心类,Activity的调度相关的工作都是在ActivityStackSuperVisor中处理,主要管理Task和Stack.它是在AMS启动的时候创建的。


6. startActivityMayWait

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 config,

Bundle options, boolean ignoreTargetSecurity, int userId,

IActivityContainer iContainer, TaskRecord inTask) {

……

//PMS服务依据intent查询要启动的Activity B的信息,保存到ActivityInfo中

intent = new Intent(intent);

ActivityInfo aInfo =

resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);

……

        //决定当前活动的stack

ActivityContainer container = (ActivityContainer)iContainer;

final ActivityStack stack;

if (container == null || container.mStack.isOnHomeDisplay()) {

stack = mFocusedStack;

} else {

stack = container.mStack;

}

       ……
        //将PMS中查询到的Activity B的信息当做參数
        int res = startActivityLocked(caller, intent, resolvedType, aInfo,
                voiceSession, voiceInteractor, resultTo, resultWho,
                requestCode, callingPid, callingUid, callingPackage,
                realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
                componentSpecified, null, container, inTask);
      ……
}

resolveActivity方法中终于是PMS服务依据Intent信息去查询即将要启动的ActivityB的具体信息,


然后是查找相应的ActivityStack。即将要启动的Activity应该在放到哪个ActivityStack中,从參数传递过来的iContainer为null,所以要启动的Activity应该在mFocusStack中来管理。


mFocusStack指的是当前获得焦点的ActivityStack。


Android中普通情况下有下面几种ActivityStack


· Home Stack,这个是Launcher所在的Stack。 事实上另一些系统界面也执行在这个Stack上,比如最近任务.SystemUI等


· FullScreen Stack。全屏的Activity所在的Stack。 但事实上在分屏模式下。Id为1的Stack仅仅占了半个屏幕。


· Freeform模式的Activity所在Stack


· Docked Stack 下文中我们将看到,在分屏模式下。屏幕有一半执行了一个固定的应用。这个就是这里的Docked Stack


· Pinned Stack 这个是画中画Activity所在的Stack


第一个和第二个是较经常使用的Stack,后边三个的Stack主要和多窗体模式有关。由此可见,大部分应用程序事实上都是在同一个Stack中即FullScreenStack中。


然后调用startActivityLocked方法继续启动Activity.


7. startActivityLocked

final int startActivityLocked(IApplicationThread caller,

Intent intent, String resolvedType, ActivityInfo aInfo,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,

IBinder resultTo, String resultWho, int requestCode,

int callingPid, int callingUid, String callingPackage,

int realCallingPid, int realCallingUid, int startFlags, Bundle options,

boolean ignoreTargetSecurity, boolean componentSpecified, ActivityRecord[] outActivity,

ActivityContainer container, TaskRecord inTask) {

    //callerApp 代表调用方的应用进程。即App1的应用进程
    ProcessRecord callerApp = null;
    if (caller != null) {
      //依据caller找到AMS中保存的App1的processRecord对象
        callerApp = mService.getRecordForAppLocked(caller);
        if (callerApp != null) {
        //得到App1应用进程的pid和应用的uid
            callingPid = callerApp.pid;
            callingUid = callerApp.info.uid;
        }
    ……
    }
    ……
  //即调用者的Activity组件
    ActivityRecord sourceRecord = null;
  //返回结果的Activity组件
    ActivityRecord resultRecord = null;
    if (resultTo != null) {
      //依据resultTo Binder对象得到其指向的ActivityRecord,即Activity A的ActivityRecord信息
        sourceRecord = isInAnyStackLocked(resultTo);
        //普通情况下请求的Activity和要接收返回结果的Activity是同一个
        if (sourceRecord != null) {
            if (requestCode >= 0 && !sourceRecord.finishing) {
                resultRecord = sourceRecord;
            }
        }
    }
    final int launchFlags = intent.getFlags();
    ……
    //依据准备的信息,创建一个即将启动的ActivityRecord对象。即Activity B
    ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
            intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
            requestCode, componentSpecified, voiceSession != null, this, container, options);
    ……
    //将刚创建的目标Activity的ActivityRecord作为參数,继续调用startActivityUncheckedLocked方法来启动
    err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
            startFlags, true, options, inTask);
    ……
    return err;
}

StartActivityLocked方法主要作用是创建一个即将要启动的Activity的ActivityRecord对象。


该方法中首先依据參数传进来的代表Activity A的binder对象,来获得Activity A的ActivityRecord信息。然后获取调用进程的pid和调用程序的uid。


依据这些信息和ainfo创建一个ActivityRecord对象r。代表ActivityB。


这样就获取了调用者的Activity A的组件信息,和即将要启动的目标Activity B的信息。


分别保存在sourceRecord和r中


最后调用startActivityUncheckedLocked方法来继续启动Activity B


8. startActivityUncheckedLocked

final int startActivityUncheckedLocked(final ActivityRecord r, ActivityRecord sourceRecord,

IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,

boolean doResume, Bundle options, TaskRecord inTask) {

    //依据flag获取对应的启动模式。我们代码中没有设置启动模式,所以默认应该是标准模式。这三个变量都应该是false    
    final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;
    final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
    final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK;
  ……
  //即将要启动activity的 task
  ActivityStack targetStack;
  ……
  //是否新建一个Task
  boolean newTask = false;
  ……
  //获取Activity A的Task,然后赋值给B,即两个Activity位于同一个Task中
   else if (sourceRecord != null) {

//获取Activity A所在的Task

final TaskRecord sourceTask = sourceRecord.task;

//将A所在的ActivityStack作为B启动的Stack

targetStack = sourceTask.stack;

targetStack.moveToFront(“sourceStackToFront”);

//获取ActivityStack中的 top Task是不是和当前的Task一致,假设不一致则将当前的Task移动到ActivityStack的顶端

final TaskRecord topTask = targetStack.topTask();

if (topTask != sourceTask) {

targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options,

r.appTimeTracker, “sourceTaskToFront”);

}

……

//将当前Activity Stack mLastPausedActivity设置为null

targetStack.mLastPausedActivity = null;

//调用startActivityLocked方法

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


这种方法中主要处理的工作就是Activity四种启动模式的处理。依据Intent中的flag标志来决定不同的启动模式,然后依据不同的启动模式来决定Activity在Task中的位置。


这种方法比較复杂。我们仅仅关注了和我们逻辑相关的代码。


关于启动模式的处理就不在具体分析。


因为我们没有设置Activity B 的启动模式,即Activity B默认是标准的启动模式。Activity A和Activity B应该放在同一个Task中。


在前面我们也已经分析过了,普通情况下全部的应用程序的Activity都是位于同一个ActivityStack中的。此处代码中也能够分析出把ActivityA所在的ActivityStack直接当成启动Activity B的Activity Stack.


我们通过一幅图来了解下Task和ActivityStack的关系。


Task和Stack的关系


Android系统中的每个Activity都位于一个Task中。一个Task能够包括多个Activity,同一个Activity也可 能有多个实例。 在AndroidManifest.xml中,我们能够通过 android:launchMode 来控制Activity在Task中的实例。


另外,在startActivity的时候。我们也能够通过 setFlag 来控制启动的Activity在Task中的实例。


Task管理的意义还在于近期任务列表以及Back栈。 当你通过多任务键(有些设备上是长按Home键。有些设备上是专门提供的多任务键)调出多任务时。事实上就是从ActivityManagerService获取了近期启动的Task列表。


事实上在ActivityManagerService与WindowManagerService内部管理中,在Task之外,另一层容器。 这个容器应用开发人员和用户可能都不会感觉到或者用到。但它却很重要,那就是Stack,Android系统中的多窗体管理,就是建立在Stack的数据结构上的 。 一个Stack中包括了多个Task。一个Task中包括了多个Activity(Window)


最后调用获取的ActivityTask的startActivityLocked来继续启动


9. ActivityStack . startActivityLocked

final void startActivityLocked(ActivityRecord r, boolean newTask,

boolean doResume, boolean keepCurTransition, Bundle options) {

//获取要启动的Task对象

TaskRecord rTask = r.task;

final int taskId = rTask.taskId;

   TaskRecord task = null;
    if (!newTask) {
        //遍历AMS中全部的Task,找到目标task,然后将即将要启动的Activity增加到Task的栈顶
        boolean startIt = true;
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            task = mTaskHistory.get(taskNdx);
            if (task.getTopActivity() == null) {
                // All activities in task are finishing.
                continue;
            }
            if (task == r.task) {
                if (!startIt) {
                    task.addActivityToTop(r);
                    r.putInHistory();
                    ……
  //传入的resume位 true。然后调用  resumeTopActivitiesLocked方法继续启动Activity     
    if (doResume) {
        mStackSupervisor.resumeTopActivitiesLocked(this, r, options);
    }

该方法主要作用是变量全部Task,找到目标task然后将即将要启动的Activity的ActivityRecord增加到栈顶


10. ActivityStackSuperVisor. resumeTopActivitiesLocked

boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,

Bundle targetOptions) {

    //假设目标task是front Stack调用resumeTopActivityLocked
    if (isFrontStack(targetStack)) {
        result = targetStack.resumeTopActivityLocked(target, targetOptions);
    }
    //然后遍历找到时frontStack 的task运行resumeTopActivityLocked
    for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
        ……
            }
            if (isFrontStack(stack)) {
                stack.resumeTopActivityLocked(null);
            }
……

这种方法的主要作用就是找到frontStack然后调用它的resumeTopActivityLocked方法。


因为我们的ActivityA启动ActivityB都是位于同一个Task,所以当前Task就是frontStack,所以直接调用ActicityStack的resumeTopActivityLocked方法。


resumeTopActivityLocked方法则直接有调用了resumeTopActivityInnerLocked方法


ActivityStack. resumeTopActivityInnerLocked

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {

……
//找到栈顶第一个不是出于finishing状态的ActicityRecord
final ActivityRecord next = topRunningActivityLocked(null);
……
//调用startPausingLocked方法来暂停上一个Activity
 if (mResumedActivity != null) {
    if (DEBUG_STATES) Slog.d(TAG_STATES,
            "resumeTopActivityLocked: Pausing " + mResumedActivity);
    pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause);
}

仅仅看这种方法中的关键部分。


首先调用topRunningActivityLocked方法来找到当前栈顶不是出于fininshing状态的ActicityRecord。


我们刚才把即将要启动的ActicityRecord r增加到了当前Task的栈顶,所以这个next就是即将要启动的Acticity B的ActicityRecord


当mResumedActivity不是Null的时候就调用startPausingLocked方法来暂停上一个Activity。ActivityStack有三个成员变量。mResumedActivity表示这个栈中处于激活状态的Activity。这个栈中处于激活状态的Acticity就是Acticity A了。mLastPausedActivity表示上一次被暂停的Activity。mLastPausingActivity即当前栈中正在被暂停的Activity.


mResumedActivity表示Activity A不是Null,所以调用startPasingLocked来暂停Activity A。


Activity启动流程的第一部分就到此为止。总结下这部分做的主要工作


调用Activity的startActivity方法来启动目标Activity


调用Instrumentation的方法execStartActivity方法。方便Instrumentation对交互进行监測


以上部分是在App1的进程中运行。之后会通过进程间通信调用到AMS服务中调用AMS的startActivity方法。此时进入SystemServer进程。


然后由AMS中管理Acticity核心调度的类ActivityStackSupervisor的方法startActivityMayWait来处理。该方法中主要是依据Intent从PMS中查询目标Activity的信息


ActivityStackSuperVisor的startActivityLocked方法主要是在AMS中找调用进程的processRecord信息,调用Activity的ActivityRecord信息。目标Activity还没有启动,所以须要先创建一个目标Activity的ActivityRecord信息。


ActivityStackSuperVisor的StartActivityUncheckedLocked方法主要来处理启动模式相关的逻辑,依据不同的启动模式,找到对应的对的ActivityStack,然后又对应的ActivityStack进行处理


ActivityStack将目标Activity增加到相应的Task栈顶


调用ActivityStackSuperVisor的resumeTopActivityLocked方法找到处于前台的Task。然后调用它的resumeTopActivityLocked方法激活目标Activity.


当前的Task的栈開始Pasuing调用的Activity


以上几个步骤完毕了AMS对调用Activity及目标Activity的信息收集处理,依据启动模式来决定将目标Activity方法那个栈中。然后目标栈開始讲当前处于激活状态的Activity Pause掉给目标Activity腾地方。


接着看第二部分,怎样调用Activity 的由Resume变为Pause的过程


第二步:暂停Activity A

12. ActivityStack.startPausingLocked

final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming,

boolean dontWait) {

//mResumeActivity代表当前激活的Activity。即Activity A

ActivityRecord prev = mResumedActivity;

……

//当前处于激活状态mResumedActivity 的设置为null

mResumedActivity = null;

//即将要处于pasuing状态的Activity 就是Activity A

mPausingActivity = prev;

mLastPausedActivity = prev;

……

//将Activity A的状态设置为PAUSING

prev.state = ActivityState.PAUSING;

//找到当前的栈顶的topActivity就是 Activity B

final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();

//调用进程不为null,且调用进程的ApplicationThread不为null

if (prev.app != null && prev.app.thread != null) {

try {

……

通过调用进程的ApplicationThread通知调用进程schedulePauseActivity方法

prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,

userLeaving, prev.configChangeFlags, dontWait);


首先得到要Pause的Activity prev,就是mResumeActivity,代表当前处于激活状态的Activity A,然后把mResumeActivity设置为null,由于Activity A立即要为pause状态了。而Activity B还没有启动。所以此时没有激活状态的Activity。


把mPausingActivity设置为Activity B,表示处于Pausing状态的Activity. 同一时候prev.state = ActivityState.PAUSING,把Activity A的状态设置为PAUSING。


Prev是Activity A的ActivityRecord 对象。prev.app代表的是Activity A所在进程的ProcessRecord,prev.app.thread就是Activity A 所在进程的ApplicationThread,我们在前面说过,ApplicationThread是一个Binder对象,服务端在应用进程中,AMS服务中这持有每一个进程的Binderclient。这样AMS就能够向应用进程发送消息了。


进程间通信Binder机制此处不解释


此方法最后会通过进程间通信调用Activity A所在进程的schedulePauseActivity方法,继续Activity Pause的过程。


13. ApplicationThread. schedulePauseActivity

public final void schedulePauseActivity(IBinder token, boolean finished,

boolean userLeaving, int configChanges, boolean dontReport) {

sendMessage(

finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,

token,

(userLeaving ?

1 : 0) | (dontReport ? 2 : 0),

configChanges);

}


应用进程的ApplicationThread是ActivityThread的一个内部类,參数token是一个Binder类型的对象,指向了AMS中与Activity A相应的一个ActivityRecord。当前要进入pause状态,传进来的finished位false。


此方法发送了一个PAUSE_ACTIVITY的Message给Handler,接着看Handler的handleMessage方法


14. H.handleMessage

ase PAUSE_ACTIVITY:

        ……
        handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
                        (msg.arg1&2) != 0);

调用handlePauseActivity方法来处理


15. ActivityThread.handPauseActivity

private void handlePauseActivity(IBinder token, boolean finished,

boolean userLeaving, int configChanges, boolean dontReport) {

ActivityClientRecord r = mActivities.get(token);

if (r != null) {

        //调用Activity A的onPause方法
        performPauseActivity(token, finished, r.isPreHoneycomb());
        //等待pause状态的数据写入完毕.
        if (r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }
         ……
            try {
                //进程间通信。调用AMS的ActivityPause方法
                ActivityManagerNative.getDefault().activityPaused(token);
            } catch (RemoteException ex) {

在ActivityThread中的mActivities集合中保存了当前进程中的全部的Activity,每个Activity都用一个ActivityClientRecord表示,集合中以binder为key来保存,相同AMS服务中的全部Activity也是在一个集合中存储。在AMS服务中的每个Activity用ActivityRecord来表示,相同key值也是Binder对象,我们知道Binder对象是能够进程间传递的,所以使用binder来做key值,能够使应用进程的ActivityClientRecord和AMS中的ActivityRecord一一相应起来。


此方法中首先依据AMS中传递过了的binder对象来获取应用进程中的ActivityClientRecord对象,因为參数token指向的是AMS中的代表Activity A的ActivityRecord,所以此处获取的就是Activity A的ActivityClientRecord.


然后调用preformPauseActivity方法,这种方法中会调用Activity A的onSaveInstance方法。然后调用Activity A的onPause方法。


由于onSaveInstance方法会存储当前的Activity信息,要写入磁盘进行IO操作,所以QueuedWork.waitToFinish()方法用来等待存储操作完毕。


最后,又是进程间通信,调用AMS的activityPaused方法,此处离开了Activity A的进程。进入了AMS的 SystemServer进程。


16. AMS.activityPaused

public final void activityPaused(IBinder token) {

//获或Activity 所在的ActivityStack

ActivityStack stack = ActivityRecord.getStackLocked(token);

if (stack != null) {

//调用目标ActivityStack的activityPauseLocked方法

stack.activityPausedLocked(token, false);

}

}


Token代表的是Activity A ,此处依据token找到Activity A所在的ActivityStack,然后由目标Stack来处理activityPause逻辑


17. ActivityStack.activityPauseLocked

final void activityPausedLocked(IBinder token, boolean timeout) {

//依据token获取Activity A的ActivityRecord对象

final ActivityRecord r = isInStackLocked(token);

if (r != null) {

//移除pause超时消息

mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);

if (mPausingActivity == r) {

//调用completePauseLocked方法继续运行pause逻辑

completePauseLocked(true);

}

……


Token代表的是Activity的binder对象,依据token能够获得Activity A的信息ActivityRecord,移除Pause超时消息,当运行完pause逻辑的ActivityRecord和我们运行pause逻辑前的activityRecord一样的时候,即是同一个Activity。就能够调用completePauseLocked方法来完毕Activity A Pause最后的逻辑了。


18. ActivityStack. completePauseLocked

if (resumeNext) {

final ActivityStack topStack = mStackSupervisor.getFocusedStack();

if (!mService.isSleepingOrShuttingDown()) {

mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null);

}

……

}


resumeNext就是completePauseLocked传进来的參数,为true。调用mStackSupervisor的getFocusedStack()方法来获取正在处理的ActivityStack. mService.isSleepingOrShuttingDown。推断AMS服务是否处于正常激活状态,然后调用mStackSupervisor的resumeTopActivitiesLocked继续处理。


19. ActivityStackSuperVisor. resumeTopActivitiesLocked

这个步骤我们在第10步骤的时候处理过,此处和第10步逻辑一致,假设当前的ActivityStack是frontStack。直接调用ActivityStack的resumeTopActivityLocked。


ActivityStack的resumeTopActivityLocked方法则直接有调用了ActivityStack的resumeTopActivityInnerLocked方法。


20. ActivityStack. resumeTopActivityInnerLocked

private boolean resumeTopActivityInnerLocked(ActivityRecord prev, Bundle options) {

……

//找到栈顶的Activity,此时栈顶的acitvity就是即将要启动的Activity B

final ActivityRecord next = topRunningActivityLocked(null);

……

  //我们知道,在前面Activity A变为pause状态的时候。我们就把mResumeActivity 置为了Null
  if (mResumedActivity != null) {
        if (DEBUG_STATES) Slog.d(TAG_STATES,
                "resumeTopActivityLocked: Pausing " + mResumedActivity);
        pausing |= startPausingLocked(userLeaving, false, true, dontWaitForPause);
    }
  ……
  //即将要启动的Activity处于一个新的进程,眼下还没有启动
  if (next.app != null && next.app.thread != null) {
    ……
  }else{
    ……
    //来启动目标Activity
    mStackSupervisor.startSpecificActivityLocked(next, true, true);
  }

在第11步的时候,已经走过这种方法的逻辑,那个时候调用这种方法来激活目标Activity的时候。推断mResumeActivity不为空,说明当前有一个处于正在激活状态的Activity,须要先将原先的Activity变为pause状态。


当Activity A变为Pause状态后。再次走到这种方法来激活目标Activity,此时mResumeActivity已经为空,所以不须要再运行pause的逻辑。


然后推断目标Activity next的进程app和ApplicationThread是否为空,由于Activity A 所在的进程还没有创建,所以两个都为空,直接运行mStackSupervisor的startSpecificActivityLocked来启动新的Activity。


第三步:启动新进程

21. ActivityStackSuperVisor. startSpecificActivityLocked

void startSpecificActivityLocked(ActivityRecord r,

boolean andResume, boolean checkConfig) {

//获取目标Activity的进程ProcessRecord

ProcessRecord app = mService.getProcessRecordLocked(r.processName,

r.info.applicationInfo.uid, true);

……

//,因为目标Activity所在进程还没有创建,所以为空

if (app != null && app.thread != null) {

……

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

该方法比較简单,依据目标Activity的ProcessName来查找相应的ProcessRecord.依据processRecord中的进程app和ApplicationThread来推断。假设这两个变量不为空,说明目标Activity的进程和执行环境已经具备。直接启动Activity就能够,我们知道眼下目标Activity的进程还没有启动,所以须要调用AMS先启动一个目标Activity的进程。


22. AMS. startProcessLocked

final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,

……) {

long startTime = SystemClock.elapsedRealtime();

ProcessRecord app;

if (!isolated) {

app = getProcessRecordLocked(processName, info.uid, keepIfLarge);

     String hostingNameStr = hostingName != null
            ?

hostingName.flattenToShortString() : null;

  ……
  startProcessLocked(
        app, hostingType, hostingNameStr, abiOverride, entryPoint, entryPointArgs);

得到一个processRecord对象,然后把目标activity的Compoment转化成一个字符串,方便传输。


最后调用还有一个startProcessLocked方法来继续处理新进程的启动。


private final void startProcessLocked(ProcessRecord app, String hostingType,

String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {

    int uid = app.uid;
        int[] gids = null;
    ……
     //從PMS服务中查找目标应用相应的权限组
     final IPackageManager pm = AppGlobals.getPackageManager();
                permGids = pm.getPackageGids(app.info.packageName, app.userId);
        ……
        gids = new int[permGids.length + 2];
                System.arraycopy(permGids, 0, gids, 2, permGids.length);
     ……
     app.gids = gids;
     ……
      if (entryPoint == null) entryPoint = "android.app.ActivityThread";
     调用Process的静态方法启动一个新的进程 
     Process.ProcessStartResult startResult = Process.start(entryPoint,
                app.processName, uid, uid, gids, debugFlags, mountExternal,
                app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                app.info.dataDir, entryPointArgs);

这种方法做的主要工作就是调用Process的静态方法启动一个新的进程。启动新的进程的过程大概是,Zygote进程会fork一个新的子进程出来,子进程创建完毕之后。classLoader载入ActivityThread类并创建一个ActivityThread实例,反射调用ActivityThread的main方法。


这样ActivityThread主线程就在新的进程中启动起来了。


接着看ActivityThread的main方法,此时已经在新的进程中运行了。我们来看ActivityThread的main方法。


23. ActivityThread.main

public static void main(String[] args) {

    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    Looper.loop();

}


这种方法主要工作就是调用Looper.prepareMainLooper创建一个消息循环队列。然后调用Looper.loop进入消息循环,当前线程进入消息循环中。使当前线程成为新进程的主线程,然后创建一个ActivityThread对象。调用Attach方法。


24. ActivityThread.attach

final ApplicationThread mAppThread = new ApplicationThread();


private void attach(boolean system) {

sCurrentActivityThread = this;

mSystemThread = system;

if (!system) {

……

final IActivityManager mgr = ActivityManagerNative.getDefault();

try {

mgr.attachApplication(mAppThread);

} catch (RemoteException ex) {

// Ignore

}

……

} else {

}


在AMS服务启动的时候。初始系统进程的执行环境的时候。那时候传入的參数system为true,表示是系统进程,而这次是普通的应用进程,所以參数system为false。


ActivityManagerNative.getDefault()方法获取AMS的代理,调用attachApplication方法发送一个进程间通信的请求,将创建的ApplicationThread对象传递给AMS服务。


ApplicationThread是一个ActivityThread本地binder对象,Binder的服务端在ActivityThread中,将Binder对象传递给AMS服务。则AMS服务中保存它的代理,AMS就获得了与新进程通信的方式。


此前的代码实在新建的进程中。即应用App2所在的进程,然后通过进程间通信,以下的代码再次进入AMS服务中。


25. AMS.attachApplication

      ApplicationInfo appInfo = app.instrumentationInfo != null
                ? app.instrumentationInfo : app.info;
        //进程间调用:调用新进程的bindApplication方法
        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());
     ……
    if (normalMode) {
        try {
        //调用ActivityStackSupervisor的方法来启动新的Activity
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        }
  ……
}

attachApplication方法获得调用进程的ID之后,直接调用attachApplicationLocked方法继续运行。


attachApplicationLocked方法主要工作


首先依据进程ID,从mPidsSelfLocked中查找相应进程的ProcessRecord。这个ProcessRecord就是新进程的对象。仅仅只是它之前没有指向不论什么进程,由于新的进程还没有创建。如今新进程已经创建完毕。所以须要将它指向新的进程。


从PMS服务中查询新进程相关的ContentProvider的信息。然后通过进程间通信请求。调用thread的bindApplication方法。这个thread就是新进程的ApplicationThread的代理端Binder对象。通过它终于调用到新进程中ActivityThread的handleBindApplication方法。来进一步处理新进程的执行环境的初始化。主要是新进程Application的初始化,Instrumentation的初始化和安装相关的ContentProvider.。


交给ActivityStackSupervisor来继续处理attchApplication逻辑。


26. ActivityStackSuperVisor. attachApplicationLocked

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {

final String processName = app.processName;

//遍历全部的stack,找到处于前台的ActivityStack

for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {

ArrayList stacks = mActivityDisplays.valueAt(displayNdx).mStacks;

for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {

final ActivityStack stack = stacks.get(stackNdx);

if (!isFrontStack(stack)) {

continue;

}

//找到处于栈顶的ActivityRecord

ActivityRecord hr = stack.topRunningActivityLocked(null);

if (hr != null) {

if (hr.app == null && app.uid == hr.info.applicationInfo.uid

&& processName.equals(hr.processName)) {

try {

//调用realstartActivityLocked方法来启动目标Activity

if (realStartActivityLocked(hr, app, true, true)) {

didSomething = true;

}

} catch (RemoteException e) {

……

}

}


此方法的主要工作就是遍历并找到目标stack,然后拿到这个ActivityStack栈顶的ActivityRecord,这就是目标Activity,是我们之前放到栈顶的。得到要启动的Activity信息之后,做了这么多的准备工作,最终要真正来启动新的Activity了。


第四步:启动Activity B

27. ActivityStackSuperVisor. realStartActivityLocked

final boolean realStartActivityLocked(ActivityRecord r,

ProcessRecord app, boolean andResume, boolean checkConfig)

throws RemoteException {

……

//将新进程的信息保存到ActivityRecord的app变量中

r.app = app;

    //获取目标Task
    final TaskRecord task = r.task;
    //找到Task对用的Stack
    final ActivityStack stack = task.stack;
    //跨进程调用,通知目标进程来启动Activity
    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);

这种方法中首先将ActivityRecord的app对象指向了新的进程。这样ActivityRecord就和新的进程关联了起来。


然后通过目标进程ApplicationThread代理Binder对象发起进程间通信请求,调用目标进程的scheduleLaunchActivity方法来启动新的Activity.


此处代码通过跨进程调用再次进入到了目标进程中。


28. ApplicationThread. scheduleLaunchActivity

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,

ActivityInfo info, Configuration curConfig,…… ProfilerInfo profilerInfo) {

        updateProcessState(procState, false);
    //依据进程间传递的消息,初始化ActivityClientRecord
        ActivityClientRecord r = new ActivityClientRecord();
        r.token = token;
        r.ident = ident;
    ……
    //发送消息,终于有Handler来处理
        sendMessage(H.LAUNCH_ACTIVITY, r);
    }

调用到ActivityThread内部的ApplicationThread中。该ApplicationThread实现了ApplicationThreadNative。这样就实现了进程间通信的Binder服务端。Binder进程间通信不在详解


终于发送消息,有Handler的HandMessage来处理。


在handleMessage中又调用了ActivityThread的handleLaunchActivity来处理。


29. ActivityThread.handleLaunchActivity

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {

//调用performLaunchActivity方法来启动Activity
  Activity a = performLaunchActivity(r, customIntent);
//Activity启动完毕后。调用handlResumeActivity来使Activity进入resume激活状态
    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        Bundle oldState = r.state;
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);

……


首先调用performLaunchActivity来创建一个Activity对象,并调用Activity的onCreate方法完毕Activity启动,随后调用handleResumeActivity方法。激活Activity,时Activity计入resume状态。


30. ActivityThread.performLaunchActivity

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

ActivityInfo aInfo = r.activityInfo;

//获取Activity的packageInfo信息

if (r.packageInfo == null) {

r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,

Context.CONTEXT_INCLUDE_CODE);

}

//获取Activity的Component信息

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

}

调用Instrumentation类来创建一个依据Activity的信息Activity对象
    Activity activity = null;
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
    activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
 //创建完毕后,调用Activity的attach方法来初始化Activity
 if (activity != null) {
            Context appContext = createBaseContextForActivity(r, activity);
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);
            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                    + r.activityInfo.name + " with config " + config);
            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);
……
//调用Activity的onCreate方法
mInstrumentation.callActivityOnCreate(activity, r.state);
……
//运行Activity的onStart方法
if (!r.activity.mFinished) {
                activity.performStart();
                r.stopped = false;
            }
……
//调用Activity的onRestoreInstancestate方法
if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
……
//调用Activity的onPostCreate方法
 mInstrumentation.callActivityOnPostCreate(activity, r.state);
     ……
//把创建的相应的ActivityClientRecord以binder为键值,保存到mActivities中
 mActivities.put(r.token, r);

在新的进程中,依据AMS传递过来的信息创建了一个ActivityClientRecord对象。该对象和AMS服务中的一个ActivityRecord相应。


在这种方法中。依据目标Activity B的ActivityClientRecord,终于调用Instrumentation类来创建一个Acitivity,创建过程就是ClassLoader载入相应的Activity类。用反射方法创建一个对象。


创建完新的Activity对象后,即Activity B的对象,然后调用它的onCreate方法,这样Activity B的onCreate对象就会被调用,以便载入自定义的用户界面,以及其它的初始化方法。


onCreate方法调用完毕之后。然后依次调用Activity的onStart,onRestoreInstanceState,onPostCreate方法等。这个就是Activity生命周期运行的逻辑。


到此为止。ActivityB就启动完毕了,它启动起来之后,意味着ActivityB所在的应用程序也就启动起来了。



相关文章
|
3月前
|
Android开发 开发者
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
Android UI设计: 请解释Activity的Theme是什么,如何更改应用程序的主题?
40 1
|
3月前
|
数据库 Android开发 开发者
Android基础知识:请解释Activity的生命周期。
Android基础知识:请解释Activity的生命周期。
39 2
|
7月前
|
Android开发
Android一些特殊情况下的生命周期分析
Android一些特殊情况下的生命周期分析
43 0
|
6月前
|
存储 SQL 人工智能
Android Activity启动流程一:从Intent到Activity创建
Android Activity启动流程一:从Intent到Activity创建
|
6月前
|
SQL 人工智能 移动开发
Android应用启动流程:从启动到可交互的过程解析
Android应用启动流程:从启动到可交互的过程解析
|
2月前
|
Android开发
[Android 四大组件] --- Activity
[Android 四大组件] --- Activity
22 1
|
3月前
|
Android开发
Android基础知识:什么是Fragment?与Activity的区别是什么?
Android基础知识:什么是Fragment?与Activity的区别是什么?
265 54
|
4月前
|
XML 安全 Java
Android Studio App开发入门之活动Activity中为活动补充附加信息讲解及实战(附源码 超详细必看)
Android Studio App开发入门之活动Activity中为活动补充附加信息讲解及实战(附源码 超详细必看)
35 0
|
4月前
|
Android开发
Android Studio App开发入门之在活动之间传递消息(附源码 超详细必看)(包括显示和隐式Intent,向上一个和下一个Activity发送数据)
Android Studio App开发入门之在活动之间传递消息(附源码 超详细必看)(包括显示和隐式Intent,向上一个和下一个Activity发送数据)
43 0
|
4月前
|
Android开发
Android Studio APP开发入门之活动Activity中启停活动页面的讲解及实战(附源码,包括Activity的启动结束、生命周期、跳转等)
Android Studio APP开发入门之活动Activity中启停活动页面的讲解及实战(附源码,包括Activity的启动结束、生命周期、跳转等)
41 0