Android 插件化入门 (插桩原理)


插桩方式实现插件化

项目机构如下:app为宿主application ,pluginstand为中间连接作用,负责定义主app和插件之间组件传递规则。shop用来打包插件apk。
在这里插入图片描述

  • 主app和shop同时依赖 pluginstand
  • 为了模拟实现网络下载插件这里采用文件拷贝的方式将apk存储到内存卡,拷贝到/data/data/目录下。
  • app 中,首先定义 PluginManager 用来解析下载下来的插件文件,代码如下:
  • 原理就是通过解析 apk 文件格式来获取想要的内容。
// 设置单利模式
object PluginManager {
	// 获取 dexClassLoader 
    private var dexClassLoader: DexClassLoader? = null
	// 获取资源
    private var resources: Resources? = null
	// 获取文件包信息
    private var packageInfo: PackageInfo? = null
	
    fun loadPath(context: Context) {
        // context.getDir 保存位置 /data/user/0/com.lu.plugin/app_plugin
        // Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可以使用Context.MODE_APPEND
        // Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
        // Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件。
        // MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;
        // MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。
        val filesDir = context.getDir("plugin", Context.MODE_PRIVATE)
        val name = "shop-debug.apk"
        val path = File(filesDir, name).absolutePath
        val packageManager = context.packageManager
        // 未安装的 Apk 文件,想要解析出 Apk 文件中的额外信息,PM 中,也有对应的 Api。
        // 非常的方便,直接使用 getPackageArchiveInfo() 即可
        packageInfo = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES)
        // activity
        val dex = context.getDir("dex", Context.MODE_PRIVATE)
        // DexClassLoader:用于加载SD卡上的class.dex、jar、或apk文件。
        dexClassLoader = DexClassLoader(path, dex.absolutePath, null, context.classLoader)
        // resource
        try {
            val manager = AssetManager::class.java.newInstance()
            val addAssetPath =
                AssetManager::class.java.getMethod("addAssetPath", String::class.java)
            addAssetPath.invoke(manager, path)
            resources = Resources(
                manager,
                context.resources.displayMetrics,
                context.resources.configuration
            )
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    fun getResources(): Resources? {
        return resources
    }

    fun getDexClassLoader(): DexClassLoader? {
        return dexClassLoader
    }

    fun getPackageInfo(): PackageInfo? {
        return packageInfo
    }
}
  • MainActivity中负责下载和跳转插件的页面
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }
	// 下载插件 之前用Kotlin的文件拷贝会出现一些文件File文件读取的文图,还没查找原因 
	// 改为自己写流的方式
    fun downLoadPlugin(view: View) {
//        val name = "shop-debug.apk"
//        val desFile = File(Environment.getExternalStorageDirectory(), name)
//
//        val targetFile = getDir("plugin", Context.MODE_APPEND)
//        val copyRecursively = desFile.copyRecursively(targetFile, true)
//        Log.e("main", copyRecursively.toString());
//        Log.e("path = ", targetFile.absolutePath)
//        if (copyRecursively) {
//            PluginManager.loadPath(this)
//        }

        val filesDir = getDir("plugin", Context.MODE_PRIVATE)
        val name = "shop-debug.apk"
        val filePath = File(filesDir, name).absolutePath
        val file = File(filePath)
        if (file.exists()) {
            file.delete()
        }
        var inputStream: InputStream? = null
        var outputStream: FileOutputStream? = null
        try {
            inputStream = FileInputStream(File(Environment.getExternalStorageDirectory(), name))
            outputStream = FileOutputStream(filePath)
            var len = 0
            val buffer = ByteArray(1024)
            while (inputStream.read(buffer).also { len = it } != -1) {
                outputStream.write(buffer, 0, len)
            }
            val f = File(filePath)
            if (f.exists()) {
                Toast.makeText(this, "dex overwrite", Toast.LENGTH_SHORT).show()
            }
            PluginManager.loadPath(this)
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            try {
                outputStream?.close()
                inputStream?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }
	// 具体跳转 首先跳到 app 的 ProxyActivity 由它来执行具体的跳转逻辑
    fun toShopPlugin(view: View) {
        val intent = Intent(this, ProxyActivity::class.java)
        //  PluginManager.getPackageInfo()!!.activities[0] 中的顺序是按照 manifests 中的注册顺序由上到下的顺序
        intent.putExtra(
            "className", PluginManager.getPackageInfo()!!.activities[0].name
        )
        startActivity(intent)
    }
}

  • ProxyActivity 具体代码:由于插件apk是没有进行安装的,所有插件中最麻烦的就是如何获取Context,所以我们把所有需要获取 Context 的地方都需要重写。这里只是写了一部分,包括生命周期也需要重写。因为插件的 activity 中是没有 this 所谓的 context 的。

/**
 * 通过代理 activity 做具体跳转
 */
class ProxyActivity : Activity() {
    private var pluginInterface: PluginInterface? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        init()
    }

    private fun init() {
        // 获取要跳转的 className 传进来的 className 是在 apk 中的 manifest 文件解析出来的
        val className = intent.getStringExtra("className")
        className?.let {
            // 通过 classLoader 根据 className 获取 class
            val aClass = classLoader!!.loadClass(className)
            // 获取构造方法
            val constructor = aClass.getConstructor()
            // 根据构造方法创建具体要跳转的 activity
            val realClass = constructor.newInstance()
            // 跳转的页面必须也实现接口,用来传递数据
            pluginInterface = realClass as PluginInterface?
            pluginInterface?.pluginAttach(this)
            // bundle 中可以传递数据
            val bundle = Bundle()
            pluginInterface?.pluginOnCreate(bundle)
        }
    }

    override fun startActivity(intent: Intent) {
        val className = intent.getStringExtra("className")
        val intent1 = Intent(this, ProxyActivity::class.java)
        intent1.putExtra("className", className)
        super.startActivity(intent1)
    }

    //重写加载类
    override fun getClassLoader(): ClassLoader? {
        return PluginManager.getDexClassLoader()
    }

    //重写加载资源
    override fun getResources(): Resources? {
        return PluginManager.getResources()
    }
    
    override fun onStart() {
        super.onStart()
        pluginInterface?.pluginOnStart()
    }

    override fun onResume() {
        super.onResume()
        pluginInterface?.pluginOnResume()
    }

    override fun onStop() {
        super.onStop()
        pluginInterface?.pluginOnStop()
    }

    override fun onDestroy() {
        super.onDestroy()
        pluginInterface?.pluginOnDestroy()
    }
}
  • plugnstand 插件中存有接口如下:
