美文网首页Android知识Android开发Android开发
从启动activity开始分析插件化

从启动activity开始分析插件化

作者: super_shanks | 来源:发表于2017-03-21 16:30 被阅读551次

    我想在最开始再废一些周章,来把启动的过程略微的阐述一下,为的就是接下去描述插件化痛点的时候,你会更加的身临其境。

    activity的启动过程如下:

    • Launcher是系统的apk,点击界面图标会调用相应的Activity.startActivity,或者是直接从ActivityA启动ActivityB
    • contextImpl.startActivity()->Instrumentation.executeActivity()
    • ActivityManagerNative.getDefault().startActivity()->AMS.startActivity
      Instrumentation通过aidl进行跨进程通信,最终调用AMS的startActivity方法
    • ActivityStarter.startActivityMayWait()->startActivityLocked()->startActivityUnchecked()
    • ActivityStack.resumeFocusedStackTopActivityLocked()->resumeTopActivityUncheckedLocked()->resumeTopActivityInnerLocked()
    • ActivityStackSupervisor.startSpecificActivityLocked()->realStartActivityLocked()(如果应用还没有启动那么走mService.startProcessLocked方法)
    • app.thread.scheduleLaunchActivity()-> sendMessage(H.LAUNCH_ACTIVITY, r)
    • H.handleMessage()->handleLaunchActivity()->performLaunchActivity()

    如果是初次启动应用,那么我们接着上面的mService.startProcessLocked去看,直接请求Zygote给启动应用fork一个进程出来

    • startProcessLocked()(经过几次重载跳转)->
    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.startViaZygote() 请求Zygote进程为其生成新进程

    • 在startViaZygote方法中设置完各种参数之后,最终调用zygoteSendArgsAndGetResult方法,向Zygote发送创建进程的请求。
      内部使用socket通信
    zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote)
    
    • Zygote内部的不断循环方法runSelectLoop会接受到这个请求
    if (i == 0) {
                        ZygoteConnection newPeer = acceptCommandPeer(abiList);
                        peers.add(newPeer);
                        fds.add(newPeer.getFileDesciptor());
                    } else {
                        boolean done = peers.get(i).runOnce();
                        if (done) {
                            peers.remove(i);
                            fds.remove(i);
                        }
                    }
    

    先进行包装成ZygoteConnection对象,并将socket的文件标识做保存。然后在下一次的循环中去执行runOnce

    • 在runOnce方法中
                pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,parsedArgs.appDataDir);
    
    通过这个方法去创建进程,实际上就是fork出一个新的虚拟机实例。
    
    • 如果fork成功了,那么返回的pid==0
    if (pid == 0) {
                    // in child
                    IoUtils.closeQuietly(serverPipeFd);
                    serverPipeFd = null;
                    handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
    
                    // should never get here, the child is expected to either
                    // throw ZygoteInit.MethodAndArgsCaller or exec().
                    return true;
                } else {
                    // in parent...pid of < 0 means failure
                    IoUtils.closeQuietly(childPipeFd);
                    childPipeFd = null;
                    return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
                }
    
     我们接着去执行handleChildProc
    
    • 这里我们做的就是关闭socket通道,并且启动新的进程
    private void handleChildProc(Arguments parsedArgs,
                FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
                throws ZygoteInit.MethodAndArgsCaller {
            //关闭socket
            closeSocket();
            ZygoteInit.closeServerSocket();
    
            ...
    
            // End of the postFork event.
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            if (parsedArgs.invokeWith != null) {
                WrapperInit.execApplication(parsedArgs.invokeWith,
                        parsedArgs.niceName, parsedArgs.targetSdkVersion,
                        VMRuntime.getCurrentInstructionSet(),
                        pipeFd, parsedArgs.remainingArgs);
            } else {
                //基本走的都是这种启动方式,通过参数,去寻找目标类的main方法并执行
                RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                        parsedArgs.remainingArgs, null /* classLoader */);
            }
        }
    
    • zygoteInit
    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
                throws ZygoteInit.MethodAndArgsCaller {
            ...
            //初始化
            commonInit();
            //native的初始化
            nativeZygoteInit();
            //application的初始化
            applicationInit(targetSdkVersion, argv, classLoader);
        }
    
    主要的就是applicationInit方法,设置虚拟机的堆大小,和虚拟机的sdkversion
    
     VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
          VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
    

    并且通过反射的方式去触发目标类的main方法

     try {
                cl = Class.forName(className, true, classLoader);
            } catch (ClassNotFoundException ex) {
                throw new RuntimeException(
                        "Missing class when invoking static main " + className,
                        ex);
            }
    
            Method m;
            try {
                m = cl.getMethod("main", new Class[] { String[].class });
            } catch (NoSuchMethodException ex) {
                throw new RuntimeException(
                        "Missing static main on " + className, ex);
            } catch (SecurityException ex) {
                throw new RuntimeException(
                        "Problem getting static main on " + className, ex);
            }
    
     最终触发,ActivityThread的main方法
    

    对于activity启动的总结

    • activityA启动ActivityB

      由contextImp发起的startActivity跳转到instrumentation的executeStartActivity然后交由ActivityStarter做启动准备,分别又经过ActivityStack栈,观察是否有栈顶Activity可以直接用来resume,最终交由ActivityStackSupervisor来realStartActivityLocked,然后通过applicationThread的scheduleLaunchActivity方法发送消息给H的handler,经过处理执行handleLaunchActivity到performLaunchActivity

    • Launcher点击图标启动应用

      依然有Activity.startActivity启动应用,直到ActivityStackSupervisor的startSpecificActivityLocked通过启动service的startProcessLocked,这个是AMS的方法,通过Socket通道,请求Zygote系统进程为目标应用分配VM虚拟机。分配成功之后关闭通道,并且通过反射的方式执行目标类的main方法,也就是ActivityThread的main方法。


    走进插件化

    讲在前面

    我们前面废了相当多的周章,来把启动的过程详细的阐述了一下(其实也没有很详细),为的就是接下去阐述痛点的时候,你会更加的身临其境。

    本文是基于android7.1的最新源码分析,你会发现google在不断的修改并强化activity的启动过程,它会想方设法的去阻止你进行插件化修改,因为插件化的方式从某些层面来讲,并不利于android的生态发展。所以你会发现这篇插件化讲解中的启动的源码是这样的,而到了那篇就变了个样,实际以最新版本源码为主。

    首先,对于插件化,你需要先了解它的模式

    插件化干什么

    比如说我们的主app相当的巨大,里面集成了大量的模块,我们的app可能有300+mb,那么这个时候客户可能就很不愿意去下载您的app。

    我们需要把我们app的主干功能保存下来,放在主app当中,然后将其余的模块功能做成插件的方式,在主app需要使用到的时候再去调用。

    这样我们可以做到

    • 并行开发,快速迭代
    • 模块间解耦
    • 按需加载,节省内存
    • 动态升级,我们只需要更新插件就可以做到功能更新,而无需更新整个app

    碰到的坎

    那么很明显了,好处相当多,我们开始准备着手去做了,我们搞了两个apk,一个是主apk,一个是插件apk。我们琢磨着在主apk中去启动插件apk中的activity。但是发现行不通。报了一个平时开发中也会遇到的exception,当你没有将你需要启动的activity在manifest文件中注册的时候就会出现的错误。

    Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {org.ding.testmulti/org.ding.testmulti.JustTest}; have you declared this activity in your AndroidManifest.xml?
    

    怎么办?我们先通过错误提示定位到出问题的所在地

    at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1794)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1512)
    

    前往execStartActivity方法,问题就出在下面这段代码

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

    也就是说跨进程访问的AMS的startActivity返回的result是错误的result,我们经过一番定位最终发现问题出在ActivityStarter的startActivityMayWait方法中,

    ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
    

    返回的是空,

     ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags) {
            try {
                return AppGlobals.getPackageManager().resolveIntent(intent, resolvedType,
                        PackageManager.MATCH_DEFAULT_ONLY | flags
                        | ActivityManagerService.STOCK_PM_FLAGS, userId);
            } catch (RemoteException e) {
            }
            return null;
        }
    

    最终我们走的PMS的resolveIntent,如果你看过我的应用安装分析的话(Android app安装过程分析(基于Nougat),你应该会了解,在应用安装的过程中,PMS会将Manifest文件中的内容依次读出,并保存在packageInfo当中,以供使用

    最终在PMS.java文件中找到resolveIntent方法

    @Override
        public ResolveInfo resolveIntent(Intent intent, String resolvedType,
                int flags, int userId) {
            try {
                ...
                final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType,
                        flags, userId);
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
    
                final ResolveInfo bestChoice =
                        chooseBestActivity(intent, resolvedType, flags, query, userId);
                return bestChoice;
            } finally {
                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
            }
        }
    

    也就是说queryIntentActivitiesInternal方法返回的列表为空,并没有找到与intent匹配的activity。

    当前流行的解决方案

    当前失眠流行的插件化代理解决方案有很多,但是基本的原理无外乎如下两类。分别是hookInstrumentation和hookActivityManagerNative,此处的hook我们可以把它理解成狸猫换太子。就是利用我们自己伪造的来骗过系统,从而达到插件化的效果。

    hookInstrumention

    市面上使用hookInstrumentation方法的是Small框架,在它的ApkBundleLauncher类中的onCreate方法:

    
    @Override
        public void onCreate(Application app) {
            super.onCreate(app);
            Object/*ActivityThread*/ thread;
            List<ProviderInfo> providers;
            Instrumentation base;
            ApkBundleLauncher.InstrumentationWrapper wrapper;
            Field f;
            // Get activity thread
            thread = ReflectAccelerator.getActivityThread(app);
    
            // Replace instrumentation
            try {
                //拿到当前thread的Instrumentation对象
                //我们主要大费周章的把Instrumentation这个对象取出来是为了对其进行保存,要注意,只要是hook了对象,那么除非真的没有必要
                //一般都需要对这个对象进行缓存,以防不时之需
                f = thread.getClass().getDeclaredField("mInstrumentation");
                f.setAccessible(true);
                base = (Instrumentation) f.get(thread);
                wrapper = new ApkBundleLauncher.InstrumentationWrapper(base);
                f.set(thread, wrapper);
            } catch (Exception e) {
                throw new RuntimeException("Failed to replace instrumentation for thread: " + thread);
            }
    
            ...
        }
    

    我们在InstrumentationWrapper中看到它重写了execStartActivity方法,来进行替换

    
             /** @Override V21+
             * Wrap activity from REAL to STUB */
            public ActivityResult execStartActivity(
                    Context who, IBinder contextThread, IBinder token, Activity target,
                    Intent intent, int requestCode, android.os.Bundle options) {
                wrapIntent(intent);
                return ReflectAccelerator.execStartActivity(mBase,
                        who, contextThread, token, target, intent, requestCode, options);
            }
    
            /** @Override V20-
             * Wrap activity from REAL to STUB */
            public ActivityResult execStartActivity(
                    Context who, IBinder contextThread, IBinder token, Activity target,
                    Intent intent, int requestCode) {
                wrapIntent(intent);
                return ReflectAccelerator.execStartActivity(mBase,
                        who, contextThread, token, target, intent, requestCode);
            }
    

    wrapIntent(intent)方法包装intent,然后对activity进行替换,将realActivity替换成我们提前在Manifest中注册好的Activity。
    在Small的最新版本中,将Activity再替换回来是放在Hook的Handler.CallBack中的(之前是直接走的mInstrumentation的newActivity来进行Activity的复原)

    // Inject message handler
            try {
                f = thread.getClass().getDeclaredField("mH");
                f.setAccessible(true);
                Handler ah = (Handler) f.get(thread);
                f = Handler.class.getDeclaredField("mCallback");
                f.setAccessible(true);
                f.set(ah, new ApkBundleLauncher.ActivityThreadHandlerCallback());
            } catch (Exception e) {
                throw new RuntimeException("Failed to replace message handler for thread: " + thread);
            }
    
    
     @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case LAUNCH_ACTIVITY:
                        //在handler收到LAUNCH_ACTIVITY的信号时进行进行替换
                        redirectActivity(msg);
                        break;
    
                    case CREATE_SERVICE:
                        ensureServiceClassesLoadable(msg);
                        break;
    
                    default:
                        break;
                }
    
                return false;
            }
    

    HookActivityManagerNative

    而DroidPlugin使用的是将ActivityManagerNative整个的Hook掉,具体我们见ProxyHool类

    public abstract class ProxyHook extends Hook implements InvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
            try {
                if (!isEnable()) {
                    return method.invoke(mOldObj, args);
                }
                HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
                if (hookedMethodHandler != null) {
                    return hookedMethodHandler.doHookInner(mOldObj, method, args);
                }
                return method.invoke(mOldObj, args);
            }
    
            ......
    }
    

    拿出具体的HookedMethodHandler,然后调用其doHookInner方法

    public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
            long b = System.currentTimeMillis();
            try {
                mUseFakedResult = false;
                mFakedResult = null;
                boolean suc = beforeInvoke(receiver, method, args);
                Object invokeResult = null;
                if (!suc) {
                    invokeResult = method.invoke(receiver, args);
                }
                afterInvoke(receiver, method, args, invokeResult);
                if (mUseFakedResult) {
                    return mFakedResult;
                } else {
                    return invokeResult;
                }
            } finally {
                long time = System.currentTimeMillis() - b;
                if (time > 5) {
                    Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
                }
            }
        }
    

    通过AOP的方式分别在其方法前后进行hook
    我们此处替换掉的ActivityManagerNative,就是在IActivityManagerHookHandle中的startActivity类然后我们看他的beforeInvoke方法

    @Override
    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
    
        RunningActivities.beforeStartActivity();
        boolean bRet;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
            bRet = doReplaceIntentForStartActivityAPILow(args);
        } else {
            bRet = doReplaceIntentForStartActivityAPIHigh(args);
        }
        if (!bRet) {
            setFakedResult(Activity.RESULT_CANCELED);
            return true;
        }
    
        return super.beforeInvoke(receiver, method, args);
    }
    

    doReplaceIntentForStartActivityAPIHigh这个方法也是将真Activity信息保存,然后用stubActivity来进行替换。

    至于再换回来也是用的callBack,实际上DroidPlugin要比Small要更早的使用Hook callBack的方式
    PluginCallbackHook.java

    @Override
        protected void onInstall(ClassLoader classLoader) throws Throwable {
            Object target = ActivityThreadCompat.currentActivityThread();
            Class ActivityThreadClass = ActivityThreadCompat.activityThreadClass();
    
            /*替换ActivityThread.mH.mCallback,拦截组件调度消息*/
            Field mHField = FieldUtils.getField(ActivityThreadClass, "mH");
            Handler handler = (Handler) FieldUtils.readField(mHField, target);
            Field mCallbackField = FieldUtils.getField(Handler.class, "mCallback");
            //*这里读取出旧的callback并处理*/
            Object mCallback = FieldUtils.readField(mCallbackField, handler);
            if (!PluginCallback.class.isInstance(mCallback)) {
                PluginCallback value = mCallback != null ? new PluginCallback(mHostContext, handler, (Handler.Callback) mCallback) : new PluginCallback(mHostContext, handler, null);
                value.setEnable(isEnable());
                mCallbacks.add(value);
                FieldUtils.writeField(mCallbackField, handler, value);
                Log.i(TAG, "PluginCallbackHook has installed");
            } else {
                Log.i(TAG, "PluginCallbackHook has installed,skip");
            }
        }
    

    PluginCallback.java

    private boolean handleLaunchActivity(Message msg) {
            try {
                Object obj = msg.obj;
                Intent stubIntent = (Intent) FieldUtils.readField(obj, "intent");
                //ActivityInfo activityInfo = (ActivityInfo) FieldUtils.readField(obj, "activityInfo", true);
                stubIntent.setExtrasClassLoader(mHostContext.getClassLoader());
                Intent targetIntent = stubIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
                // 这里多加一个isNotShortcutProxyActivity的判断,因为ShortcutProxyActivity的很特殊,启动它的时候,
                // 也会带上一个EXTRA_TARGET_INTENT的数据,就会导致这里误以为是启动插件Activity,所以这里要先做一个判断。
                // 之前ShortcutProxyActivity错误复用了key,但是为了兼容,所以这里就先这么判断吧。
                if (targetIntent != null && !isShortcutProxyActivity(stubIntent)) {
                    IPackageManagerHook.fixContextPackageManager(mHostContext);
                    ComponentName targetComponentName = targetIntent.resolveActivity(mHostContext.getPackageManager());
                    ActivityInfo targetActivityInfo = PluginManager.getInstance().getActivityInfo(targetComponentName, 0);
                    if (targetActivityInfo != null) {
    
                        if (targetComponentName != null && targetComponentName.getClassName().startsWith(".")) {
                            targetIntent.setClassName(targetComponentName.getPackageName(), targetComponentName.getPackageName() + targetComponentName.getClassName());
                        }
    
                        ResolveInfo resolveInfo = mHostContext.getPackageManager().resolveActivity(stubIntent, 0);
                        ActivityInfo stubActivityInfo = resolveInfo != null ? resolveInfo.activityInfo : null;
                        if (stubActivityInfo != null) {
                            PluginManager.getInstance().reportMyProcessName(stubActivityInfo.processName, targetActivityInfo.processName, targetActivityInfo.packageName);
                        }
                        PluginProcessManager.preLoadApk(mHostContext, targetActivityInfo);
                        ClassLoader pluginClassLoader = PluginProcessManager.getPluginClassLoader(targetComponentName.getPackageName());
                        setIntentClassLoader(targetIntent, pluginClassLoader);
                        setIntentClassLoader(stubIntent, pluginClassLoader);
    
                        ...
    
                        Log.i(TAG, "handleLaunchActivity OK");
                    } else {
                        Log.e(TAG, "handleLaunchActivity oldInfo==null");
                    }
                } else {
                    Log.e(TAG, "handleLaunchActivity targetIntent==null");
                }
            } catch (Exception e) {
                Log.e(TAG, "handleLaunchActivity FAIL", e);
            }
    
            if (mCallback != null) {
                return mCallback.handleMessage(msg);
            } else {
                return false;
            }
        }
    

    至此两种最经典的插件化方式分析完毕,之后我们再来看框架的具体使用心得

    TBC。。

    相关文章

      网友评论

        本文标题:从启动activity开始分析插件化

        本文链接:https://www.haomeiwen.com/subject/crncnttx.html