文章目录
插桩方式实现插件化
项目机构如下: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记得注册等