美文网首页Android技术知识Android插件化技术Android开发
Android插件化技术之旅 2-广播插件的实现与安装apk原理

Android插件化技术之旅 2-广播插件的实现与安装apk原理

作者: 289346a467da | 来源:发表于2018-12-27 23:43 被阅读6次

    前言

    Android技术如今已很成熟了,组件化、插件化、热修复等等框架层出不穷,如果只停留在单纯的会用框架上,技术永远得不到成长,只有懂得其原理,能够婉婉道来,能够自己手动写出,技术才会得到成长,与其焦虑未来,不如把握现在。本篇将手写教大家写出插件化框架,插件化技术是Android高级工程师必备的技术之一,懂其思想,知其原理。本篇专题将由10篇文章来详细的讲解插件化技术,深耕一个技术领域,才能懂得如何更广阔的横向发展。

    本专题代码地址

    在上一篇文章中,我们实现了,如何启动一个插件,和启动插件内的Activity和Service.

    我们再来回顾一下如何启动一个插件:

    1. 首先,我们要明白一个插件是没有安装到手机上的,所以我们需要将上下文(Context)传递到插件中,插件用到上下文到方法需要复写.
    2. 启动插件到一个Activity(注意插件中的Activity必须是launchMode="standard",我们会在后续解决这个问题),其实就是启动宿主(app)中的一个空壳的Activity(ProxyActivity),然后通过DexClassLoader加载apk,拿到Activity的全类名,然后反射拿到Activity实例,强转为PluginInterfaceActivity(插件Activity实现的接口).通过接口将生命周期传递给插件.
    3. 启动插件的Service也是同样的道理.

    做完本章能达到什么效果呢?

    1545909862509.gif

    本篇文章我们来实现广播插件.广播主要分两种,一种动态广播,一种静态广播.

    插件中动态广播的实现:

    启动插件中的动态广播其实和启动Activity和Service是一样的流程.

    首先我们需要在通用库中新建一个接口,插件中的广播都要实现此接口

    public interface PluginInterfaceBroadcast {
        void attach(Context context);
    
        void onReceive(Context context, Intent intent);
    }
    

    然后在BaseActivity中重写registerReceiversendBroadcastunregisterReceiver,因为这两个方法都用到了上下文(Context),我们需要用宿主(app)的Context来注册和发送广播.代码如下:

    @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            if (mActivity != null) {
                return mActivity.registerReceiver(receiver, filter);
            }
            return super.registerReceiver(receiver, filter);
        }
    
        @Override
        public void sendBroadcast(Intent intent) {
            if (null != mActivity) {
                mActivity.sendBroadcast(intent);
            } else {
                super.sendBroadcast(intent);
            }
        }
        @Override
        public void unregisterReceiver(BroadcastReceiver receiver) {
            if (null != mActivity) {
                mActivity.unregisterReceiver(receiver);
            } else {
                super.unregisterReceiver(receiver);
            }
        }
    

    上述代码其实是,调用了宿主(app)的方法,其实就是启动了宿主定义好的的一个空壳的广播,然后通过DexClassLoader反射插件中的广播类,然后通过继承的接口,来进行方法的调用和参数的传递.

    public class ProxyBroadcast extends BroadcastReceiver {
        private String className;
    
        private PluginInterfaceBroadcast bordcast;
    
        private static final String TAG = "ProxyBroadcast";
    
        public ProxyBroadcast(String name, Context context) {
            this.className = name;
            Class loadClass = null;
            try {
                loadClass = PluginManager.getInstance().getClassLoader().loadClass(className);
                Constructor constructor = loadClass.getConstructor(new Class[]{});
                bordcast = (PluginInterfaceBroadcast) constructor.newInstance(new Object[]{});
                bordcast.attach(context);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        //class --- object --- p
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e(TAG, "ProxyBroadcast onReceive: ");
            if (bordcast != null) {
                bordcast.onReceive(context, intent);
            }
        }
    }
    

    同理,在宿主方法中需要做一些处理,new ProxyBroadcast 然后注册此广播,实际上就是注册了宿主的空壳的一个广播:

     private List<ProxyBroadcast> proxyBroadcastList = new ArrayList<>();
    
        @Override
        public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
            //重写真正注册的是ProxyBroadcast 转发
            IntentFilter filter1 = new IntentFilter();
            for (int i = 0; i < filter.countActions(); i++) {
                filter1.addAction(filter.getAction(i));
                Log.e(TAG, "sendBroadcast: 注册插件的广播 -> " + filter1.getAction(i));
            }
            ProxyBroadcast proxyBroadcast = new ProxyBroadcast(receiver.getClass().getName(), this);
            proxyBroadcastList.add(proxyBroadcast);
            return super.registerReceiver(proxyBroadcast, filter1);
        }
    
        @Override
        public void unregisterReceiver(BroadcastReceiver receiver) {
            if (proxyBroadcastList != null && proxyBroadcastList.size() > 0) {
                for (ProxyBroadcast proxyBroadcast : proxyBroadcastList) {
                    if (proxyBroadcast.getClass().getName().equals(receiver.getClass().getName())) {
                        super.unregisterReceiver(proxyBroadcast);
                    }
                }
            } else {
                super.unregisterReceiver(receiver);
            }
        }
    

    我们来看一个插件中的广播实现,通过继承PluginInterfaceBroadcast,宿主调用接口的方法:

    public class MyReceive extends BroadcastReceiver implements PluginInterfaceBroadcast {
        @Override
        public void attach(Context context) {
            Toast.makeText(context, "注册广播成功", Toast.LENGTH_SHORT).show();
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接收广播成功", Toast.LENGTH_SHORT).show();
        }
    }
    

    OK,这样我们就实现了,插件动态广播的实现,非常简单,几乎没有什么难点,接下来我们来看插件静态广播如何实现?

    插件静态广播实现原理:

    我们知道任何插件都是没有安装到手机上的,静态广播是注册在AndroidManifest中,那么,我们就不能通过上述那样轻松的拿到类名进行反射了,如下代码

     <receiver android:name=".StaticBroadcastReceiver">
                <intent-filter>
                    <action android:name="com.prim.plugin.a" />
                </intent-filter>
            </receiver>
    

    需要通过PMS来解析xml,拿到在xml注册的类名,和intent-filter,然后如上述动态广播一样,宿主动态注册插件中的静态广播,以达到这样的效果.实际上宿主就是做了动态注册静态广播,天马行空的想象.

    APK安装时做了什么呢?

    1. 安装时把apk文件复制到data/app目录下
    2. 开辟存放应用文件的数据data/data/包名
    3. 将apk中的dex文件安装到data/dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一)

    apk安装时并有做很多操作,那么它是如何真正的加载静态广播呢? 真正的加载广播,是发生在系统启动时,
    系统启动可以理解成 将所有app重新安装一遍到系统中,会重复上述过程.

    PMS安装APK原理

    为了方便阅读,我已经将PackageManagerService 和PackageParser的源码放到了代码中,结合起来阅读本文更容易理解.

    PMS 全称(PackageManagerService) ,当安装apk时会调用PackageManagerService的main方法

     public static final PackageManagerService main(Context context, Installer installer,
                boolean factoryTest, boolean onlyCore) {
            PackageManagerService m = new PackageManagerService(context, installer,
                    factoryTest, onlyCore);
            ServiceManager.addService("package", m);
            return m;
        }
    

    很显然上述代码,new PackageManagerService,我们接着看PackageManagerService的构造方法.然后我们可以看到data app 这样创建的目录,着重关注mAppInstallDir,字面意思就是app的安装路径

    public PackageManagerService(Context context, Installer installer,
                boolean factoryTest, boolean onlyCore) {
                .....
                synchronized (mPackages) {
                mHandlerThread = new ServiceThread(TAG,
                        Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
                mHandlerThread.start();
                mHandler = new PackageHandler(mHandlerThread.getLooper());
                Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
                //TODO
                File dataDir = Environment.getDataDirectory();
                mAppDataDir = new File(dataDir, "data");
                mAppInstallDir = new File(dataDir, "app");
                mAppLib32InstallDir = new File(dataDir, "app-lib");
                mAsecInternalPath = new File(dataDir, "app-asec").getPath();
                mUserAppDataDir = new File(dataDir, "user");
                mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
                ......
                 if (!mOnlyCore) {
                    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
                            SystemClock.uptimeMillis());
                    scanDirLI(mAppInstallDir, 0, scanFlags, 0);
    
                    scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                            scanFlags, 0);
                            .....
                            }
                }
    

    接着会调用scanDirLI去扫面apk文件,点击去看一下它做了什么? 内部有调用scanPackageLI方法.

    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
    for (File file : files) {
                final boolean isPackage = (isApkFile(file) || file.isDirectory())
                        && !PackageInstallerService.isStageName(file.getName());
                if (!isPackage) {
                    // Ignore entries which are not packages
                    continue;
                }
                try {
                    scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                            scanFlags, currentTime, null);
                } catch (PackageManagerException e) {
                    Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
    
                    // Delete invalid userdata apps
                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                            e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
                        logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
                        if (file.isDirectory()) {
                            FileUtils.deleteContents(file);
                        }
                        file.delete();
                    }
                }
            }
            }
    

    接着点scanPackageLI,从字面意思上看是解析包,我们看看它做了什么?返回了一个Package,这个Package是不是我们想要的呢?

     private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
                long currentTime, UserHandle user) throws PackageManagerException {
    ....
            final PackageParser.Package pkg;
            try {
                pkg = pp.parsePackage(scanFile, parseFlags);
            } catch (PackageParserException e) {
                throw PackageManagerException.from(e);
            }
            
            .....
            }
    

    看一下Package 里面有什么? 很显然它是我们想要的Package存放的就是AndroidManifest.xml 注册的四大组件.

     public final static class Package {
     ....
            public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
            public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
            public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
            public final ArrayList<Service> services = new ArrayList<Service>(0);
            ......
            }
    

    注意这里的Activity不是四大组件的Activity而是PackageParser的一个内部类,activity 与receivers 在 AndroidManifest里面属性都差不多一样,很显然Google复用了Activity.

    我们看一下Activity里面存放了什么?

      public final static class Activity extends Component<ActivityIntentInfo> {
            public final ActivityInfo info;
    
            public Activity(final ParseComponentArgs args, final ActivityInfo _info) {
                super(args, _info);
                info = _info;
                info.applicationInfo = args.owner.applicationInfo;
            }
            
            public void setPackageName(String packageName) {
                super.setPackageName(packageName);
                info.packageName = packageName;
            }
    
           .......
        }
    

    Activity中只存放了ActivityInfo,继续点进去ActivityInfo中是否有我们想要的类名等信息

    ActivityInfo extends ComponentInfo 
    ComponentInfo extends PackageItemInfo
    /**
         * Public name of this item. From the "android:name" attribute.
         */
        public String name;
    

    PackageItemInfo中找到了这样的一个属性,终于找到了我们想要的类名了.只要我们拿到ActivityInfo就可以拿到类名

    <receiver android:name=".StaticBroadcastReceiver">
                <intent-filter>
                    <action android:name="com.prim.plugin.a" />
                </intent-filter>
            </receiver>
    

    但是还忽略了一点,intent-filter 还没有找到,从代码中看

    final static class Activity extends Component<ActivityIntentInfo>
    
     public static class Component<II extends IntentInfo> {
            public final Package owner;
            public final ArrayList<II> intents;
            public final String className;
            public Bundle metaData;
            }
    

    ArrayList<II> intents 看起来像是intent-filter,II泛型 --》 IntentInfo

     public static class IntentInfo extends IntentFilter {
            public boolean hasDefault;
            public int labelRes;
            public CharSequence nonLocalizedLabel;
            public int icon;
            public int logo;
            public int banner;
            public int preferred;
        }
    

    可以看到IntentInfo 继承 IntentFilter,找到了IntentFilter.

    中途总结

    从上述代码中,我们可以知道通过,parsePackage解析包,得到Package,就可以拿到AndroidManifeast的信息.

    PackageParser pp = new PackageParser();
    
    final PackageParser.Package pkg;
    //解析apk 得到pkg
    pkg = pp.parsePackage(scanFile, parseFlags);
    

    但是很不幸的是,Google将这个类写成了@hide 隐藏的API那我们就只能通过反射去获取了,感觉瞬间蛋疼了.

    image.png

    没办法,就只能硬来咯

    首先我们实例化PackageParser然后调用parsePackage得到Package

     //反射获取解析apk包的类
                Class packageParserClass = Class.forName("android.content.pm.PackageParser");
                //获取方法
                Method parsePackage = packageParserClass.getDeclaredMethod("parsePackage",
                        File.class, int.class);
                //实例化PackageParser类
                Object packageParser = packageParserClass.newInstance();
                //Package 得到
                Object packageObj = parsePackage.invoke(packageParser, new File(absolutePath), PackageManager.GET_ACTIVITIES);
                //拿到注册的静态广播
    

    然后通过Package拿到xml注册的静态广播

    //拿到注册的静态广播
                Field receiversField = packageObj.getClass().getDeclaredField("receivers");
                //获取List<Activity>
                List receivers = (List) receiversField.get(packageObj);
    

    然后循环Activity,拿到类名和intent-fliter

    //循环receivers
                for (Object activity : receivers) {
                    //拿到ActivityInfo
                    ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, activity, 0, defaltUserState, userId);
                    //根据ActivityInfo,拿到BroadCastReceiver
                    BroadcastReceiver broadcastReceiver = (BroadcastReceiver) classLoader.loadClass(info.name).newInstance();
                    //拿到intentFilter
                    List<? extends IntentFilter> intentFilters = (List<? extends IntentFilter>) intentsField.get(activity);
                    for (IntentFilter filter : intentFilters) {
                        //动态注册插件中的静态广播
                        context.registerReceiver(broadcastReceiver, filter);
                    }
                }
    

    这样看起来,实现思路很简单啊,一脸的懵逼啊.

    image.png

    SystemService -> main

    ->scanDirLI 扫描apk的文件 --》scanPackageLI 解析apk PackageParser -> parsePackage get Package (一个apk对应一个package)
    -> parseBaseApk loadApkIntoAssetManager -> parseBaseApk -> parseBaseApplication -> parseActivity -> parseIntent

    核心完整代码如下:

    /**
         * 解析xml静态注册的广播
         *
         * @param context
         * @param absolutePath
         */
        private void parserReceive(Context context, String absolutePath) {
            try {
                //反射获取解析apk包的类
                Class packageParserClass = Class.forName("android.content.pm.PackageParser");
                //获取方法
                Method parsePackage = packageParserClass.getDeclaredMethod("parsePackage",
                        File.class, int.class);
                //实例化PackageParser类
                Object packageParser = packageParserClass.newInstance();
                //Package 得到
                Object packageObj = parsePackage.invoke(packageParser, new File(absolutePath), PackageManager.GET_ACTIVITIES);
                //拿到注册的静态广播
                Field receiversField = packageObj.getClass().getDeclaredField("receivers");
                //获取List<Activity>
                List receivers = (List) receiversField.get(packageObj);
                //public final static class Activity extends Component<ActivityIntentInfo>
                //获取Component
                Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
                //获取intents
                Field intentsField = componentClass.getDeclaredField("intents");
                //generatePackageInfo(PackageParser.Package p,
                //            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
                //            HashSet<String> grantedPermissions, PackageUserState state, int userId)
    
    
                // 调用generateActivityInfo 方法, 把PackageParser.Activity 转换成
                Class<?> packageParser$ActivityClass = Class.forName("android.content.pm.PackageParser$Activity");
                // generateActivityInfo方法
                Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
                Object defaltUserState = packageUserStateClass.newInstance();
    
                Method generateReceiverInfo = packageParserClass.getDeclaredMethod("generateActivityInfo",
                        packageParser$ActivityClass, int.class, packageUserStateClass, int.class);
                //获取userID
                Class<?> userHandler = Class.forName("android.os.UserHandle");
                Method getCallingUserIdMethod = userHandler.getDeclaredMethod("getCallingUserId");
                int userId = (int) getCallingUserIdMethod.invoke(null);
    
                //循环receivers
                for (Object activity : receivers) {
                    //拿到ActivityInfo
                    ActivityInfo info = (ActivityInfo) generateReceiverInfo.invoke(packageParser, activity, 0, defaltUserState, userId);
                    //根据ActivityInfo,拿到BroadCastReceiver
                    BroadcastReceiver broadcastReceiver = (BroadcastReceiver) classLoader.loadClass(info.name).newInstance();
                    //拿到intentFilter
                    List<? extends IntentFilter> intentFilters = (List<? extends IntentFilter>) intentsField.get(activity);
                    for (IntentFilter filter : intentFilters) {
                        //动态注册插件中的静态广播
                        context.registerReceiver(broadcastReceiver, filter);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    插件中的静态广播就不需要实现接口PluginInterfaceBroadcast

    public class StaticBroadcastReceiver extends BroadcastReceiver {
    
        private static final String ACTION = "com.prim.plugin.host";
    
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "我是插件,收到发送的广播,我将向宿主发送广播", Toast.LENGTH_SHORT).show();
            //接收到广播,然后给宿主发送广播
            context.sendBroadcast(new Intent(ACTION));
        }
    }
    

    相关文章

      网友评论

        本文标题:Android插件化技术之旅 2-广播插件的实现与安装apk原理

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