Android App启动流程

本文详细探讨了Android系统中App的启动流程,从Android系统桌面分析,到点击应用图标后系统执行的操作,再到ActivityManagerService如何通过Binder通知ActivityThread启动Activity。文中详细解释了每个步骤,包括用户句柄对Activity启动方式的影响,以及启动过程中涉及的Binder通信和Activity生命周期的调用。
摘要由CSDN通过智能技术生成

前言

在使用Android手机时,我们总会启动各种各样的App以满足生活的各种需求,你是否想过,我们是怎样启动这些APP的?今天我将带着读者一起探索在Android系统中,一个App是如何被启动的。

在开始分析之前,我们先回想下启动一个App的流程:

Android系统桌面->点击应用图标->启动App

从这个过程来看,只要弄明白:

  1. Android系统桌面是什么
  2. 点击应用图标后Android系统执行了什么操作

就可以解决我们“App如何被启动”的疑问。

Android系统桌面是什么

如何分析Android系统桌面是什么

在Android系统中,Activity是视图存在的根本,那么我们可以通过命令adb shell dumpsys activity activities判断是哪个Activity为我们呈现桌面视图的。

以我的小米5为例,通过USB连上电脑后,输入命令adb shell dumpsys activity activities得到结果如下:

可以看到,显示桌面视图的Activity是com.miui.home包下的名为Launcher的Activity。

因为虚拟机编译AOSP实在是太慢了,所以我没有编译AOSP得到系统镜像,然后运行模拟器跑AOSP,再通过Ubuntu的Shell跑命令。国内手机厂商虽然会对Android系统进行定制,但是命名和包名都会和原生尽可能保持一致的。

那么我们在IDE中搜索Launcher,看看这个Activity是什么。结果如下:

这里摘选的是Launcher2的Launcher进行分析,虽然新版本Android已经使用Launcher3作为桌面App了,但是我进入源码看了看发现核心的逻辑是没有变化的,所以选取了代码更短的Launcher2的Launcher进行分析。

点击应用图标后Android系统执行了什么操作

既然Launcher是Activity,那就意味着我们点击桌面的事件可以表达为:

呈现Android桌面视图(View)->点击View上某个应用图标->产生点击事件->点击事件被响应->通知Android系统的某个/某些进程->Android系统执行某些操作->启动App

Launcher如何响应由我们产生的点击事件

/**
 * Launches the intent referred by the clicked shortcut.
 *
 * @param v The view representing the clicked shortcut.
 */
public void onClick(View v) {
    // Make sure that rogue clicks don't get through while allapps is launching, or after the
    // view has detached (it's possible for this to happen if the view is removed mid touch).
    if (v.getWindowToken() == null) {
        return;
    }

    if (!mWorkspace.isFinishedSwitchingState()) {
        return;
    }

    Object tag = v.getTag();
    if (tag instanceof ShortcutInfo) {
        // Open shortcut
        final Intent intent = ((ShortcutInfo) tag).intent;
        int[] pos = new int[2];
        v.getLocationOnScreen(pos);
        intent.setSourceBounds(new Rect(pos[0], pos[1],
                pos[0] + v.getWidth(), pos[1] + v.getHeight()));

        boolean success = startActivitySafely(v, intent, tag);

        if (success && v instanceof BubbleTextView) {
            mWaitingForResume = (BubbleTextView) v;
            mWaitingForResume.setStayPressed(true);
        }
    } else if (tag instanceof FolderInfo) {
        if (v instanceof FolderIcon) {
            FolderIcon fi = (FolderIcon) v;
            handleFolderClick(fi);
        }
    } else if (v == mAllAppsButton) {
        if (isAllAppsVisible()) {
            showWorkspace(true);
        } else {
            onClickAllAppsButton(v);
        }
    }
}
boolean startActivitySafely(View v, Intent intent, Object tag) {
    boolean success = false;
    try {
        success = startActivity(v, intent, tag);
    } catch (ActivityNotFoundException e) {
        Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
        Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
    }
    return success;
}

