想要翱翔天空,必先丰满自己的羽翼-——插件化:你得先从爬开始学习
(一)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构
(二)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构
(三)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构
(四)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构(五) 大型项目架构:全动态插件化+模块化+Kotlin+协程+Flow+Retrofit+JetPack+MVVM+极限瘦身+极限启动优化+架构示例+全网唯一
(六) 大型项目架构:解析全动态插件化框架WXDynamicPlugin是如何做到全动态化的?
(七) 还在不断升级发版吗?从0到1带你看懂WXDynamicPlugin全动态插件化框架
插件化系列文章太难学了,特别是基础不扎实的,本文将从最简单的插件化开始:
本文学习思路:
- Jetpack Compose是新一代的声明式的UI开发框架,是未来的一种趋势,介绍最基础用法学习
- 如何通过代码直接链接点过去查看官方案例代码?
- 插件化中最简单的 纯代码是怎么做到插件化的?(用compose 代码布局来示例)
用一个Demo 示例工程,带你入门Compose基础开发:
用一个Demo 示例工程,带你入门Compose插件化开发:
示例工程不涉及任何架构,最自由式写法,单条目介绍Compose 相关写法
- 示例基本布局,横向写法,竖向写法:(
Row
,Column
,Box
,ConstraintLayout
) - 示例竖向滚动写法:(
Column + Modifier.verticalScroll(rememberScrollState())
) - 示例竖向滚动写法:(
Row = Modifier.horizontalScroll(rememberScrollState())
) - 示例复杂列表相关写法:(
LazyRow
,LazyColumn
,LazyVerticalGrid +GridItemSpan
,LazyHorizontalGrid +GridItemSpan
) - 示例横竖翻页相关写法:(
HorizontalPager
,VerticalPager
) - 示例Tab栏相关写法:(
TabRow
,ScrollableTabRow
) - 示例底部菜单栏相关写法:(
NavHost
,NavigationBar
) - 示例侧滑单栏相关写法:(
ModalNavigationDrawer
) - 示例头部栏相关写法:(
TopAppBar
) - 示例下拉刷新相关写法:(
Modifier.pullToRefresh
) - 示例网络图片相关写法:(
AsyncImage + coil-compose库
) - 示例吸顶栏相关写法:(
stickyHeader
) - 示例收缩固定头部栏相关写法:(
me.onebone:toolbar-compose:2.3.5 库
) - 示例WebView相关写法:(
AndroidView + WebView
) - 示例基础控件相关写法:(
Image,Icon
,Text
,Checkbox
,Switch
,Button
,TextField
,RadioButton
,Slider
,DropdownMenu
,ExposedDropdownMenuBox
,IconButton
,Slider
,Slider
) - 示例Compose插件化相关写法:
示例部分截图:
本文涉及到demo示例:大多都是官方代码example,下文中会带大家直接查看官方示例代码
因为这是最简单的布局使用,只贴几个案例代码就行了,参考上面表格,和项目代码:
- 以
LazyVerticalGrid
为例:
3. 直接点击上图2中 androidx. compose. foundation. samples. LazyVerticalGridSpanSample就可以查看相关example源代码了
本文是基础介绍:与其说compose插件化,
不如说是纯代码打包成jar 怎么实现插件化?
本文先介绍如何实现?
Compose界面是代码写布局,打包成纯代码jar,如果在apk包内是以dex包装形式存在的
通过ClassLoader加载外部dex文件,android加载外部dex文件涉及到classLoader是
DexClassLoader
怎么通过Android Studio 制作成jar ,再制作成可以 dex文件供
DexClassLoader
加载呢?通过
build-tools\33.0.0
下面d8
执行ANT命令可以将jar转化成xxx_dex.jar (早些年只有java写的是用dx
)通过宿主包含接口,classloader反射加载插件包内接口实现便可以完成
本文只是简单介绍jar最简单的插件化,更多插件化,全动态插件化请参考:
> 大型项目架构:全动态插件化+模块化+Kotlin+协程+Flow+Retrofit+JetPack+MVVM+极限瘦身+极限启动优化+架构示例+全网唯一,
> 以及头部介绍的WXDynamicPlugin系列的7篇文章
- Compose 插件化开搞: 先建一个依赖库
WX-Compose-IPlugin
,里面只包含了一个接口入下,让宿主app工程依赖它:
interface ICompose {
fun setComposeContent(activity: ComponentActivity)
}
- 宿主里面自定义一个
WXClassLoader
(这里我直接从 我的 WXDynamicPlugin工程copy过来了) :
public class WXClassLoader extends DexClassLoader {
protected ClassLoader parent;
public WXClassLoader(String dexPath, String optimizedDirectory, ClassLoader parent) {
super(dexPath, optimizedDirectory, null, parent);
this.parent = parent;
}
public WXClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
this.parent = parent;
}
public <T> T getInterface(Class<T> clazz, String className) {
try {
Class<?> interfaceImplementClass = loadClass(className);
Object interfaceImplement = interfaceImplementClass.newInstance();
return clazz.cast(interfaceImplement);
} catch (ClassNotFoundException | InstantiationException
| ClassCastException | IllegalAccessException e) {
return null;
}
}
}
- 建一个模块工程lib:
WX-Compose-PluginImpl
依赖WX-Compose-IPlugin
工程,在其里面实现代码:
class PluginComposeImpl : ICompose {
override fun setComposeContent(activity: ComponentActivity) {
activity.setContent {
WXComposeXXXTheme {
//这里面都是 @Composable 的方法,全自己写就行了
baseUIXXXX({ paddingvalues ->
layoutExamplexxx(paddingvalues)
}, onClick = {
Toast.makeText(activity, "我是插件里面的", Toast.LENGTH_SHORT).show()
})
}
}
}
}
-
编译
WX-Compose-PluginImpl
工程后,找到该工程下build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar
-
拷贝classes.jar到自己电脑 \build-tools\33.0.0\下面(注意一定要33.0.0以下)然后在该环境变量下打开cmd命令执行命令:(即在该文件所在的上面地址栏敲cmd,然后回车)
d8 --dex --output=classes_dex.jar classes.jar
注意:等号后面是输出文件名, 后面为输入文件名,早起不是kotlin代码写的,是全java代码写的需要执行命令为:
dx --dex --output=classes_dex.jar classes.jar
输出的classes_dex.jar就是我们classloader 能够加载到android能识别的jar了
把classes_dex.jar 重命名成 compose_plugin_lib_dex
(因为下面宿主代码里面写的文件名是compose_plugin_lib_dex) -
宿主app工程代码写法:
class ComposePluginActivity : ComponentActivity() {
val viewModel by viewModels<ComposeViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
getPluginPath()?.let {
WXClassLoader(it, null, classLoader).getInterface(
ICompose::class.java, "com.wx.compose.plugin.compose.PluginComposeImpl"
).setComposeContent(this@ComposePluginActivity)
}
}
private fun getPluginPath(): String? {
val sb = StringBuilder(filesDir.absolutePath).append(File.separator).append("d_dex").append(File.separator).append("compose_plugin_lib_dex")
val file = File(sb.toString())
if (!file.exists()) {
val fileDir = File(file.parent)
if (!fileDir.exists()) {
fileDir.mkdirs()
}
return null
}
return sb.toString()
}
}
- 本示例我没有把插件放在assets下写copy操作,也没有写下载,
查看效果先运行安装debug包,打开插件页
面然后其在手机的data/data/com.wx.compose.plugin/files/d_dex/下上传 我们的 compose_plugin_lib_dex插件到手机, 就可以看到效果啦 如下图:
________________________________________ 我是分割线君 ________________________________________
-
上面 4 和 5 的步骤太麻烦了,可以一键自动搞定吗? 可以的
-
整个项目中在
local.properties
里面配置:build-tools\33.0.0
下面**d8
** 执行ANT命令可以将jar转化成xxx_dex.jar的环境目录,注意此处需要33.0.0或者32.0.0
,没有的可以自行下载
sdk.dir=D:\android_software\android_sdk\android_sdk
workingDirPath=D:\android_software\android_sdk\android_sdk\build-tools\33.0.0\
在主工程下面配置如下:
ext {
def inputSteam = project.rootProject.file('local.properties').newDataInputStream()
def properties = new Properties()
properties.load(inputSteam)
workingDirPath = properties.getProperty('workingDirPath')
}
WX-Compose-PluginImpl
工程的build.gradle
下配置好task
def createCopyTask(buildType) {
def workingDirPath = rootProject.ext.workingDirPath
def outputFile = file("${workingDirPath}compose_plugin_lib.jar")
def outputDexFile = file("${workingDirPath}compose_plugin_lib_dex.jar")
def lastOutputDexFile = file("${rootProject.getBuildDir()}/compose_plugin_lib_dex")
if (lastOutputDexFile.exists()) {
lastOutputDexFile.delete()
}
if (outputDexFile.exists()) {
outputDexFile.delete()
}
if (outputFile.exists()) {
outputFile.delete()
}
def inputFile = file("${getProject().getBuildDir()}/intermediates/aar_main_jar/${buildType}/sync${buildType}LibJars/classes.jar")
def copyTask = tasks.create("assembleCopy${buildType.capitalize()}", Copy) {
group = 'other'
description = "复制${name}到dx环境中."
from(inputFile.getParent()) {
include(inputFile.name)
rename { outputFile.name }
}
into(outputFile.getParent())
}.dependsOn("assemble${buildType.capitalize()}")
def assembleDxCommand = tasks.create("assembleDxCommand", Exec) {
group = 'other'
description = "${name}到dx执行中..."
workingDir workingDirPath
if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
it.commandLine 'cmd', '/c', "d8 --output ${outputDexFile.name} ${outputFile.name}"
} else {
it.commandLine bash, '-c ', "d8 --output ${outputDexFile.name} ${outputFile.name}"
}
}.dependsOn(copyTask.name)
return tasks.create("assembleDxCommandAndCopy") {
doLast {
copy {
from(outputDexFile.getParent()) {
include(outputDexFile.name)
rename { lastOutputDexFile.name }
}
into(lastOutputDexFile.getParent())
}
}
}.dependsOn(assembleDxCommand.name)
}
tasks.whenTaskAdded { task ->
if (task.name == "assembleRelease") {
createCopyTask("Release")
}
}
- 直接点击Gradle中
assembleDxCommandAndCopy
,会自动把 4和5中的步骤 操作完,并把输出的compose_plugin_lib_dex文件放到 整个主工程的build下面:
本文重点用一个Demo示例工程介绍了:
- Compose最基础的布局写法,并输出了和传统XML常用控件对照表
- 如何直接链接到官方example,查看代码
- 纯代码jar 如何做到插件化的(以compose作为示例)
- 同时涉及到ANT编程,如何将jar 转化成 android上能识别的dex文件,Gradle相关Task配置用法
VX号:wgllss ,如果想更多交流请加我VX