interface PluginInterface {
    fun pluginAttach(proxyActivity: Activity)
    // 生命周期
    fun pluginOnCreate(saveInstanceState: Bundle?)
    fun pluginOnStart()
    fun pluginOnResume()
    fun pluginOnPause()
    fun pluginOnStop()
    fun pluginOnDestroy()
    fun pluginOnSaveInstanceState(outState: Bundle?)
    fun pluginOnTouchEvent(event: MotionEvent?): Boolean
    fun pluginOnBackPressed()
}
  • shop 插件中
  • 定义 BaseActivity,实现 PluginInterface 接口。that 为插件中的 Context
open class BaseActivity : Activity(), PluginInterface {
    protected var that: Activity? = null
    override fun pluginAttach(proxyActivity: Activity) {
        that = proxyActivity
    }

    override fun setContentView(view: View?) {
        if (that != null) {
            that!!.setContentView(view)
        } else {
            super.setContentView(view)
        }
    }

    override fun setContentView(layoutResID: Int) {
        if (that != null) {
            that!!.setContentView(layoutResID)
        } else {
            super.setContentView(layoutResID)
        }
    }

    override fun startActivity(intent: Intent?) {
//        val intent = Intent()
        intent?.let {
            intent.putExtra("className", intent.component?.className)
            that?.startActivity(intent)
        }

    }

    override fun <T : View> findViewById(id: Int): T {
        return that!!.findViewById(id)
    }

    override fun pluginOnCreate(saveInstanceState: Bundle?) {

    }

    override fun pluginOnStart() {
    }

    override fun pluginOnResume() {
    }

    override fun pluginOnPause() {
    }

    override fun pluginOnStop() {
    }

    override fun pluginOnDestroy() {
    }

    override fun pluginOnSaveInstanceState(outState: Bundle?) {
    }

    override fun pluginOnTouchEvent(event: MotionEvent?): Boolean {
        return false
    }

    override fun pluginOnBackPressed() {
    }

}
  • shop的MainActiviy很简单,就是一个图片,有个跳转的功能

class ShopMainActivity : BaseActivity() {

    override fun pluginOnCreate(saveInstanceState: Bundle?) {
        super.pluginOnCreate(saveInstanceState)
        setContentView(R.layout.activity_main)
        img.setOnClickListener {
            val intent = Intent(that, ShopSecondActivity::class.java)
           startActivity(intent)
        }
    }

}

  • 这样一个插桩就完成了。service和broadCast也是一样的。在 pluginstand 中定义规则,然后重写需要重写的东西。service记得注册等

demo代码下载连接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值