从代码来看,产生点击事件后,如果产生点击事件的View的Tag是ShortcutInfo(即启动应用的快捷方式),就会取得ShortcutInfo中保存的Intent(这个Intent指向我们要启动的App),然后执行startActivitySafely(v, intent, tag)方法,而startActivitySafely方法只是对startActivity方法的简单封装。

所以,Launcher响应我们产生的点击事件后,实际上就是启动一个新的Activity。

我们现在回想下App开发时,每个App都需要有一个“MainActivity”,这个Activity必须在AndroidManifest.xml文件中有以下配置:

<intent-filter>    
    <action android:name="android.intent.action.MAIN" />    
    <category android:name="android.intent.category.LAUNCHER" />    
</intent-filter>  

在配置AndroidManifest.xml文件时,将Activity的Action指定为android.intent.action.MAIN,会使Activity在一个新的Task中启动(Task是一个Activity栈)。将category指定为android.intent.category.LAUNCHER,表示通过Intent启动此Activity时,只接受category为LAUNCHER的Intent。

所以,Launcher将会通过App的快捷方式(ShortcutInfo)得到应用的Intent,并通过这个Intent启动应用的“MainActivity”,从而启动应用。

所以我们研究的问题就从“App启动流程”变为“Activity启动流程”。

Launcher通过Binder通知ActivityManagerService启动Activity

现在我们就进入Launcher的startActivity方法里面探索“Activity启动流程”吧:

boolean startActivity(View v, Intent intent, Object tag) {
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    try {
        // Only launch using the new animation if the shortcut has not opted out (this is a
        // private contract between launcher and may be ignored in the future).
        boolean useLaunchAnimation = (v != null) &&
                !intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
        UserHandle user = (UserHandle) intent.getParcelableExtra(ApplicationInfo.EXTRA_PROFILE);
        LauncherApps launcherApps = (LauncherApps)
                this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
        if (useLaunchAnimation) {
            ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(v, 0, 0,
                    v.getMeasuredWidth(), v.getMeasuredHeight());
            if (user == null || user.equals(android.os.Process.myUserHandle())) {
                // Could be launching some bookkeeping activity
                startActivity(intent, opts.toBundle());
            } else {
                launcherApps.startMainActivity(intent.getComponent(), user,
                        intent.getSourceBounds(),
                        opts.toBundle());
            }
        } else {
            if (user == null || user.equals(android.os.Process.myUserHandle())) {
                startActivity(intent);
            } else {
                launcherApps.startMainActivity(intent.getComponent(), user,
                        intent.getSourceBounds(), null);
            }
        }
        return true;
    } catch (SecurityException e) {
        Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
        Log.e(TAG, "Launcher does not have the permission to launch " + intent +
                ". Make sure to create a MAIN intent-filter for the corresponding activity " +
                "or use the exported attribute for this activity. "
                + "tag="+ tag + " intent=" + intent, e);
    }
    return false;
}

在这个方法中,首先,将Intent的Flag设为Intent.FLAG_ACTIVITY_NEW_TASK,使得Android系统将创建一个新的Task来放置即将被打开的新Activity(应用的“MainActivity)。然后获取一个布尔值以用于后续判断是否显示启动App的动画。

然后获取Intent中是否传输了Parcelable格式的用户句柄,并通过Context.LAUNCHER_APPS_SERVICE获取用于在多用户情境下启动App的系统服务。

不管是否显示启动App的动画,最终都会执行startActivity(intent)launcherApps.startMainActivity方法以启动应用的“MainActivity”。而launcherApps.startMainActivity只在用户句柄不为空且用户句柄不等于当前进程句柄时(其他用户的句柄)调用。

为什么用户句柄会影响Activity的启动方式

这一点和Android的多用户安全机制有关。

假设我们有用户A和用户B在使用同一台手机,用户A是无法访问到用户B的文件或者和用户B的App通信的。所以假如我们现在是用户A,但我们想启动用户B的App,是无法直接实现的,因为用户A没有权限访问到用户B的数据,即使我们在代码中强行把user id设为用户B的user id,交给内核执行时也会抛出SecurityException。因此我们需要取得用户A的句柄(和用户A相关的数据),将我们想启动的用户B的App的Intent、用户A的句柄交给内核,让拥有权限的Android系统服务(内核态进程)去访问用户B的数据并执行相关的操作。

