进入目录视图点击置顶文章查看博客目录(全站式导航)

恪守本分,勿起躁念,脚踏实地,踏浪前行,坚持原创,宁缺勿滥,请点击文章左上角+号查看目录...

Android进阶——Small源码分析之跳转流程详解

前言

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跳转流程分为下面几步

  1. 简单的类型和插件的检查然后获取启动类名
  2. 启动前把启动类名藏起来,找一个替身Intent来帮我
  3. 跳到坑中,替身欺骗了系统,完成跳转
  4. 回调生命周期,捕捉异常
阅读更多

扫码向博主提问

去开通我的Chat快问

qq_30379689

博客专家

职业规划、职业发展、YY福利说
  • 擅长领域:
  • Android
  • 职业规划
  • 职业发展
  • 内推YY
  • 面试套路分享
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_30379689/article/details/79221413
个人分类: 今年大四了
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