• Android集成其他应用的sdk(aar)如何实现application合并


    每个android应用程序中都有一个唯一的上下文Application对象,这个Application一般我们不需要去创建,应用启动时,系统会自动创建一个默认的Application实例。由于Application在整个应用中是唯一的,它是一个单例。所以,有的应用就可能希望利用Application来完成一些工作。

    在android中,实现一个自定义的Application是很简单的。直接自定义一个类继承Application,然后在AndroidManifest.xml的application节点属性里将android:name设置为你自定义的这个application类即可。

    如果在集成sdk时,涉及到子应用也有自己的application。那么主应用如何将多个子应用的application一起合并呢?

    我们先来尝试一下,如果子应用有自己的application ,如果不将主应用与子应用的application进行聚合,会出现什么情况呢?

    答:子应用会注册到主应用的application 上,会将主应用的application覆盖掉。如下是主应用的清单文件,其中application组件下的android:name 属性的值是子应用的application。
    在这里插入图片描述
    此时会将主应用的application给覆盖掉。导致打开主应用后,直接加载的是子应用的application。如果一个主应用集成多个子aar时,就会出现混乱。所以这里应该使用多继承策略。

    合并application的思想

    下面我会提供一个案例:一主两从

    一主: 即主应用。下面的项目成为 H应用
    二从: 两个子应用,也就是将要接入到主应用的两个子应用 分别为 A应用和B应用

    合并application无非就是在加载主应用的application时也要动态的去加载子应用的application,使得主子应用的application具备各自的生命周期。那这里有两种备用方案:
    1、需要使用子应用的Application,就把子应用的application实现的方法拷贝到主应用中,这种无疑就是太麻烦,方法太笨,如果需要集成多个子应用就会有更多的工作量。
    2、或者自定义一个Application去继承子应用的Application,在Application对应的接口里调用他们的方法。这种也是比较麻烦。

    但是,现在问题来了,因为主应用整套框架的核心思想就是,兼顾所有渠道。不可能直接在主应用的AndroidManifest.xml中配置上某个子应用的Application或者自定义一个Application,去继承某一个子应用的Application。我们必须想办法越过去。

    幸运的是,方法总是有的。在这里,我在主应用抽象层中定义了一个Application监听器IApplicationListener,同时定义一个继承了Application类的HostApplication。在HostApplication类中,维护了一个IApplicationListener实例。这样在HostApplication的onCreate,attackBaseContext等方法中,会间接的调用IApplicationListener中相应的接口。

    这样,在具体接入子SDK的时候,我们就定一个适配器模式的类来继承渠道自己的Application,同时实现主应用抽象层的IApplicationListener接口。然后在IApplicationListener的接口实现中,直接调用基类(子SDK的Application)的对应方法。

    如果子应用没有自己的application 。那么就直接将HostApplication配置到AndroidManifest.xml的application节点的android:name属性中。这样在打包时会自动注册到主应用的清单文件中。

    上述思想很明确:就是静态代理

    静态代理模式
    1、必须要求我们定一个一个父类或接口 这个接口定义了application类的公共方法。
    2、必须要定义代理对象,主应用通过代理对象去访问目标对象。
    3、多个子应用需要定义多个代理对象。

    在这里我们再统一一下合并思路:

    主应用配置

    (1)首先在主应用中定义一个接口

    public interface IApplicationListener {
    
        void onProxyCreate();
    
        void onProxyAttachBaseContext(Context base);
    
        void onProxyConfigurationChanged(Configuration newConfig);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    (2)定义各个子sdk的代理application对象

    如果A应用的本身的sdk中定义的application如下:

    public class MyApplication extends Application {
        public MyApplication() {
        }
    
        public void onCreate() {
            super.onCreate();
            Toast.makeText(this, "this is A plugin application", 0).show();
        }
    
        public void onConfigurationChanged(@NonNull Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
        }
    
        protected void attachBaseContext(Context base) {
            super.attachBaseContext(base);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    为当前的MyApplication设置代理对象 AProxyApplication,让此代理对象继承MyApplication,并实现IApplicationListener接口

    public class AProxyApplication extends MyApplication implements IApplicationListener {
    
        @Override
        public void onProxyCreate() {
           super.onCreate();
        }
    
        @Override
        public void onProxyAttachBaseContext(Context base) {
            super.attachBaseContext(base);
        }
    
        @Override
        public void onProxyConfigurationChanged(Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    (3)定义主应用中的HostApplication。这里是关键,这里主要实现的是:
    通过接口来聚合目标对象 简单来说就是定义接口的引用,获取子应用的代理对象(通过反射的方式,因为application具备完整的生命周期,aar包被依赖打包到主应用后所有的对象实例都已经初始化,所以在运行时需要通过反射方式获取,这里也不知道理解的对不对),通过代理对象去调用目标对象中的方法,目标对象为子应用的application。

    package com.hzs.plguin;
    
    import android.app.Application;
    import android.content.Context;
    import android.content.res.Configuration;
    import android.util.Log;
    import android.widget.Toast;
    
    /**
     * 作者:  senda-志森
     * 创建于  2022/8/26
     * 说明:主应用
     **/
    public class HostApplication  extends Application {
    
        private IApplicationListener listener; // 根据接口聚合目标对象
    
        private static final String PROXY_NAME = "com.plugin.sdk.FirstStandPluginApplication";
    
        @Override
        public void onCreate() {
            super.onCreate();
            Log.i("Host","加载主应用的application");
            // 加载子应用的sdk中的application
            if( listener != null){
                listener.onProxyCreate();
            }
        }
    
        @Override
        public void attachBaseContext(Context base) {
            super.attachBaseContext(base);
            //获取代理对象
            this.listener = initProxyApplication();
    
            if( this.listener != null){
                this.listener.onProxyAttachBaseContext(base);
            }
        }
    
        public void onConfigurationChanged(Configuration newConfig){
            super.onConfigurationChanged(newConfig);
            if( this.listener != null){
                this.listener.onProxyConfigurationChanged(newConfig);
            }
        }
    
        private IApplicationListener initProxyApplication() {
            String proxyAppName = SDKTools.getAppMetaData(this, PROXY_NAME);
            if(proxyAppName == null ){
                return null;
            }
            try {
                Class clazz = Class.forName(proxyAppName);
                return (IApplicationListener)clazz.newInstance();
    
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
    
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    在上述(3)步骤以前还漏下关键的一步,就是需要在主应用的清单文件中进行声明子sdk 的代理application

    在这里插入图片描述
    SDKTools 代码

    package com.hzs.plguin;
    
    import android.content.Context;
    import android.content.pm.ApplicationInfo;
    import android.content.pm.PackageManager;
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.util.Log;
    
    /**
     * 作者:  senda-志森
     * 创建于  2022/9/11
     * 说明:
     **/
    public class SDKTools {
        // 获取application中meta-data 中的key value 值
        /**
         * 根据key从Application中返回的Bundle中获取value
         * @param context
         * @param key
         * @return
         */
        private String getMetaDataStringApplication(Context context ,String key) {
            Bundle bundle = getAppMetaDataBundle(context.getPackageManager(), context.getPackageName());
            if (bundle != null && bundle.containsKey(key)) {
                return bundle.getString(key);
            }
            return null;
        }
    
        /**
         * 获取Application中的meta-data.
         *
         * @param packageManager
         * @param packageName
         * @return
         */
        private Bundle getAppMetaDataBundle(PackageManager packageManager,String packageName) {
            Bundle bundle = null;
            try {
                ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
                        PackageManager.GET_META_DATA);
                bundle = ai.metaData;
            } catch (PackageManager.NameNotFoundException e) {
                Log.e("getMetaDataBundle", e.getMessage(), e);
            }
            return bundle;
        }
    
        /**
         * 获取app当前的渠道号或application中指定的meta-data
         *
         * @return 如果没有获取成功(没有对应值,或者异常),则返回值为空
         */
        public static String getAppMetaData(Context context, String key) {
            if (context == null || TextUtils.isEmpty(key)) {
                return null;
            }
            String channelNumber = null;
            try {
                PackageManager packageManager = context.getPackageManager();
                if (packageManager != null) {
                    ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
                    if (applicationInfo != null) {
                        if (applicationInfo.metaData != null) {
                            channelNumber = applicationInfo.metaData.getString(key);
                        }
                    }
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
            return channelNumber;
        }
    
    //    注:上面所说的key指的是清单文件中你在注册友盟统计时的"UMENG_CHANNEL"(即getChannelNumber(Context context, String key)方法的第二个参数key传入"UMENG_CHANNEL"),而不是"UMENG_APPKEY",也不是任何一个value值
    //            
    //    
    //    android:name="UMENG_APPKEY"
    //    android:value="*****************" />
    //    
    //    android:name="UMENG_CHANNEL"
    //    android:value="${UMENG_CHANNEL_VALUE}" />
        /**
         * 在需要的地方调用上述方法
         */
    //    String channelNumber = getAppMetaData(getBaseContext(), "UMENG_CHANNEL");//获取app当前的渠道号
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    子应用配置

    无需任何配置

    总结

    至此application合并完毕。如果有其他的业务驱动,也有可能在此基础上进行改造,主要要理解其中的解决思想,为了让大家更好的理解静态代理,下面贴一个类图:对角线的为聚合关系

    在这里插入图片描述

    科普一下meta-data:

    在这里插入图片描述

  • 相关阅读:
    十八、CANdelaStudio深入-Data Types
    java 基础5道题
    李沐机器学习入门
    软考 系统架构设计师系列知识点之边缘计算(5)
    kubeadm部署k8s教程(4)---基本资源概念
    京东API接口大全
    js分割字符串
    CAMx的空气质量模拟及污染来源解析丨大气臭氧来源解析模拟与臭氧成因分析
    创建SpringCloudGateWay
    .NET8 WebApplication剖析
  • 原文地址:https://blog.csdn.net/superzhang6666/article/details/126542654