假如是单用户情境,就会相对简单了。因为此时只有一个用户,而该用户始终有权限直接访问自己的数据。

startActivity(intent)如何启动Activity

进入Activity类后层层深入就可以看到最终调用的是startActivityForResult方法:

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
    if (mParent == null) {
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            // If this start is requesting a result, we can avoid making
            // the activity visible until the result is received.  Setting
            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
            // activity hidden during this time, to avoid flickering.
            // This can only be done when a result is requested because
            // that guarantees we will get information back when the
            // activity is finished, no matter what happens to it.
            mStartedActivity = true;
        }

        cancelInputsAndStartExitTransition(options);
        // TODO Consider clearing/flushing other event sources and events for child windows.
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}

从代码上看,如果Launcher有mParent Activity,就会执行mParent.startActivityFromChild;如果没有,就会执行mInstrumentation.execStartActivity。进入mParent.startActivityFromChild方法会看到最终也是执行了mInstrumentation.execStartActivity。执行完成后,会取得一个ActivityResult对象,用于给调用者Activity传递一些数据,最后在Activity切换时显示Transition动画。

这里有一点需要指出的是:这里的ParentActivity指的是类似TabActivity、ActivityGroup关系的嵌套Activity。之所以要强调parent和child,是要避免混乱的Activity嵌套关系。

我们进入Instrumentation类看看execStartActivity方法吧:

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    Uri referrer = target != null ? target.onProvideReferrer() : null;
    if (referrer != null) {
        intent.putExtra(Intent.EXTRA_REFERRER, referrer);
    }
    if (mActivityMonitors != null) {
        synchronized (mSync) {
            final int N = mActivityMonitors.size();
            for (int i=0; i<N; i++) {
                final ActivityMonitor am = mActivityMonitors.get(i);
                if (am.match(who, null, intent)) {
                    am.mHits++;
                    if (am.isBlocking()) {
                        return requestCode >= 0 ? am.getResult() : null;
                    }
                    break;
                }
            }
        }
    }
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess();
        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);
    }
    return null;
}

首先,我们通过参数IBinder contextThread取得一个IApplicationThread类型的对象whoThread,而contextThread是由mMainThread.getApplicationThread()取得的ApplicationThread对象,此时mMainThread指的就是Launcher应用的主线程,所以whoThread指代的自然是Launcher的ApplicationThread。

因为Activity的onProvideReferrer()方法默认返回null,除非该方法被重写,而我们使用的Launcher并没有重写该方法,所以不用管referrer。

然后判断是否有ActivityMonitor,如果有,则即将要打开的Activity是否和ActivityMonitor中保存的IntentFilter匹配,如果匹配则增加ActivityMonitor的计数。大致是用于监控符合匹配规则的Activity的数量的。

最后调用ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);启动Activity,并检查启动是否成功。换句话说,最终负责启动Activity的是ActivityManager,前面得到的ApplicationThread也是在这里使用的。

那么ActivityManager、ApplicationThread、ActivityThread都是什么呢?

ActivityManagerService通过Binder将Launcher切换到pause状态

首先,调用ActivityManagerNative.getDefault()方法实际调用的是asInterface(IBinder obj)方法,也就意味着我们使用的其实是ActivityManagerProxy,而ActivityManagerProxy则是ActivityManagerService的代理,详见下面的代码:

static public IActivityManager getDefault() {
        return gDefault.get();
    }

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        IBinder b = ServiceManager.getService("activity");
        if (false) {
            Log.v("ActivityManager", "default service binder = " + b);
        }
        IActivityManager am = asInterface(b);
        if (false) {
            Log.v("ActivityManager", "default service = " + am);
        }
        return am;
    }
};

static public IActivityManager asInterface(IBinder obj) {
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值