IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android集成其他应用的sdk(aar)如何实现application合并 -> 正文阅读

[移动开发]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);

}

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

为当前的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);
    }
}

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

在上述(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值
//            <!-- 友盟统计 -->
//    <meta-data
//    android:name="UMENG_APPKEY"
//    android:value="*****************" />
//    <meta-data
//    android:name="UMENG_CHANNEL"
//    android:value="${UMENG_CHANNEL_VALUE}" />
    /**
     * 在需要的地方调用上述方法
     */
//    String channelNumber = getAppMetaData(getBaseContext(), "UMENG_CHANNEL");//获取app当前的渠道号

}

子应用配置

无需任何配置

总结

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

在这里插入图片描述

科普一下meta-data:

在这里插入图片描述

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 11:27:25  更:2022-09-13 11:27:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:36:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码