Android 插件化原理解析——广播插件的实现与安装apk原理解析

发布一下 0 0


前言

android技术特别成熟了,热修复,组件化......等框架已经层出不穷,如果还仅限于使用框架,技术永远很难得到成长,只有我们懂得他的原理,能够娓娓道来,能够自己动手的写出来,技术才会越来越好,与其想着未来怎么办,不如把握现在。这一篇文章教大家手写出插件化框架,插件化技术是Android工程师必备的技术之一,我们要懂其思想,知其原理。

那么在 android中,什么是「 插件化 」,顾名思义啊,就是把一些核心复杂依赖度高的业务模块封装成独立的插件,然后根据不同业务需求进行不同组合,动态进行替换,可对插件进行管理、更新,后期对插件也可进行版本管理等操作。在插件化中有两个概念需要讲解下:

宿主

所谓宿主,就是需要能提供运行环境,给资源调用提供上下文环境,一般也就是我们主 APK ,要运行的应用,它作为应用的主工程所在,实现了一套插件的加载和管理的框架,插件都是依托于宿主的APK而存在的。

插件

插件可以想象成每个独立的功能模块封装为一个小的 APK ,可以通过在线配置和更新实现插件 APK 在宿主 APK 中的上线和下线,以及动态更新等功能。

那么为何要使用插件化技术,它有何优势,能给我们带来什么样好处,这里简单列举了以下几点:

让用户不用重新安装 APK 就能升级应用功能,减少发版本频率,增加用户体验。 提供一种快速修复线上 BUG 和更新的能力。 按需加载不同的模块,实现灵活的功能配置,减少服务器对旧版本接口兼容压力。 模块化、解耦合、并行开发、 65535 问题。

简单的描述一下入门的知识

首先我们要知道插件化技术是属于比较复杂一个领域,复杂点在于它涉及知识点广泛,不仅仅是上层做应用架构能力,还要求我们对 Android 系统底层知识需要有一定的认知,这里简单罗列了其中会涉及的知识点:

Android 插件化原理解析——广播插件的实现与安装apk原理解析

然后这一篇文章我带大家来熟悉及实现广播插件,广播主要分两种,一种动态广播,一种静态广播。

插件中动态广播的实现:

启动插件中的动态广播其实和启动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();    }}

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

插件静态广播实现原理:

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

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

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

APK安装时做了什么呢?

  • -将apk文件复制到data/app目录
  • ---使用PackageManager的installPackage接口
  • ---之后调用installPackageAsUser。installPackageAsUser方法中主要完成两件事情。
  • ------是权限检查
  • ------是构建InstallParams,然后发送INIT_COPY的msg,这个mHandler运行在一个HandlerThread中,INIT_COPY主要是确保DefaultContainerService 已 bound,DefaultContainerService是一个应用服务,具体负责实现APK等相关资源文件在内部或外部存储器上的存储工作。而MCS_BOUND中则执行 params.startCopy()
  • ------HandlerParams.startCopy该方法中除了检查重试次数外只是简单的调用了handleStartCopy()及handleReturnCode()方法。 -解析apk信息
  • ---copy到data/app目录的操作后,就到了 handleReturnCode,这个方法又跳转到processPendingInstall()方法 ---这个方法有几个关键步骤,一是installPackageLI(args, res);,这个方法具体执行了解析package和后续操作,而再installPackageLI(args, res);执行完毕 后会走到bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);,会调用backupservice的restoreAtInstall方法,而restoreAtInstall方法最 终又会调用PMS的finishPackageInstall()方法,完成安装。
  • -dexopt操作
  • -更新权限信息
  • -完成安装,发送广播(Intent.ACTION_PACKAGE_ADDED)

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 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 得到pkgpkg = pp.parsePackage(scanFile, parseFlags);

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

Android 插件化原理解析——广播插件的实现与安装apk原理解析

没有办法嘞,我们就只能硬来咯

首先我们实例化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);                }            }

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

Android 插件化原理解析——广播插件的实现与安装apk原理解析

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

最后

如果前面我说的还有点不理解,我在举个例子:

(以下的静A 表示静态广播接收器,同理动B。)

1、 静A (优先级1)

2、 动B(优先级1)

3、 静C (优先级2,后扫描)

4、静D (优先级2,先扫描)

5 、动E (优先级2,先注册)

6、 动F (优先级2,后注册)

当来了一个 有序广播,接收顺序如下:动E > 动F > 静D > 静C > 动B > 静A

当来了一个 普通广播,接收顺序如下:动E > 动F > 动B > 静D > 静C > 静A

Android插件化的发展方向

Android插件化方向主要有2个方向:

  • 1.结合组件化技术,成为一个大中型app的基础框架。
  • 2.完全模拟app运行环境的沙盒系统。

版权声明:内容来源于互联网和用户投稿 如有侵权请联系删除

本文地址:http://0561fc.cn/69436.html