【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )

简介: 【Android 插件化】“ 插桩式 “ 插件化框架 ( 运行应用 | 代码整理 )

文章目录

Android 插件化系列文章目录

一、编译 " 插件 " 模块

二、首次编译运行 " 宿主 " 模块

三、第二次运行 " 宿主 " 模块

四、" 宿主 " 模块代码

五、" 插件 " 模块代码

六、" 依赖库 " 模块代码

1、插件 Activity 接口

2、插件 Activity 基类

3、代理 Activity ( 桩 )

4、插件管理器

七、博客资源





一、编译 " 插件 " 模块


首先编译 " 插件 " 模块 , 生成 APK 安装包 ;


image.png






二、首次编译运行 " 宿主 " 模块


编译运行 " 宿主 " 模块 , 目的是为了生成 getExternalFilesDir(null).getAbsolutePath() 目录 ;


该目录在应用首次运行时自动生成 , 应用卸载后 , 自动删除 ;


该目录的绝对路径名称如下 :


/sdcard/Android/data/kim.hsl.plugin/files



拷贝插件包 : 将 上一章节 编译的插件包 apk 拷贝到 /sdcard/Android/data/kim.hsl.plugin/files 目录中 ;


在 " Device FIle Explorer " 面板中 , 右键点击 /sdcard/Android/data/kim.hsl.plugin/files 目录 , 点击 " Upload " ,

image.png



再弹出的对话框中 , 选择编译生成的 apk 安装包 , 上传到该目录中 ;


image.png


上传完成 ;


image.png






三、第二次运行 " 宿主 " 模块


第一次运行 " 宿主 " 模块 后 , 生成 /sdcard/Android/data/kim.hsl.plugin/files 目录 , 将 " 插件 " 模块编译后的插件包拷贝到该目录中 ;


第二次运行时 , 在 onCreate 方法中就会加载解析该插件包 , 解析 dex 文件与资源文件 ;


点击跳转按钮 , 即可跳转到插件模块 Activity 中 ;


image.png






四、" 宿主 " 模块代码


package kim.hsl.plugin;
import androidx.appcompat.app.AppCompatActivity;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import com.example.plugin_core.PluginManager;
import com.example.plugin_core.ProxyActivity;
import pub.devrel.easypermissions.EasyPermissions;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "getClassLoader() : " +
                getClassLoader());
        Log.i(TAG, "getClassLoader().getParent() : " +
                getClassLoader().getParent());
        Log.i(TAG, "getClassLoader().getParent().getParent() : " +
                getClassLoader().getParent().getParent());
        EasyPermissions.requestPermissions(
                this,
                "权限申请原理对话框 : 描述申请权限的原理",
                100,
                // 下面是要申请的权限 可变参数列表
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
        );
        /*
            加载 " 插件 " 模块的 apk 文件
            先将该插件包拷贝到
         */
        String path = getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk";
        Log.i(TAG, "path : " + path);
        PluginManager.getInstance().loadPlugin(this, path);
    }
    public void onClick(View view) {
        // 跳转到 plugin_core 模块的代理 Activity
        // 首先要获取 " 插件 " 模块中的入口 Activity 类
        Intent intent = new Intent(this, ProxyActivity.class);
        // 获取  " 插件 " 模块中的 Activity 数组信息
        ActivityInfo[] activityInfos = PluginManager.getInstance().getmPackageInfo().activities;
        // 获取的插件包中的 Activity 不为空 , 才进行界面跳转
        if (activityInfos.length > 0) {
            Log.i(TAG, "CLASS_NAME : " + activityInfos[0].toString());
            // 这里取插件包中的第 0 个 Activity
            // 次序就是在 AndroidManifest.xml 清单文件中定义 Activity 组件的次序
            // 必须将 Launcher Activity 定义在第一个位置
            // 不能在 Launcher Activity 之前定义 Activity 组件
            // 传入的是代理的目标组件的全类名
            intent.putExtra("CLASS_NAME", activityInfos[0].name);
            startActivity(intent);
        }
    }
}





五、" 插件 " 模块代码


package kim.hsl.plugin;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.example.plugin_core.BaseActivity;
public class PluginActivity extends BaseActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);
    }
}





六、" 依赖库 " 模块代码



1、插件 Activity 接口


package com.example.plugin_core;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
public interface PluginActivityInterface {
    /**
     * 绑定代理 Activity
     * @param proxyActivity
     */
    void attach(Activity proxyActivity);
    void onCreate(Bundle savedInstanceState);
    void onStart();
    void onResume();
    void onPause();
    void onStop();
    void onDestroy();
    void onSaveInstanceState(Bundle outState);
    boolean onTouchEvent(MotionEvent event);
    void onBackPressed();
}



