前言
Small的跳转流程只不过是利用占过的坑和Uri的转换欺骗Manifest的检测,达到启动Activity的效果,Small将启动的真正的Intent放在了Category中了,然后用假的Intent通过验证后,再取出Intent的Category真正的Intent。不过跳转过程会利用到启动流程中存储的bundle信息、bundleLauncher信息、和ActivityBundleLauncher存储的宿主Activity,这里需要注意的是bundle的变量和类型理解,在前面也说有三种类型,web、lib、app,由于不同的类型会导致bundle存储的变量和类型都是有差别的,这样就导致启动的时候,执行不同的BundleLauncher的启动和跳转逻辑
跳转流程
由于app和lib的启动器都是 ApkBundleLauncher ,所以这里以 ApkBundleLauncher 为例进行说明
├── Small.openUri()
├── Bundle.getLaunchableBundle(uri)
│ └── Bundle.matchesRule()
└── Bundle.launchFrom(context)
└──ApkBundleLauncher.launchBundle()
├── ApkBundleLauncher.prelaunchBundle()
│ └── Bundle.getActivityName()
└── BundleLauncher.launchBundle()
└── Activity.startActivityForResult()
└── InstrumentationWrapper.execStartActivity()
├── InstrumentationWrapper.wrapIntent()
└── InstrumentationWrapper.dequeueStubActivity
└── InstrumentationWrapper.callActivityOnCreate
└── InstrumentationWrapper.callActivityOnStop
└── InstrumentationWrapper.callActivityOnDestroy
└── InstrumentationWrapper.onException
----------------------------------消息处理----------------------------------
├── ActivityThreadHandlerCallback.LAUNCH_ACTIVITY
└── redirectActivity()
├── ActivityThreadHandlerCallback.CREATE_SERVICE
└── ensureServiceClassesLoadable()
源码分析
由于启动流程的源码比较简单,只是拿启动流程保存好的一些插件的变量进行使用,可以按照代码注释的步骤阅读
一、Small.openUri()
我们以 Small.openUri(“main”, LaunchActivity.this); 为例子,讲述它整个跳转流程
public static boolean openUri(String uriString, Context context) {
return openUri(makeUri(uriString), context);
}
//1、这里的makeUri会将"main"返回成Uri形式,http://code.wequick.net/small-sample/main
private static Uri makeUri(String uriString) {
if (!uriString.startsWith("http://")
&& !uriString.startsWith("https://")
&& !uriString.startsWith("file://")) {
uriString = sBaseUri + uriString;
}
return Uri.parse(uriString);
}
public static boolean openUri(Uri uri, Context context) {
//2、这里获取scheme = "http"
String scheme = uri.getScheme();
//3、检查是否为系统级别的Activity
if (scheme != null
&& !scheme.equals("http")
&& !scheme.equals("https")
&& !scheme.equals("file")
&& ApplicationUtils.canOpenUri(uri, context)) {
ApplicationUtils.openUri(uri, context);
return true;
}
// 4、通过当前的Uri和启动时候存储的所有插件Uri做对比,获取当前的插件
Bundle bundle = Bundle.getLaunchableBundle(uri);
if (bundle != null) {
//5、追踪这里
bundle.launchFrom(context);
return true;
}
return false;
}
protected static Bundle getLaunchableBundle(Uri uri) {
if (sPreloadBundles != null) {
for (Bundle bundle : sPreloadBundles) {
if (bundle.matchesRule(uri)) {
if (bundle.mApplicableLauncher == null) {
break;
}
if (!bundle.enabled) return null; // Illegal bundle (invalid signature, etc.)
return bundle;
}
}
}
// 由于是main插件,这里不会执行
// Downgrade to show webView
if (uri.getScheme() != null) {
Bundle bundle = new Bundle();
try {
bundle.url = new URL(uri.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
}
bundle.prepareForLaunch();
bundle.setQuery(uri.getEncodedQuery()); // Fix issue #6 from Spring-Xu.
bundle.mApplicableLauncher = new WebBundleLauncher();
bundle.mApplicableLauncher.prelaunchBundle(bundle);
return bundle;
}
return null;
}
protected void launchFrom(Context context) {
if (mApplicableLauncher != null) {
//6、由于插件保存的mApplicableLauncher是ApkBundleLauncher
//所以这里就会到ApkBundleLauncher的launchBundle()
mApplicableLauncher.launchBundle(this, context);
}
}
二、ApkBundleLauncher.launchBundle()
public void launchBundle(Bundle bundle, Context context) {
//7、先预加载
this.prelaunchBundle(bundle);
super.launchBundle(bundle, context);
}
@Override
publicvoid prelaunchBundle(Bundle bundle) {
super.prelaunchBundle(bundle);
Intent intent = new Intent();
bundle.setIntent(intent);
//8、获取该插件的入口Activity
String activityName = bundle.getActivityName();
//9、判断一下ActivityLauncher中的sActivityClasses是否包含该Activity,即判断是否为宿主app和Small框架里面的Activity
if (!ActivityLauncher.containsActivity(activityName)) {
//一般启动插件Activity的情况下面是会走到这里的
//sLoadedActivities 包含的是插件里面定义的Activity,在启动初始化时解析的
if (sLoadedActivities == null) {
throw new ActivityNotFoundException("Unable to find explicit activity class " +
"{ " + activityName + " }");
}
//这里表示之前启动流程的插件列表中不包括记录有当前插件的类名
if (!sLoadedActivities.containsKey(activityName)) {
if (activityName.endsWith("Activity")) {
throw new ActivityNotFoundException("Unable to find explicit activity class " +
"{ " + activityName + " }");
}
String tempActivityName = activityName + "Activity";
if (!sLoadedActivities.containsKey(tempActivityName)) {
throw new ActivityNotFoundException("Unable to find explicit activity class " +
"{ " + activityName + "(Activity) }");
}
activityName = tempActivityName;
}
}
//10、设置启动插件的ComponentName
intent.setComponent(new ComponentName(Small.getContext(), activityName));
//由于是main插件所以没有带query参数
String query = bundle.getQuery();
if (query != null) {
intent.putExtra(Small.KEY_QUERY, '?' + query);
}
}
public void launchBundle(Bundle bundle, Context context) {
if(bundle.isLaunchable()) {
if(context instanceof Activity) {
Activity activity = (Activity)context;
if(this.shouldFinishPreviousActivity(activity)) {
activity.finish();
}
//11、正常的启动插件
activity.startActivityForResult(bundle.getIntent(), 10000);
} else {
context.startActivity(bundle.getIntent());
}
}
}
你以为启动插件就那么容易就启动了吗?Manifest并没有注册插件的Activity,这个时候就到了启动拦截Activity的Intent的时候
三、InstrumentationWrapper
我们都知道在启动流程中,我们已经将四大组件和资源进行了占坑和merge了,现在就是到它们工作的时候到了
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, android.os.Bundle options) {
//12、将intent 中真正的Activity替换为占坑位的Activity
wrapIntent(intent);
ensureInjectMessageHandler(sActivityThread);
return ReflectAccelerator.execStartActivity(mBase,
who, contextThread, token, target, intent, requestCode, options);
}
//这里有API版本区别21以上和20以下中的execStartActivity方法参数多了一个
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode) {
wrapIntent(intent);
ensureInjectMessageHandler(sActivityThread);
return ReflectAccelerator.execStartActivity(mBase,
who, contextThread, token, target, intent, requestCode);
}
private void wrapIntent(Intent intent) {
// 此处为插件中注册的真正的Activity
ComponentName component = intent.getComponent();
String realClazz;
if (component == null) {
// 如果component为空,交给宿主来处理这个intent
component = intent.resolveActivity(Small.getContext().getPackageManager());
if (component != null) {
// 系统或者宿主处理掉了,直接返回
return;
}
// 如果Action没有处理掉,看一下插件注册的Activity能否处理
realClazz = resolveActivity(intent);
if (realClazz == null) {
// 如果插件也不能处理,就直接返回,无能为力了……
return;
}
} else {
// 13、取出需要跳转Class
realClazz = component.getClassName();
// 如果是 PACKAGE_NAME + ".A"
if (realClazz.startsWith(STUB_ACTIVITY_PREFIX)) {
// Re-wrap to ensure the launch mode works.
// 如果这个Activity已经是占坑位的Activity,进行解开回原来的Activity
realClazz = unwrapIntent(intent);
}
}
if (sLoadedActivities == null) return;
// 14、从插件列表中获得真正Activity的信息
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) return;
// Carry the real(plugin) class for incoming `newActivity' method.
// 15、把真实的Activity放到Category中并用'>'进行标识
intent.addCategory(REDIRECT_FLAG + realClazz);
// 16、获取一个占坑位的Activity
String stubClazz = dequeueStubActivity(ai, realClazz);
// 17、将真正需要启动的Activity替换为占坑位的Activity
intent.setComponent(new ComponentName(Small.getContext(), stubClazz));
}
private static String unwrapIntent(Intent intent) {
Set<String> categories = intent.getCategories();
if (categories == null) return null;
// Get plugin activity class name from categories
// 18、遍历所有Category,找到对应的标识符,后面接的就是真实的类名
Iterator<String> it = categories.iterator();
while (it.hasNext()) {
String category = it.next();
if (category.charAt(0) == REDIRECT_FLAG) {
return category.substring(1);
}
}
return null;
}
通过包装后的Intent就能欺骗过系统的检查,当我们返回的时候,就通过解包装Intent,然后在Category中获取真正的Activity,而unwrapIntent的调用时机在HandlerCallBack中
private static class ActivityThreadHandlerCallback implements Handler.Callback {
private static final int LAUNCH_ACTIVITY = 100;
private static final int CREATE_SERVICE = 114;
private static final int CONFIGURATION_CHANGED = 118;
private static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
private Configuration mApplicationConfig;
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY:
//偷天换日 Activity
redirectActivity(msg);
break;
case CREATE_SERVICE:
ensureServiceClassesLoadable(msg);
break;
case CONFIGURATION_CHANGED:
recordConfigChanges(msg);
break;
case ACTIVITY_CONFIGURATION_CHANGED:
return relaunchActivityIfNeeded(msg);
default:
break;
}
return false;
}
private void redirectActivity(Message msg) {
Object/*ActivityClientRecord*/ r = msg.obj;
Intent intent = ReflectAccelerator.getIntent(r);
//解包装Intent
String targetClass = unwrapIntent(intent);
boolean hasSetUp = Small.hasSetUp();
if (targetClass == null) {
// 在宿主中注册的Activity
if (hasSetUp) return; // nothing to do
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
// The launcher activity will setup Small.
// 带CATEGORY_LAUNCHER属性的Activity
return;
}
// Launching an activity in remote process. Set up Small for it.
Small.setUpOnDemand();
return;
}
if (!hasSetUp) {
// Restarting an activity after application recreated,
// maybe upgrading or somehow the application was killed in background.
Small.setUp();
}
// Replace with the REAL activityInfo
// 替换为真正的 activityInfo
ActivityInfo targetInfo = sLoadedActivities.get(targetClass);
ReflectAccelerator.setActivityInfo(r, targetInfo);
// Ensure the merged application-scope resource has been cached so that
// the incoming activity can attach to it without creating a new(unmerged) one.
ReflectAccelerator.ensureCacheResources();
}
}
InstrumentationWrapper不仅做了欺骗Mainfest的作用,还可以用到已经Hook的Instrumentation来回调Activity的生命周期
@Override
/** Prepare resources for REAL */
public void callActivityOnCreate(Activity activity, android.os.Bundle icicle) {
do {
if (sLoadedActivities == null) break;
ActivityInfo ai = sLoadedActivities.get(activity.getClass().getName());
if (ai == null) break;
//用来设置Activity的一些转屏和键盘状态
applyActivityInfo(activity, ai);
} while (false);
// Reset activity instrumentation if it was modified by some other applications #245
if (sBundleInstrumentation != null) {
try {
Field f = Activity.class.getDeclaredField("mInstrumentation");
f.setAccessible(true);
Object instrumentation = f.get(activity);
if (instrumentation != sBundleInstrumentation) {
f.set(activity, sBundleInstrumentation);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
//回调生命周期
sHostInstrumentation.callActivityOnCreate(activity, icicle);
}
@Override
public void callActivityOnSaveInstanceState(Activity activity, android.os.Bundle outState) {
sHostInstrumentation.callActivityOnSaveInstanceState(activity, outState);
if (mStubQueue != null) {
outState.putCharSequenceArray(STUB_QUEUE_RESTORE_KEY, mStubQueue);
}
}
@Override
public void callActivityOnRestoreInstanceState(Activity activity, android.os.Bundle savedInstanceState) {
sHostInstrumentation.callActivityOnRestoreInstanceState(activity, savedInstanceState);
if (mStubQueue == null) {
mStubQueue = savedInstanceState.getStringArray(STUB_QUEUE_RESTORE_KEY);
}
}
@Override
public void callActivityOnStop(Activity activity) {
//回调生命周期
sHostInstrumentation.callActivityOnStop(activity);
if (!Small.isUpgrading()) return;
// If is upgrading, we are going to kill self while application turn into background,
// and while we are back to foreground, all the things(code & layout) will be reload.
// Don't worry about the data missing in current activity, you can do all the backups
// with your activity's `onSaveInstanceState' and `onRestoreInstanceState'.
// Get all the processes of device (1)
ActivityManager am = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> processes = am.getRunningAppProcesses();
if (processes == null) return;
// Gather all the processes of current application (2)
// Above 5.1.1, this may be equals to (1), on the safe side, we also
// filter the processes with current package name.
String pkg = activity.getApplicationContext().getPackageName();
final List<RunningAppProcessInfo> currentAppProcesses = new ArrayList<>(processes.size());
for (RunningAppProcessInfo p : processes) {
if (p.pkgList == null) continue;
boolean match = false;
int N = p.pkgList.length;
for (int i = 0; i < N; i++) {
if (p.pkgList[i].equals(pkg)) {
match = true;
break;
}
}
if (!match) continue;
currentAppProcesses.add(p);
}
if (currentAppProcesses.isEmpty()) return;
// The top process of current application processes.
RunningAppProcessInfo currentProcess = currentAppProcesses.get(0);
if (currentProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) return;
// Seems should delay some time to ensure the activity can be successfully
// restarted after the application restart.
// FIXME: remove following thread if you find the better place to `killProcess'
new Thread() {
@Override
public void run() {
try {
sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (RunningAppProcessInfo p : currentAppProcesses) {
android.os.Process.killProcess(p.pid);
}
}
}.start();
}
@Override
public void callActivityOnDestroy(Activity activity) {
do {
if (sLoadedActivities == null) break;
String realClazz = activity.getClass().getName();
ActivityInfo ai = sLoadedActivities.get(realClazz);
if (ai == null) break;
inqueueStubActivity(ai, realClazz);
} while (false);
//回调生命周期
sHostInstrumentation.callActivityOnDestroy(activity);
}
Instrumentation还处理了Activity的报错,由于Small需要把插件包中的所有provider预先注册在宿主的AndroidManifest中,但是系统会在Application的onCreate生命周期执行之前根据注册的provider信息进行install,这样会导致类找不到而抛出异常,所以这里通过onException拦截当前的ClassNotFoundException并且判断如果是从方法installProvider中抛出的话我们进行捕获处理不抛出异常,把异常中的provider也就是在宿主中定义的其他插件包中的provider的类名存进数组,在后续加载完毕插件的dex和处理完classloader之后再进行install操作
@Override
public boolean onException(Object obj, Throwable e) {
if (e.getClass().equals(ClassNotFoundException.class)) {
if (sProviders == null) return super.onException(obj, e);
boolean errorOnInstallProvider = false;
StackTraceElement[] stacks = e.getStackTrace();
for (StackTraceElement st : stacks) {
if (st.getMethodName().equals("installProvider")) {
errorOnInstallProvider = true;
break;
}
}
if (errorOnInstallProvider) {
// We'll reinstall this content provider later, so just ignores it!!!
// FIXME: any better way to get the class name?
String msg = e.getMessage();
final String prefix = "Didn't find class \"";
if (msg.startsWith(prefix)) {
String providerClazz = msg.substring(prefix.length());
providerClazz = providerClazz.substring(0, providerClazz.indexOf("\""));
for (ProviderInfo info : sProviders) {
if (info.name.equals(providerClazz)) {
if (mLazyInitProviders == null) {
mLazyInitProviders = new ArrayList<ProviderInfo>();
}
mLazyInitProviders.add(info);
break;
}
}
}
return true;
}
} else if (HealthManager.fixException(obj, e)) {
return true;
}
return super.onException(obj, e);
}
结语
简单的来说,Small跳转流程分为下面几步
- 简单的类型和插件的检查然后获取启动类名
- 启动前把启动类名藏起来,找一个替身Intent来帮我
- 跳到坑中,替身欺骗了系统,完成跳转
- 回调生命周期,捕捉异常