美文网首页Android 插件式Android技术知识Android开发
滴滴开源Android插件化框架VirtualAPK原理分析

滴滴开源Android插件化框架VirtualAPK原理分析

作者: Android高级开发 | 来源:发表于2019-01-25 22:35 被阅读33次

    概述

    滴滴出行公司的首个对外开源项目 - VirtualAPK。地址:https://github.com/didi/VirtualAPK

    滴滴自行研发了这款插件化框架,功能全面、兼容性好,还能够适用于有耦合的业务插件,这就是VirtualAPK存在的意义。业内认为,在加载耦合插件方面,VirtualAPK可以说是开源方案的首选。据说滴滴打车里面已经用上了,所以还是有必要一探究竟的~~

    VirtualAPK 的工作流程如图所示:

    image.png
    VirtualAPK 对于插件没有额外的约束,原生的 apk 即可作为一个插件。插件工程编译生成 apk 后,通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的 LoadedPlugin 对象。如上图所示,通过这些 LoadedPlugin 对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。

    Activity 支持

    Hook ActivityManagerService

    插件化支持首先要解决的一点就是插件里的Activity并未在宿主程序的 AndroidMainfest.xml 注册,常规方法肯定无法直接启动插件的Activity,这个时候就需要去了解Activity的启动流程。

    从上文中可知,Activity 启动实际上是调用了 Instrumentation.execStartActivity 这个方法。源码如下:

    public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,  
                Intent intent, int requestCode, Bundle options) {  
        IApplicationThread whoThread = (IApplicationThread) contextThread;  
        if (mActivityMonitors != null) {  
            synchronized (mSync) {  
                final int N = mActivityMonitors.size();  
                for (int i=0; i<N; i++) { //先查找一遍看是否存在这个activity  
                    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();  
            //这里才是真正打开activity的地方,其核心功能在whoThread中完成。  
            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); // 处理各种异常,如ActivityNotFound  
        } catch (RemoteException e) {  
        }  
        return null;  
    }
    

    可见, startActivity 最终通过 ActivityManagerNative.getDefault() 远程调用了AMS的startActivity方法, ActivityManagerNative 实际上就是 ActivityManagerService 这个远程对象的 Binder 代理对象,每次需要与AMS交互时,需要通过这个 Binder 对象完成远程IPC调用。

    
    // ActivityManagerNative.getDefault()
    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;
               }
    };
    

    从这我们可以知道,ActivityManagerNative.getDefault() 实际上是返回了一个 IActivityManager 的单例对象。

    那么,VirtualApk 所要做的第一件事,就是把这个 AMS 代理对象保存起来。首先,我们可以看一下 VirtualApk 核心库里面 com.didi.virtualapk.PluginManager 这个类的初始化:

    
    // 构造方法
    private PluginManager(Context context) {
        Context app = context.getApplicationContext();
        if (app == null) {
            this.mContext = context;
        } else {
            this.mContext = ((Application)app).getBaseContext();
        }
        prepare();
    }
    
    // 初始化
    private void prepare() {
        Systems.sHostContext = getHostContext();
        this.hookInstrumentationAndHandler();
        this.hookSystemServices();
    }
    
    /**
     * Hook 出一个IActivityManager,也就是 AMS 的代理对象
     */
    private void hookSystemServices() {
        try {
            // 反射调用 ActivityManagerNative.getDefault(),实际上这在6.0中是公开的静态方法,反射可能是考虑到版本兼容性吧?
            Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
            // 通过动态代理的方式去创建代理对象,之后所有ActivityManagerNative中的方法被调用的时候都会经过这个代理
            IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());
    
            // Hook IActivityManager from ActivityManagerNative,实际上就是把 ActivityManagerNative 替换为刚创建的 activityManagerProxy
            ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);
    
            if (defaultSingleton.get() == activityManagerProxy) {
                // 两者一样,保存下来
                this.mActivityManager = activityManagerProxy;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    实际上除了 startActivity 是调用 AMS 的方法以外,startServicebindService 等方法,最终调用到AMS的里的方法,这个我们在动态代理类 com.didi.virtualapk.delegate.ActivityManagerProxy 也可以找到:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("startService".equals(method.getName())) {
            try {
                // 执行自定义的 startService 过程,后面会提到
                return startService(proxy, method, args);
            } catch (Throwable e) {
                Log.e(TAG, "Start service error", e);
            }
        } else if ("stopService".equals(method.getName())) {
            try {
                return stopService(proxy, method, args);
            } catch (Throwable e) {
                Log.e(TAG, "Stop Service error", e);
            }
        } else if ("stopServiceToken".equals(method.getName())) {
            try {
                return stopServiceToken(proxy, method, args);
            } catch (Throwable e) {
                Log.e(TAG, "Stop service token error", e);
            }
        } else if ("bindService".equals(method.getName())) {
            try {
                return bindService(proxy, method, args);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        } else if ("unbindService".equals(method.getName())) {
            try {
                return unbindService(proxy, method, args);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        } else if ("getIntentSender".equals(method.getName())) {
            try {
                getIntentSender(method, args);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if ("overridePendingTransition".equals(method.getName())){
            try {
                overridePendingTransition(method, args);
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    
        try {
            // sometimes system binder has problems.
            return method.invoke(this.mActivityManager, args);
        } catch (Throwable th) {
            Throwable c = th.getCause();
            if (c != null && c instanceof DeadObjectException) {
                // retry connect to system binder
                IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                if (ams != null) {
                    IActivityManager am = ActivityManagerNative.asInterface(ams);
                    mActivityManager = am;
                }
            }
    
            Throwable cause = th;
            do {
                if (cause instanceof RemoteException) {
                    throw cause;
                }
            } while ((cause = cause.getCause()) != null);
    
            throw c != null ? c : th;
        }
    
    }
    

    所以实际上就等同于我们重写了一些 ActivityService 的相关操作。具体做些什么,后面会提到~

    Hook Instrumentation

    回过头去看看 Instrumentation.execStartActivity 这个方法,在最后有这么一句代码:

    checkStartActivityResult(result, intent); // 处理各种异常,如ActivityNotFound
    
    static void checkStartActivityResult(int res, Object intent) {  
        if (res >= ActivityManager.START_SUCCESS) {  
            return;  
        }  
    
        switch (res) {  
            case ActivityManager.START_INTENT_NOT_RESOLVED:  
            case ActivityManager.START_CLASS_NOT_FOUND:  
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)  
                    throw new ActivityNotFoundException(  
                            "Unable to find explicit activity class "  
                            + ((Intent)intent).getComponent().toShortString()  
                            + "; have you declared this activity in your AndroidManifest.xml?");  
                throw new ActivityNotFoundException(  
                        "No Activity found to handle " + intent);  
            case ActivityManager.START_PERMISSION_DENIED:  
                throw new SecurityException("Not allowed to start activity "  
                        + intent);  
            case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:  
                throw new AndroidRuntimeException(  
                        "FORWARD_RESULT_FLAG used while also requesting a result");  
            case ActivityManager.START_NOT_ACTIVITY:  
                throw new IllegalArgumentException(  
                        "PendingIntent is not an activity");  
            default:  
                throw new AndroidRuntimeException("Unknown error code "  
                        + res + " when starting " + intent);  
        }  
    } 
    

    相信大家对上面的这些异常信息不陌生吧,其中最熟悉的非 Unable to find explicit activity class 莫属了,如果 Activity 没有在 AndroidMainfest.xml 注册,将会抛出此异常。

    那么就得思考一个问题了,插件的 Activity 并未在宿主程序的 AndroidMainfest.xml 注册,要如何才能绕过这一层检测?

    前文中提到,com.didi.virtualapk.PluginManager 这个类的初始化的时候,除了 Hook 出一个 AMS 代理对象以外,还 Hook 出一个 Instrumentation 对象。代码如下:

    private void hookInstrumentationAndHandler() {
        try {
            Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
            if (baseInstrumentation.getClass().getName().contains("lbe")) {
                // reject executing in paralell space, for example, lbe.
                System.exit(0);
            }
    
            // 创建自定义的 instrumentation,重写了 newActivity() 等一些方法
            // baseInstrumentation 后面还会用到,也保存下来
            final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
    
            // 获取 ActivityThread 的实例
            Object activityThread = ReflectUtil.getActivityThread(this.mContext);
    
            // 用自定义的 instrumentation 替换掉 ActivityThread 里面的 instrumentation
            ReflectUtil.setInstrumentation(activityThread, instrumentation);
            ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
            this.mInstrumentation = instrumentation;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    既然 Activity 的启动,中间走了 Instrumentation.execStartActivity 这个方法,那么我们大概可以知道,Hook 出一个 Instrumentation 对象用来做什么了,实际上就是用来帮助启动插件的 Activity

    启动插件Activity

    我们 Hook 了一个 VAInstrumentation 以替代系统的 Instrumentation,这样当系统通过 ActivityThread 调用 它的的成员变量 mInstrumentation 的 newActivity() 等方法的时候,实际是调用我们 VAInstrumentationnewActivity()

    实际上对于插件 Activity 启动,采用的是宿主 manifest 中占坑的方式来绕过系统校验,然后再加载真正的activity。

    什么是占坑?就是构造一系列假的 Activity 替身,在 AndroidMainfest.xml 里面进行注册,以绕过检测,然后到了真正启动 Activity 的时候,再把它变回,去启动真正的目标 Activity。那么这一步是怎么做的呢?

    我们可以打开核心库里面的 AndroidMainfest.xml 看看:

    <application>
        <!-- Stub Activities -->
        <activity android:name=".A$1" android:launchMode="standard"/>
        <activity android:name=".A$2" android:launchMode="standard"
            android:theme="@android:style/Theme.Translucent" />
    
        <!-- Stub Activities -->
        <activity android:name=".B$1" android:launchMode="singleTop"/>
        <activity android:name=".B$2" android:launchMode="singleTop"/>
        <activity android:name=".B$3" android:launchMode="singleTop"/>
        <activity android:name=".B$4" android:launchMode="singleTop"/>
        <activity android:name=".B$5" android:launchMode="singleTop"/>
        <activity android:name=".B$6" android:launchMode="singleTop"/>
        <activity android:name=".B$7" android:launchMode="singleTop"/>
        <activity android:name=".B$8" android:launchMode="singleTop"/>
    
        <!-- Stub Activities -->
        <activity android:name=".C$1" android:launchMode="singleTask"/>
        <activity android:name=".C$2" android:launchMode="singleTask"/>
        <activity android:name=".C$3" android:launchMode="singleTask"/>
        <activity android:name=".C$4" android:launchMode="singleTask"/>
        <activity android:name=".C$5" android:launchMode="singleTask"/>
        <activity android:name=".C$6" android:launchMode="singleTask"/>
        <activity android:name=".C$7" android:launchMode="singleTask"/>
        <activity android:name=".C$8" android:launchMode="singleTask"/>
    
        <!-- Stub Activities -->
        <activity android:name=".D$1" android:launchMode="singleInstance"/>
        <activity android:name=".D$2" android:launchMode="singleInstance"/>
        <activity android:name=".D$3" android:launchMode="singleInstance"/>
        <activity android:name=".D$4" android:launchMode="singleInstance"/>
        <activity android:name=".D$5" android:launchMode="singleInstance"/>
        <activity android:name=".D$6" android:launchMode="singleInstance"/>
        <activity android:name=".D$7" android:launchMode="singleInstance"/>
        <activity android:name=".D$8" android:launchMode="singleInstance"/>
    
    </application>
    

    可以发现,在清单里面注册了一堆假的 StubActivity。 ABCD分别对应不同的启动模式,那么,我们启动插件的 Activity 的时候,是如何把它改为清单里面已注册的这些假的 Activity 名呢?

    VAInstrumentation 里面,重写了 startActivity 的必经之路,就是 execStartActivity() 方法:

    public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {
    
        // 这里面做了一系列操作,实际上就是查找插件里面第一个符合隐式条件的第一个ResolveInfo,并设置进intent
        mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
        // null component is an implicitly intent
        if (intent.getComponent() != null) {
            Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
                    intent.getComponent().getClassName()));
            // !!! 重头戏在这里,用那些注册的假的StubActivity来替换真实的Activity,以绕过检测 !!!
            this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
        }
    
        ActivityResult result = realExecStartActivity(who, contextThread, token, target,
                    intent, requestCode, options);
    
        return result;
    
    }
    
    private ActivityResult realExecStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ActivityResult result = null;
        try {
            Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
            int.class, Bundle.class};
            result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
                    "execStartActivity", parameterTypes,
                    who, contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
        }
    
        return result;
    }
    

    那么,是如何替换 StubActivity 的呢? 跟进代码:

    public void markIntentIfNeeded(Intent intent) {
        if (intent.getComponent() == null) {
            return;
        }
    
        String targetPackageName = intent.getComponent().getPackageName();
        String targetClassName = intent.getComponent().getClassName();
        // 判断是否是启动插件的Activity
        if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
            // 做标记
            intent.putExtra(Constants.KEY_IS_PLUGIN, true);
            // 保存真实的意图
            intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
            intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
            dispatchStubActivity(intent);
        }
    }
    
    /**
     * 真正的转换就在这里。根据启动模式,转换对应的 StubActivity
     */
    private void dispatchStubActivity(Intent intent) {
        ComponentName component = intent.getComponent();
        String targetClassName = intent.getComponent().getClassName();
        LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
        ActivityInfo info = loadedPlugin.getActivityInfo(component);
        if (info == null) {
            throw new RuntimeException("can not find " + component);
        }
        int launchMode = info.launchMode;
        // 临时替换主题
        Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
        themeObj.applyStyle(info.theme, true);
    
        // 实际上就是这一句,完成转换
        String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
        Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
        intent.setClassName(mContext, stubActivity);
    }
    

    继续跟进代码:

    class StubActivityInfo {
        public static final int MAX_COUNT_STANDARD = 1;
        public static final int MAX_COUNT_SINGLETOP = 8;
        public static final int MAX_COUNT_SINGLETASK = 8;
        public static final int MAX_COUNT_SINGLEINSTANCE = 8;
    
        public static final String corePackage = "com.didi.virtualapk.core";
    
        // 这个格式,就是那些假的Activity的名字
        public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d";
        public static final String STUB_ACTIVITY_SINGLETOP = "%s.B$%d";
        public static final String STUB_ACTIVITY_SINGLETASK = "%s.C$%d";
        public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D$%d";
    
        public final int usedStandardStubActivity = 1;
        public int usedSingleTopStubActivity = 0;
        public int usedSingleTaskStubActivity = 0;
        public int usedSingleInstanceStubActivity = 0;
    
        private HashMap<String, String> mCachedStubActivity = new HashMap<>();
    
        /**
         * 在这里根据启动模式及主题构造 StubActivity 
         */
        public String getStubActivity(String className, int launchMode, Theme theme) {
            String stubActivity= mCachedStubActivity.get(className);
            if (stubActivity != null) {
                return stubActivity;
            }
    
            TypedArray array = theme.obtainStyledAttributes(new int[]{
                    android.R.attr.windowIsTranslucent,
                    android.R.attr.windowBackground
            });
            boolean windowIsTranslucent = array.getBoolean(0, false);
            array.recycle();
            if (Constants.DEBUG) {
                Log.d("StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
            }
            stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
            switch (launchMode) {
                case ActivityInfo.LAUNCH_MULTIPLE: {
                    stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
                    if (windowIsTranslucent) {
                        stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2);
                    }
                    break;
                }
                case ActivityInfo.LAUNCH_SINGLE_TOP: {
                    usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;
                    stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);
                    break;
                }
                case ActivityInfo.LAUNCH_SINGLE_TASK: {
                    usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;
                    stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);
                    break;
                }
                case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {
                    usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;
                    stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);
                    break;
                }
    
                default:break;
            }
    
            mCachedStubActivity.put(className, stubActivity);
            return stubActivity;
        }
    
    }
    

    到这一步,就基本清晰了。同样的,既然变为了 StubActivity,那么真正启动的时候还得变回来才行。来看一下重写后的 newActivity() 方法:

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
            cl.loadClass(className);
        } catch (ClassNotFoundException e) {
            // 根据 intent 类型,去获取相应的插件
            LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
            // 这里就是从Intent中取出我们刚才保存的真正的意图
            String targetClassName = PluginUtil.getTargetActivity(intent);
    
            Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));
    
            if (targetClassName != null) {
                // mBase 是未替换之前的 Instrumentation 对象,所以这个实际上是交给系统原先的 Instrumentation 对象去执行,所以这个模式其实也可以理解为与动态代理等同
                // plugin.getClassLoader() 是自己构造的一个 DexClassLoader,专门用于加载对应的apk里面的类
                Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
                activity.setIntent(intent);
    
                try {
                    // for 4.1+
                    ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
                } catch (Exception ignored) {
                    // ignored.
                }
    
                return activity;
            }
        }
    
        return mBase.newActivity(cl, className, intent);
    }
    

    到这里,插件的 Activity 启动流程分析,就基本结束了。细节方面,没法一步到位,还需要大家边看源码边理解,这样才能看得更透彻。

    Service 支持

    对于 Service 的支持,采用动态代理AMS,拦截 Service 相关的请求,将其中转给Service Runtime去处理,Service Runtime会接管系统的所有操作。

    对于我们动态代理AMS,在上一节 Activity支持 中已经介绍过了,那么,简单的来看一下 ActivityManagerProxy 是如何启动一个Service的。

    在执行 startService 等方法的时候,AMS 代理对象会相应的来执行以下这些方法:

    private Object startService(Object proxy, Method method, Object[] args) throws Throwable {
        IApplicationThread appThread = (IApplicationThread) args[0];
        Intent target = (Intent) args[1];
        ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
        if (null == resolveInfo || null == resolveInfo.serviceInfo) {
            // is host service
            return method.invoke(this.mActivityManager, args);
        }
    
        return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
    }
    
    private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
        Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
        return mPluginManager.getHostContext().startService(wrapperIntent);
    }
    
    private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
        // fill in service with ComponentName
        target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
        String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
    
        // 这里进行判断,看是交给 LocalService,还是 RemoteService 处理
        // LocalService 和 RemoteService 分别对应是否在新的进程中启动Activity
        boolean local = PluginUtil.isLocalService(serviceInfo);
        Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
        Intent intent = new Intent();
        intent.setClass(mPluginManager.getHostContext(), delegate);
        intent.putExtra(RemoteService.EXTRA_TARGET, target);
    
        // 保存一下这个的Command,对应执行不同操作
        intent.putExtra(RemoteService.EXTRA_COMMAND, command);
        intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
        if (extras != null) {
            intent.putExtras(extras);
        }
    
        return intent;
    }
    

    实际上包括我们调用 stopService(),AMS 代理对象最后变换后的意图,同样也是上面代码的最后两个个方法 startDelegateServiceForTargetwrapperTargetIntent(),只不过 command 不一样。

    所以本质上 AMS 作为代理,不管你执行启动或者关闭插件里面的 Service,他都是调用 LocalService 或者 RemoteService 的 startService 方法,在 LocalService 或者 RemoteService 的 onStartCommand() 下,根据 command 进行相应的操作。那么我们来看一下 LocalService 的 onStartCommand() 方法:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
            return START_STICKY;
        }
    
        Intent target = intent.getParcelableExtra(EXTRA_TARGET);
        int command = intent.getIntExtra(EXTRA_COMMAND, 0);
        if (null == target || command <= 0) {
            return START_STICKY;
        }
    
        ComponentName component = target.getComponent();
        LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
    
        switch (command) {
            case EXTRA_COMMAND_START_SERVICE: {
                ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
                IApplicationThread appThread = mainThread.getApplicationThread();
                Service service;
    
                if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
                    service = this.mPluginManager.getComponentsHandler().getService(component);
                } else {
                    try {
                        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
    
                        Application app = plugin.getApplication();
                        IBinder token = appThread.asBinder();
                        Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
                        IActivityManager am = mPluginManager.getActivityManager();
    
                        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
                        service.onCreate();
                        this.mPluginManager.getComponentsHandler().rememberService(component, service);
                    } catch (Throwable t) {
                        return START_STICKY;
                    }
                }
    
                service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
                break;
            }
            case EXTRA_COMMAND_BIND_SERVICE: {
                ActivityThread mainThread = (ActivityThread)ReflectUtil.getActivityThread(getBaseContext());
                IApplicationThread appThread = mainThread.getApplicationThread();
                Service service = null;
    
                if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
                    service = this.mPluginManager.getComponentsHandler().getService(component);
                } else {
                    try {
                        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
    
                        Application app = plugin.getApplication();
                        IBinder token = appThread.asBinder();
                        Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
                        IActivityManager am = mPluginManager.getActivityManager();
    
                        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
                        service.onCreate();
                        this.mPluginManager.getComponentsHandler().rememberService(component, service);
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                }
                try {
                    IBinder binder = service.onBind(target);
                    IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
                    IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
                    iServiceConnection.connected(component, binder);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;
            }
            case EXTRA_COMMAND_STOP_SERVICE: {
                Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
                if (null != service) {
                    try {
                        service.onDestroy();
                    } catch (Exception e) {
                        Log.e(TAG, "Unable to stop service " + service + ": " + e.toString());
                    }
                } else {
                    Log.i(TAG, component + " not found");
                }
                break;
            }
            case EXTRA_COMMAND_UNBIND_SERVICE: {
                Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
                if (null != service) {
                    try {
                        service.onUnbind(target);
                        service.onDestroy();
                    } catch (Exception e) {
                        Log.e(TAG, "Unable to unbind service " + service + ": " + e.toString());
                    }
                } else {
                    Log.i(TAG, component + " not found");
                }
                break;
            }
        }
    
        return START_STICKY;
    }
    

    很显然,在这里面才对应去控制了插件Service的生命周期。具体代码就留给大家分析吧~~

    ContentProvider 支持

    动态代理 IContentProvider,拦截provider相关的请求,将其中转给Provider Runtime去处理,Provider Runtime会接管系统的所有操作。

    我们来看一下 com.didi.virtualapk.internal.PluginContentResolver 这个类:

    public class PluginContentResolver extends ContentResolver {
        private ContentResolver mBase;
        private PluginManager mPluginManager;
        private static Method sAcquireProvider;
        private static Method sAcquireExistingProvider;
        private static Method sAcquireUnstableProvider;
    
        static {
            try {
                sAcquireProvider = ContentResolver.class.getDeclaredMethod("acquireProvider",
                        new Class[]{Context.class, String.class});
                sAcquireProvider.setAccessible(true);
                sAcquireExistingProvider = ContentResolver.class.getDeclaredMethod("acquireExistingProvider",
                        new Class[]{Context.class, String.class});
                sAcquireExistingProvider.setAccessible(true);
                sAcquireUnstableProvider = ContentResolver.class.getDeclaredMethod("acquireUnstableProvider",
                        new Class[]{Context.class, String.class});
                sAcquireUnstableProvider.setAccessible(true);
            } catch (Exception e) {
                //ignored
            }
        }
    
        public PluginContentResolver(Context context) {
            super(context);
            mBase = context.getContentResolver();
            mPluginManager = PluginManager.getInstance(context);
        }
    
        protected IContentProvider acquireProvider(Context context, String auth) {
            try {
                if (mPluginManager.resolveContentProvider(auth, 0) != null) {
                    // 在这里,去 hook 一个 IContentProvider 代理对象
                    return mPluginManager.getIContentProvider();
                }
    
                return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    
        // ...
    }
    

    这个类是在构造 LoadedPlugin 的时候创建的 PluginContext 对象里面的 getContentResolver() 里面创建的。

    class PluginContext extends ContextWrapper {
    
        private final LoadedPlugin mPlugin;
    
        public PluginContext(LoadedPlugin plugin) {
            super(plugin.getPluginManager().getHostContext());
            this.mPlugin = plugin;
        }
    
        @Override
        public ContentResolver getContentResolver() {
            // 创建代理支持
            return new PluginContentResolver(getHostContext());
        }
    }
    

    那么,上面Hook 的 IContentProvider 代理对象,实际上是在 PluginManager 做的。

    private void hookIContentProviderAsNeeded() {
        Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
        mContext.getContentResolver().call(uri, "wakeup", null, null);
        try {
            Field authority = null;
            Field mProvider = null;
            ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
            Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
            Iterator iter = mProviderMap.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                Object key = entry.getKey();
                Object val = entry.getValue();
                String auth;
                if (key instanceof String) {
                    auth = (String) key;
                } else {
                    if (authority == null) {
                        authority = key.getClass().getDeclaredField("authority");
                        authority.setAccessible(true);
                    }
                    auth = (String) authority.get(key);
                }
                if (auth.equals(PluginContentResolver.getAuthority(mContext))) {
                    if (mProvider == null) {
                        mProvider = val.getClass().getDeclaredField("mProvider");
                        mProvider.setAccessible(true);
                    }
                    IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
                    IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
                    mIContentProvider = proxy;
                    Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    这一块的内容,最好根据滴滴提供的Demo,再来看,比较容易理解。

    Uri bookUri = Uri.parse("content://com.ryg.chapter_2.book.provider/book");
    ContentValues values = new ContentValues();
    values.put("_id", 6);
    values.put("name", "程序设计的艺术");
    
    大纲与资料.png 高清视频资料.jpg

    +qq群:853967238。获取以上高清技术思维图,以及相关技术的免费视频学习资料。

    相关文章

      网友评论

        本文标题:滴滴开源Android插件化框架VirtualAPK原理分析

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