流程简介
启动一个Activity可能是我们在Android编程中最长用的功能之一了,方式也很简单,就是调用Context类的startActivity方法。可能有些开发者误认为startActivity就是通过调用另一个Activity子类的初始化方法来唤起这个Activity的,其实不然。
当你在使用startActivity时,这个调用会发送给属于系统一部分的ActivityManager一个信息。由ActivityManager来创建该Activity实例并且调用它的onCreate方法。流程如下图:
有人可能会问,那系统如何知道启动哪一个Activity的呢?答案是通过Intent这个组件,以下是摘自Android源码的两个最基本的Intent构造函数:
public Intent(Context packageContext, Class<?> cls) {
mComponent = new ComponentName(packageContext, cls);
}
public Intent(String action) {
setAction(action);
}
第一个构造函数中,ActivityManager可以获得包名,和具体的类名,进而启动这个Activity。第二个构造函数中,ActivityManager还可以通过一个叫action的字符串来找到对应的Activity来启动。我们可以感受出来,通过此种架构,可以让Activity的启动具有更大的灵活性,也更加松耦合。既然ActivityManager是系统一部分,那在每个应用安装时,也就需要告诉系统自己哪些部分是可以被这种方式启动的,此时我们就有了AndroidManifest文件,所以才有了我们需要在AndroidManifest文件中声明Activity这个组件的做法,否则就会报ActivityNotFoundException这个异常。
显示和隐式启动
理解了上面的流程,我们再来看下启动的两种方式就很简单了,一个叫显示启动,一个叫隐式启动。我们来举两个显示启动的例子
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
或者
ComponentName componentName = new ComponentName(this, SecondActivity.class);
// 或者 new ComponentName(this, "com.example.SecondActivity");
// 或者 new ComponentName(this.getPackageName(), "com.example.SecondActivity");
Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);
第一种我想大家再熟悉不过了,第二种在了解启动流程后耶比较容易理解,如果把第二种方法的第一个参数换成其他App的包名,还可以直接启动其它App。
我们再来介绍下隐式启动,在介绍之前,我们需要了解一个标签<intent-filter>
,从字面上来看,很好理解,就是用来过滤掉一些intent。让只有具备处理特定intent的Activity才会被启动。比如在AndroidManifest中有如下声明:
<activity
android:name="com.example.SecondActivity">
<intent-filter>
<action android:name="12345"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
那么启动该Activity除了以上介绍的显示方法外,我们还可以用以下方法
Intent intent = new Intent("12345");
intent.addCategory("android.intent.category.DEFAULT"); //可以不加,默认被加上
startActivity(intent);
隐式启动是由action、category和data共同决定的,只有三者都满足,该Activity才会被列入可处理列表,一般intent-filter包含一个action,可以包含多个category和data,所有category和某一个data满足才行。如果你手机上多个应用同时拥有相同的intent-filter,那么就会出现一个应用列表供用户选择。
注:Android对待所有隐式Intent,默认它们已经具有了"android.intent.category.DEFAULT",所以如果你想你的Activity被隐式调用,那一定需要在其intent-filter中加上该条category。不过入口Activity被"android.intent.action.MAIN" 和"android.intent.category.LAUNCHER"标记的除外,虽然你也可以加上DEFAULT的category,但不是必须的
应用
学习了以上几点后,我们来应用一下,做一个在不接入微信SDK的情况下的微信分享。你可以用apktool反编译微信,然后通过看smali代码和AndroidManifest文件,了解它规则后就能知道如何调用它的分享界面了。这里反编译方法先不介绍了。经过分析,我们找到ShareImgUI
类极为微信分享的朋友列表页面,我们来看下它的AndroidManifest.xml中内容
<activity android:configChanges="keyboardHidden|orientation|screenSize" android:icon="@drawable/icon" android:name="com.tencent.mm.ui.tools.ShareImgUI">
<intent-filter android:label="@string/c">
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
<data android:mimeType="video/*"/>
<data android:mimeType="text/*"/>
<data android:mimeType="application/*"/>
</intent-filter>
<intent-filter android:label="@string/c">
<action android:name="android.intent.action.SEND_MULTIPLE"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
根据以上分析,对应分享代码如下
Intent it = new Intent(Intent.ACTION_SEND);
it.setComponent(new ComponentName("com.tencent.mm","com.tencent.mm.ui.tools.ShareImgUI"));
it.putExtra(Intent.EXTRA_TEXT, "分享成功");
it.setType("text/plain"); //可以不要
mActivity.startActivity(Intent.createChooser(it, "本机未安装微信"));
相信上面的代码大家很容易就能看明白,不过有人可能会迷惑,我们已经用显示启动了,为什么还要用action呢,这是因为微信在代码里判断了action,如果你不传入这个action就无法分享。另外代码的最后一行我们为了避免微信未安装产生ActivityNotFoundException异常,用了Intent.createChooser来处理,如果本机未安装微信,在执行上述代码时,会出现一个标题为“本机未安装微信”的弹窗。根据同理,分享图片、视频、应用到微信是可以做到的,但你需要具体分析下ShareImgUI的smali代码,让自己传入的数据可以被微信接受就行。当然分享到朋友圈也一样道理。
写在后面
虽然上面分析了Activity启动流程,但该流程只是简化版,实际的启动流程要复杂很多,涉及到很多类。虽是简化版,但对我们理解有关Activity间跳转的代码足矣。后续也为大家能继续深入源码来了解这些流程提供了一点指导。
作者简介
彭涛(@彭涛me) 致力于让技术变得易懂且有趣
个人博客:http://pengtao.me, GitHub地址:https://github.com/CPPAlien