2、插件 Activity 基类


package com.example.plugin_core;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
/**
 * " 插件 " 模块中的 Activity 类都继承该类
 * 具体的 Activity 业务类的父类
 */
public class BaseActivity extends AppCompatActivity implements PluginActivityInterface {
    /**
     * 注入的 Activity , 代理该 Activity 类作为上下文
     */
    private Activity proxyActivity;
    /**
     * 注入代理 Activity
     * 在 ProxyActivity 中将代理 Activity 组件注入进来
     * @param proxyActivity
     */
    @Override
    public void attach(Activity proxyActivity) {
        this.proxyActivity = proxyActivity;
    }
    @Override
    public void setContentView(int layoutResID) {
        // 调用代理 Activity 中的 setContentView 方法
        if (proxyActivity != null) {
            proxyActivity.setContentView(layoutResID);
        }else{
            super.setContentView(layoutResID);
        }
    }
    @Override
    public void setContentView(View view) {
        // 调用代理 Activity 中的 setContentView 方法
        if (proxyActivity == null) {
            proxyActivity.setContentView(view);
        }else{
            super.setContentView(view);
        }
    }
    @Override
    public <T extends View> T findViewById(int id) {
        if (proxyActivity == null) {
            return proxyActivity.findViewById(id);
        }else{
            return super.findViewById(id);
        }
    }
    @Override
    public void startActivity(Intent intent) {
        if (proxyActivity == null) {
            intent.putExtra("className", intent.getComponent().getClassName());
            proxyActivity.startActivity(intent);
        }else{
            super.startActivity(intent);
        }
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {
    }
    @SuppressLint("MissingSuperCall")
    @Override
    public void onSaveInstanceState(Bundle outState) {
    }
}



3、代理 Activity ( 桩 )


package com.example.plugin_core;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
/**
 * 插件化框架核心类
 */
public class PluginManager {
    /**
     * 类加载器
     * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象
     */
    private DexClassLoader mDexClassLoader;
    /**
     * 从插件包 apk 中加载的资源
     */
    private Resources mResources;
    /**
     * 插件包信息类
     */
    private PackageInfo mPackageInfo;
    /**
     * 加载插件的上下文对象
     */
    private Context mContext;
    /**
     * PluginManager 单例
     */
    private static PluginManager instance;
    private PluginManager(){
    }
    /**
     * 获取单例类
     * @return
     */
    public static PluginManager getInstance(){
        if (instance == null) {
            instance = new PluginManager();
        }
        return instance;
    }
    /**
     * 加载插件
     * @param context 加载插件的应用的上下文
     * @param loadPath 加载的插件包地址
     */
    public void loadPlugin(Context context, String loadPath) {
        this.mContext = context;
        // DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
        // ( 模式必须是 Context.MODE_PRIVATE )
        File optimizedDirectory = context.getDir("cache_plugin", Context.MODE_PRIVATE);
        // 创建 DexClassLoader
        mDexClassLoader = new DexClassLoader(
                loadPath, // 加载路径
                optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
                null,
                context.getClassLoader() // DexClassLoader 加载器的父类加载器
        );
        // 加载资源
        try {
            // 通过反射创建 AssetManager
            AssetManager assetManager = AssetManager.class.newInstance();
            // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
            Method addAssetPathMethod = assetManager.
                    getClass().
                    getDeclaredMethod("addAssetPath", String.class);
            // 调用反射方法
            addAssetPathMethod.invoke(assetManager, loadPath);
            // 获取资源
            mResources = new Resources(
                    assetManager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration()
            );
            // 获取插件包中的 Activity 类信息
            mPackageInfo = context.getPackageManager().getPackageArchiveInfo(
                    loadPath, // 加载的插件包路径
                    PackageManager.GET_ACTIVITIES); // 获取的包信息类名
        } catch (IllegalAccessException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (InstantiationException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // getDeclaredMethod 反射方法异常
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // invoke 执行反射方法异常
            e.printStackTrace();
        }
    }
    /**
     * 获取类加载器
     * @return
     */
    public DexClassLoader getmDexClassLoader() {
        return mDexClassLoader;
    }
    /**
     * 获取插件包中的 Package 信息
     * @return
     */
    public PackageInfo getmPackageInfo() {
        return mPackageInfo;
    }
    /**
     * 获取插件包中的资源
     * @return
     */
    public Resources getmResources() {
        return mResources;
    }
}


4、插件管理器


package com.example.plugin_core;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
/**
 * 插件化框架核心类
 */
public class PluginManager {
    /**
     * 类加载器
     * 用于加载插件包 apk 中的 classes.dex 文件中的字节码对象
     */
    private DexClassLoader mDexClassLoader;
    /**
     * 从插件包 apk 中加载的资源
     */
    private Resources mResources;
    /**
     * 插件包信息类
     */
    private PackageInfo mPackageInfo;
    /**
     * 加载插件的上下文对象
     */
    private Context mContext;
    /**
     * PluginManager 单例
     */
    private static PluginManager instance;
    private PluginManager(){
    }
    /**
     * 获取单例类
     * @return
     */
    public static PluginManager getInstance(){
        if (instance == null) {
            instance = new PluginManager();
        }
        return instance;
    }
    /**
     * 加载插件
     * @param context 加载插件的应用的上下文
     * @param loadPath 加载的插件包地址
     */
    public void loadPlugin(Context context, String loadPath) {
        this.mContext = context;
        // DexClassLoader 的 optimizedDirectory 操作目录必须是私有的
        // ( 模式必须是 Context.MODE_PRIVATE )
        File optimizedDirectory = context.getDir("cache_plugin", Context.MODE_PRIVATE);
        // 创建 DexClassLoader
        mDexClassLoader = new DexClassLoader(
                loadPath, // 加载路径
                optimizedDirectory.getAbsolutePath(), // apk 解压缓存目录
                null,
                context.getClassLoader() // DexClassLoader 加载器的父类加载器
        );
        // 加载资源
        try {
            // 通过反射创建 AssetManager
            AssetManager assetManager = AssetManager.class.newInstance();
            // 通过反射获取 AssetManager 中的 addAssetPath 隐藏方法
            Method addAssetPathMethod = assetManager.
                    getClass().
                    getDeclaredMethod("addAssetPath", String.class);
            // 调用反射方法
            addAssetPathMethod.invoke(assetManager, loadPath);
            // 获取资源
            mResources = new Resources(
                    assetManager,
                    context.getResources().getDisplayMetrics(),
                    context.getResources().getConfiguration()
            );
            // 获取插件包中的 Activity 类信息
            mPackageInfo = context.getPackageManager().getPackageArchiveInfo(
                    loadPath, // 加载的插件包路径
                    PackageManager.GET_ACTIVITIES); // 获取的包信息类名
        } catch (IllegalAccessException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (InstantiationException e) {
            // 调用 AssetManager.class.newInstance() 反射构造方法异常
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            // getDeclaredMethod 反射方法异常
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            // invoke 执行反射方法异常
            e.printStackTrace();
        }
    }
    /**
     * 获取类加载器
     * @return
     */
    public DexClassLoader getmDexClassLoader() {
        return mDexClassLoader;
    }
    /**
     * 获取插件包中的 Package 信息
     * @return
     */
    public PackageInfo getmPackageInfo() {
        return mPackageInfo;
    }
    /**
     * 获取插件包中的资源
     * @return
     */
    public Resources getmResources() {
        return mResources;
    }
}





七、博客资源


博客资源 :


GitHub : https://github.com/han1202012/Plugin


目录
相关文章
|
20天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
46 14
|
23天前
|
Java Linux 数据库
探索安卓开发:打造你的第一款应用
在数字时代的浪潮中,每个人都有机会成为创意的实现者。本文将带你走进安卓开发的奇妙世界,通过浅显易懂的语言和实际代码示例,引导你从零开始构建自己的第一款安卓应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇门,让你的创意和技术一起飞扬。
|
21天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
21天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
28 0
|
Java 开发工具 Android开发
Android Studio运行报错:无法访问XXX......请删除该文件或确保该文件位于正确的类路径子目录中
今天运行一个项目发现运行不起来了,报错 错误: 无法访问XXX 错误的类文件: C:\Users\xxx\.gradle\caches\transforms-2\files-2.1\xxx\xxx(xxx/xxx/xxx.class) 错误的 RuntimeInvisibleParameterAnnotations 属性: xxxx 请删除该文件或确保该文件位于正确的类路径子目录中。
1570 0
|
Android开发 Kotlin
【错误记录】Android Studio 运行报错 ( There is not enough memory to perform the requested operation. )
【错误记录】Android Studio 运行报错 ( There is not enough memory to perform the requested operation. )
714 0
【错误记录】Android Studio 运行报错 ( There is not enough memory to perform the requested operation. )
|
1月前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
1月前
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
27 1
|
1月前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
20天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
42 19