美文网首页
Android进阶(十)资源和Service的插件化

Android进阶(十)资源和Service的插件化

作者: Android高级工程师 | 来源:发表于2019-05-15 15:30 被阅读0次

    一、系统资源加载

    1、资源类别

    • res目录下存放的资源文件。编译时会在R文件中生成资源文件的十六进制值。res目录下资源通过Context.getResource方法获取到Resource对象,然后通过getXXX获取资源。
    • assets目录下存放的原始文件,编译时不会被编译。通过AssetManager的open方法获取目录下文件资源,AssetManager来源于Resources类的getAssets方法

    2、Resources

    (1)AssetManager


    image.png
    • AssetManage有一个addAssetPath方法,将apk路径传入,Resources就能访问当前apk的所有资源。可以通过反射的方式将插件apk路径传入addAssetPath方法。
    • AssetManager内部有一个NDK方法,用来访问文件。apk打包时会生成一个resources.arsc文件,是一个Hash表,存放着十六进制和资源的对应关系

    二、VirtualApk插件资源加载

    资源插件化实现方式:

    • 合并资源:将插件的资源合并到宿主的Resources中,可以访问宿主的资源。可能存在插件和宿主的资源id重复的情况。
      解决方式:
    1. 修改Android打包流程中使用到的aapt命令,为插件的资源id指定前缀,避免与宿主资源id冲突。
    2. 在Android打包生成resources.arsc文件之后,对这个resources.arsc文件进行修改。
    • 单独加载插件资源:每个插件都会构造单独的Resources去加载插件资源,不能访问宿主资源

    1、Resources创建

    #LoadedPlugin
    public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
        ......
        this.mResources = createResources(context, getPackageName(), apk);
        ......
    }
    protected Resources createResources(Context context, String packageName, File apk) throws Exception {
        if (Constants.COMBINE_RESOURCES) {
            //插件资源合并到宿主中,插件可访问宿主资源
            return ResourcesManager.createResources(context, packageName, apk);
        } else {
            //插件创建独立的Resources,不与宿主关联
            Resources hostResources = context.getResources();
            AssetManager assetManager = createAssetManager(context, apk);
            return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
        }
    }
    

    2、插件资源独立

    主要通过反射创建新的AssetManager对象,通过addAssetPath加载插件资源。适用于资源独立的情况,无法调用宿主资源

    protected AssetManager createAssetManager(Context context, File apk) throws Exception {
        //通过反射创建新的AssetManager对象,通过addAssetPath加载插件资源
        AssetManager am = AssetManager.class.newInstance();
        Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath());
        return am;
    }
    

    3、插件资源合并

    先获取到宿主资源的AssetManager,再通过反射调用AssetManager的addAssetPath添加插件资源,返回新的Resources

    #ResourcesManager
    public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
        //根据版本创建Resources对象
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //(1)
            return createResourcesForN(hostContext, packageName, apk);
        }
        //(2)
        Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
        ResourcesManager.hookResources(hostContext, resources);
        return resources;
    }
    //创建Resource对象
    private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception {
        //宿主Resources对象
        Resources hostResources = hostContext.getResources();
        Resources newResources = null;
        AssetManager assetManager;
        Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            //通过反射创建AssetManager
            assetManager = AssetManager.class.newInstance();
            reflector.bind(assetManager);
            final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);;
            if (cookie1 == 0) {
                throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir);
            }
        } else {
            //获取到宿主的AssetManager
            assetManager = hostResources.getAssets();
            reflector.bind(assetManager);
        }
        final int cookie2 = reflector.call(apk);
        if (cookie2 == 0) {
            throw new RuntimeException("createResources failed, can't addAssetPath for " + apk);
        }
        List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
        for (LoadedPlugin plugin : pluginList) {
            final int cookie3 = reflector.call(plugin.getLocation());
            if (cookie3 == 0) {
                throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation());
            }
        }
        //通过不同的手机品牌创建Resources对象
        if (isMiUi(hostResources)) {
            newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
        } else if (isVivo(hostResources)) {
            newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
        } else if (isNubia(hostResources)) {
            newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
        } else if (isNotRawResources(hostResources)) {
            newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
        } else {
            // is raw android resources
            newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
        }
        // lastly, sync all LoadedPlugin to newResources
        for (LoadedPlugin plugin : pluginList) {
            plugin.updateResources(newResources);
        }
        
        return newResources;
    }
    

    Hook住了ContextImpl的mResources和LoadedApk的mResources

    public static void hookResources(Context base, Resources resources) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return;
        }
        try {
            Reflector reflector = Reflector.with(base);
            //hook mResources
            reflector.field("mResources").set(resources);
            Object loadedApk = reflector.field("mPackageInfo").get();
            //hook mResources
            Reflector.with(loadedApk).field("mResources").set(resources);
    
            Object activityThread = ActivityThread.currentActivityThread();
            Object resManager;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                resManager = android.app.ResourcesManager.getInstance();
            } else {
                resManager = Reflector.with(activityThread).field("mResourcesManager").get();
            }
            Map<Object, WeakReference<Resources>> map = Reflector.with(resManager).field("mActiveResources").get();
            Object key = map.keySet().iterator().next();
            map.put(key, new WeakReference<>(resources));
        } catch (Exception e) {
            Log.w(TAG, e);
        }
    }
    

    4、Activity启动资源处理

    #VAInstrumentation
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try {
            cl.loadClass(className);
            Log.i(TAG, String.format("newActivity[%s]", className));
            
        } catch (ClassNotFoundException e) {
            ......
    
            // 通过反射将Resources赋值给Activity的mResources
            Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
            return newActivity(activity);
        }
    
        return newActivity(mBase.newActivity(cl, className, intent));
    }
    

    三、so的插件化

    so的插件化,有两种方案:基于System.Load和基于System.LoadLibrary。

    1、VirtualApk的实现

    #LoadedPlugin
    protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
        File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
        String dexOutputPath = dexOutputDir.getAbsolutePath();
        DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
    
        if (Constants.COMBINE_CLASSLOADER) {
            DexUtil.insertDex(loader, parent, libsDir);
        }
        return loader;
    }
    
    

    创建了一个DexClassLoader,解析出每个插件apk中的so文件,解压到某个位置,把这些路径用逗号连接起来成为一个字符串,放到DexClassLoader的构造函数的第3个参数中。这样插件中的so,就和宿主App中jniLib目录下的so一样,通过System.loadLibrary方法来加载。

    #DexUtil
    public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
        Object baseDexElements = getDexElements(getPathList(baseClassLoader));
        Object newDexElements = getDexElements(getPathList(dexClassLoader));
        //将宿主和插件的DexElements合并得到allDexElements
        Object allDexElements = combineArray(baseDexElements, newDexElements);
        Object pathList = getPathList(baseClassLoader);
        //通过反射将dexElements替换为allDexElements
        Reflector.with(pathList).field("dexElements").set(allDexElements);
        
        insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);
    }
    

    so插件化核心代码

    private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
        if (sHasInsertedNativeLibrary) {
            return;
        }
        sHasInsertedNativeLibrary = true;
    
        Context context = ActivityThread.currentApplication();
        //获取宿主的PathList
        Object basePathList = getPathList(baseClassLoader);
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
            Reflector reflector = Reflector.with(basePathList);
       
            List<File> nativeLibraryDirectories = reflector.field("nativeLibraryDirectories").get();
            nativeLibraryDirectories.add(nativeLibsDir);
            //获取到宿主的so集合
            Object baseNativeLibraryPathElements = reflector.field("nativeLibraryPathElements").get();
            final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements);
    
            Object newPathList = getPathList(dexClassLoader);
            //获取到插件的so集合
            Object newNativeLibraryPathElements = reflector.get(newPathList);
            Class<?> elementClass = newNativeLibraryPathElements.getClass().getComponentType();
            Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1);
            //将原来宿主的so集合拷贝到新集合中
            System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength);
    
            Field soPathField;
            if (Build.VERSION.SDK_INT >= 26) {
                soPathField = elementClass.getDeclaredField("path");
            } else {
                soPathField = elementClass.getDeclaredField("dir");
            }
            soPathField.setAccessible(true);
            //将插件的so集合拷贝到新集合中
            final int newArrayLength = Array.getLength(newNativeLibraryPathElements);
            for (int i = 0; i < newArrayLength; i++) {
                Object element = Array.get(newNativeLibraryPathElements, i);
                String dir = ((File)soPathField.get(element)).getAbsolutePath();
                if (dir.contains(Constants.NATIVE_DIR)) {
                    Array.set(allNativeLibraryPathElements, baseArrayLength, element);
                    break;
                }
            }
            //将宿主和插件so的合集替换上去
            reflector.set(allNativeLibraryPathElements);
        } else {
            Reflector reflector = Reflector.with(basePathList).field("nativeLibraryDirectories");
            File[] nativeLibraryDirectories = reflector.get();
            final int N = nativeLibraryDirectories.length;
            File[] newNativeLibraryDirectories = new File[N + 1];
            System.arraycopy(nativeLibraryDirectories, 0, newNativeLibraryDirectories, 0, N);
            newNativeLibraryDirectories[N] = nativeLibsDir;
            reflector.set(newNativeLibraryDirectories);
        }
    }
    

    获取宿主so集合,获取插件so集合,二者合并后通过反射替换原so集合,插件so文件就能正常被加载了

    四、VirtualApk的Service插件化

    1、Service启动分析

    image.png
    image.png

    插件化分析:

    • Service启动跟Instrumentation没关系,不能通过Hook Instrumentation来处理
    • 在Standard模式下多次启动占位Activity可创建多个Activity,但是多次启动占位Service并不会创建多个Service实例
    • 通过代理分发实现:启动一个代理Service统一管理,拦截所有Service方法,修改为startService到代理Service,在代理Service的onStartCommond统一管理,创建/停止目标service。

    2、Hook IActivityManager
    VirtualApk初始化时通过ActivityManagerProxy Hook了IActivityManager。启动服务时,通过ActivityManagerProxy拦截到了startService的操作

    public class ActivityManagerProxy implements InvocationHandler {
    
        protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
            IApplicationThread appThread = (IApplicationThread) args[0];
            //跳转的intent
            Intent target = (Intent) args[1];
            //检查Service信息
            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);
        }
    
        protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
            Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
            return mPluginManager.getHostContext().startService(wrapperIntent);
        }
    
        protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
            // 将目标Service的相关信息存储起来
            target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
            String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
    
            // 根据processName判断是否为远程服务
            boolean local = PluginUtil.isLocalService(serviceInfo);
            // 判断交给LocalService还是RemoteService进行处理
            Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
            // 参数传递
            Intent intent = new Intent();
            intent.setClass(mPluginManager.getHostContext(), delegate);
            intent.putExtra(RemoteService.EXTRA_TARGET, target);
            intent.putExtra(RemoteService.EXTRA_COMMAND, command);
            intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
            if (extras != null) {
                intent.putExtras(extras);
            }
            return intent;
        }
    }
    

    3、LocalService

    public class LocalService extends Service {
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            ......
            switch (command) {
                case EXTRA_COMMAND_START_SERVICE: {
                    //获取ActivityThread
                    ActivityThread mainThread = ActivityThread.currentActivityThread();
                    IApplicationThread appThread = mainThread.getApplicationThread();
                    Service service;
    
                    if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
                        //获取Service
                        service = this.mPluginManager.getComponentsHandler().getService(component);
                    } else {
                        try {
                            //通过DexClassLoader加载Service
                            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方法绑定Context       
                            attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
                            //调用Service的onCreate方法
                            service.onCreate();
                            this.mPluginManager.getComponentsHandler().rememberService(component, service);
                        } catch (Throwable t) {
                            return START_STICKY;
                        }
                    }
                    //调用service的onStartCommand方法
                    service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
                    break;
                }
                ......
            }
        }
    }
    

    4、RemoteService

    public class RemoteService extends LocalService {
        
        private static final String TAG = Constants.TAG_PREFIX + "RemoteService";
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            if (intent == null) {
                return super.onStartCommand(intent, flags, startId);
            }
            //获取目标service的intent
            Intent target = intent.getParcelableExtra(EXTRA_TARGET);
            if (target != null) {
                //获取插件路径
                String pluginLocation = intent.getStringExtra(EXTRA_PLUGIN_LOCATION);
                ComponentName component = target.getComponent();
                LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component);
                if (plugin == null && pluginLocation != null) {
                    try {
                        //加载apk插件文件
                        PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));
                    } catch (Exception e) {
                        Log.w(TAG, e);
                    }
                }
            }
    
            return super.onStartCommand(intent, flags, startId);
        }
    }
    

    启动远程服务多了一步加载其他插件的Service的操作

    5、Service插件化总结

    • 初始化时通过ActivityManagerProxy Hook住了IActivityManager。
    • 服务启动时通过ActivityManagerProxy拦截,判断是否为远程服务,如果为远程服务,启动RemoteService,如果为同进程服务则启动LocalService。
    • 如果为LocalService,则通过DexClassLoader加载目标Service,然后反射调用attach方法绑定Context,然后执行Service的onCreate、onStartCommand方法
    • 如果为RemoteService,则先加载插件的远程Service,后续跟LocalService一致。

    创作不易喜欢的话记得点击+关注哦

    相关文章

      网友评论

          本文标题:Android进阶(十)资源和Service的插件化

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