Compose插件化:一个Demo带你入门Compose,同时带你入门插件化开发

5,984 阅读8分钟

641.webp

想要翱翔天空,必先丰满自己的羽翼-——插件化  :  你得先从爬开始学习
(一)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构
(二)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构
(三)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构
(四)零反射,零HooK,全动态化,插件化框架,全网唯一结合启动优化的插件化架构

(五) 大型项目架构:全动态插件化+模块化+Kotlin+协程+Flow+Retrofit+JetPack+MVVM+极限瘦身+极限启动优化+架构示例+全网唯一

(六) 大型项目架构:解析全动态插件化框架WXDynamicPlugin是如何做到全动态化的?
(七) 还在不断升级发版吗?从0到1带你看懂WXDynamicPlugin全动态插件化框架
(八) Compose插件化:一个Demo带你入门Compose,同时带你入门插件化开发
(九) 花式高阶:插件化之Dex文件的高阶用法,极少人知道的秘密
(十) Compose的全动态插件化框架支持了,已更新到AGP 8.6,Kotlin2.0.20,支持Compose
(十一)按需下载!!全动态插件化框架WXDynamicPlugin解析怎么支持的

一、 前言

插件化系列文章太难学了,特别是基础不扎实的,本文将从最简单的插件化开始:

本文学习思路:

  1. Jetpack Compose是新一代的声明式的UI开发框架,是未来的一种趋势,介绍最基础用法学习
  2. 如何通过代码直接链接点过去查看官方案例代码?
  3. 插件化中最简单的 纯代码是怎么做到插件化的?(用compose 代码布局来示例)
  • 常用传统XML控件和Compose控件对照表 Sheet1.png

用一个Demo 示例工程,带你入门Compose基础开发:
用一个Demo 示例工程,带你入门Compose插件化开发:

二、示例工程项目介绍:

示例工程不涉及任何架构,最自由式写法,单条目介绍Compose 相关写法

  • 示例基本布局,横向写法,竖向写法:(Row,Column,Box,ConstraintLayout
  • 示例竖向滚动写法:(Column + Modifier.verticalScroll(rememberScrollState())
  • 示例竖向滚动写法:(Row = Modifier.horizontalScroll(rememberScrollState())
  • 示例复杂列表相关写法:(LazyRowLazyColumnLazyVerticalGrid +GridItemSpanLazyHorizontalGrid +GridItemSpan
  • 示例横竖翻页相关写法:(HorizontalPagerVerticalPager
  • 示例Tab栏相关写法:(TabRowScrollableTabRow
  • 示例底部菜单栏相关写法:(NavHostNavigationBar
  • 示例侧滑单栏相关写法:(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,下文中会带大家直接查看官方示例代码

项目地址github
项目地址gitee

三、项目详情

因为这是最简单的布局使用,只贴几个案例代码就行了:

  1. 类似RecyclerView 网格布局:

@Composable
fun girdLayoutExample(innerPadding: PaddingValues, viewModel: ComposeViewModel) {
    val datas by viewModel.datas.observeAsState(initial = emptyList())
    LazyVerticalGrid(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight()
//            .verticalScroll(rememberScrollState())
        , columns = GridCells.Fixed(2)
    ) {
        items(datas) { data ->
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .horizontalScroll(rememberScrollState())
                    .height(80.dp), verticalAlignment = Alignment.CenterVertically
            ) {
                Image(
                    painter = painterResource(id = data.resID), contentDescription = "小姐姐", modifier = Modifier
                        .size(80.dp)
                        .padding(5.dp), contentScale = ContentScale.Fit
                )
                AsyncImage(
                    model = data.imgUrl, contentDescription = "", contentScale = ContentScale.Crop
                )
                val modelBuilder = ImageRequest.Builder(LocalContext.current).data(data.imgUrl ?: "").crossfade(false).allowHardware(true).build()
                Image(
                    painter = rememberAsyncImagePainter(model = modelBuilder), contentDescription = "小姐姐", modifier = Modifier
                        .size(80.dp)
                        .padding(5.dp), contentScale = ContentScale.Crop
                )
            }
        }
    }
}
  1. 类似RecyclerView 多个Item布局
@Composable
fun LazyVerticalGridSpanSample(paddingValues: PaddingValues) {
    val sections = (0 until 25).toList().chunked(6)
    val items22 = remember { listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5", "Item 6") }

    LazyVerticalGrid(
        modifier = Modifier.padding(paddingValues), columns = GridCells.Fixed(3), horizontalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        sections.forEachIndexed { index, items ->
            item(span = { GridItemSpan(maxLineSpan) }) {
                Text(
                    "This is section $index",
                    Modifier
                        .border(1.dp, Color.Gray)
                        .height(80.dp)
                        .wrapContentSize()
                )
            }
            items(items, span = { GridItemSpan(1) }) {
                Text(
                    "Item $it",
                    Modifier
                        .border(1.dp, Color.Blue)
                        .height(80.dp)
                        .wrapContentSize()
                )
            }

            item(span = { GridItemSpan(3) }) {
                LazyRow(
                    modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(8.dp)
                ) {
                    items(items22) { item ->
                        Box(
                            modifier = Modifier
                                .background(Color.Green)
                                .padding(16.dp)
                                .width(120.dp)
                                .height(120.dp), contentAlignment = Alignment.Center
                        ) {
                            Text(text = "AAA $item", color = Color.White)
                        }
                    }
                }
            }
        }
    }
}

3. TabRow + HorizontalPager Tab标签 和 滑动联动翻页:


@SuppressLint("UnrememberedMutableState")
@Composable
fun tabPage(innerPadding: PaddingValues, viewModel: ComposeViewModel) {
    var state by remember { mutableStateOf(0) }
    var statePage = rememberPagerState(initialPage = state) { 5 }
    val titles = listOf("Tab 1", "Tab 2", "Tab 3 ", "Tab 4 ", "Tab 5 ")
    val scope = rememberCoroutineScope()
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .background(Color.Yellow)
//            .verticalScroll(rememberScrollState())
            .padding(5.dp)
            .fillMaxWidth()
            .fillMaxHeight()
    ) {
        TabRow(selectedTabIndex = statePage.currentPage) {
            titles.forEachIndexed { index, title ->
                Tab(modifier = Modifier.background(Color.Green), selected = state == index, onClick = {
                    scope.launch {
//                        statePage.scrollToPage(index)
                        statePage.animateScrollToPage(index)
                    }
                }, text = {
                    Text(text = title, maxLines = 2, overflow = TextOverflow.Ellipsis, fontSize = 20.sp, color = Color.Magenta)
                })
            }
        }
        HorizontalPager(
            state = statePage, modifier = Modifier
                .fillMaxWidth()
                .fillMaxHeight()
        ) { page ->
            when (page) {
                0 -> page1(viewModel)
                else -> Box(
                    modifier = Modifier
                        .padding(10.dp)
                        .background(Color.Red)
                        .fillMaxWidth()
                        .fillMaxHeight()
                        .aspectRatio(1f), contentAlignment = Alignment.Center
                ) {
                    Text(text = page.toString(), fontSize = 32.sp)
                }
            }
        }
    }
}
  1. 侧滑菜单栏:ModalNavigationDrawer:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ModalDrawerExample() {
    val drawerState = rememberDrawerState(DrawerValue.Closed)
    val scope = rememberCoroutineScope()
    // icons to mimic drawer destinations
    val items = listOf(
        Icons.Default.AccountCircle,
        Icons.Default.Email,
        Icons.Default.Favorite,
        Icons.Default.AccountBox,
        Icons.Default.Build,
        Icons.Default.LocationOn,
        Icons.Default.Lock,
        Icons.Default.CheckCircle,
        Icons.Default.AddCircle,
        Icons.Default.MailOutline,
    )
    val selectedItem = remember { mutableStateOf(items[0]) }
    val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
    Scaffold(topBar = {
        TopAppBar(modifier = Modifier
            .fillMaxWidth()
            .background(Color.Blue)
            .height(81.dp), colors = mediumTopAppBarColors(
            containerColor = Color.Blue,
            titleContentColor = MaterialTheme.colorScheme.primary,
        ), title = {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .fillMaxHeight(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center
            ) {
                Text(text = "compose navigation题栏", fontSize = 18.sp, color = Color.Red, style = TextStyle.Default)
            }
        }, navigationIcon = {
            IconButton(onClick = {
                scope.launch {
                    if (drawerState.isClosed) {
                        drawerState.open()
                    } else {
                        drawerState.close()
                    }

                }
            }) {
                Icon(
                    imageVector = Icons.Filled.Menu, contentDescription = "Localized description"
                )
            }
        }, actions = {
            IconButton(onClick = {
                /* doSomething() */
            }) {
                Icon(imageVector = Icons.Filled.Favorite, contentDescription = "Localized description")
            }
        }, scrollBehavior = scrollBehavior
        )

    }) { innerPadding ->
        ModalNavigationDrawer(modifier = Modifier.padding(innerPadding), drawerState = drawerState, drawerContent = {
            ModalDrawerSheet(drawerState) {
                Column(Modifier.verticalScroll(rememberScrollState())) {
                    Spacer(Modifier.height(12.dp))
                    items.forEach { item ->
                        NavigationDrawerItem(icon = {
                            Icon(item, contentDescription = null)
                        }, label = {
                            Text(item.name.substringAfterLast("."))
                        }, selected = item == selectedItem.value, onClick = {
                            scope.launch {
                                drawerState.close()
                            }
                            selectedItem.value = item
                        }, modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
                        )
                    }
                }
            }
        }, content = {
            Column(
                modifier = Modifier
                    .fillMaxSize()
                    .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(text = if (drawerState.isClosed) ">>> Swipe >>>" else "<<< Swipe <<<")
                Spacer(Modifier.height(20.dp))
                Button(onClick = { scope.launch { drawerState.open() } }) { Text("Click to open") }
            }
        })
    }
}
  1. 底部菜单栏:NavigationBar

@Composable
fun BottomNavigationContent() {
    var selectedItem by remember { mutableIntStateOf(0) }
    val items = listOf("首页", "视频", "购物", "设置")

    NavigationBar(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentHeight()
            .background(Color.Green)
    ) {
        items.forEachIndexed { index, item ->
            NavigationBarItem(modifier = Modifier
                .wrapContentHeight()
                .fillMaxWidth()
                .padding(0.dp)
                .background(Color.Yellow)
                .padding(0.dp), icon = {
                Icon(Icons.Filled.Favorite, contentDescription = item)
            }, label = { Text(item) }, selected = selectedItem == index, onClick = { selectedItem = index })
        }
    }
}
  1. 底部菜单栏NavHost
@Composable
fun navHost(
    modifier: Modifier = Modifier, navController: NavHostController = rememberNavController(), start: String = "first_screen", state: ScrollState = rememberScrollState()
) {
    NavHost(
        modifier = modifier, navController = navController, startDestination = start
    ) {
        composable("first_screen") {
            FirstScreen(navController = navController, state)
        }
        composable("second_screen") {
            SecondScreen(navController = navController)
        }
        composable("third_screen") {
            ThirdScreen(navController = navController)
        }
    }
}

7. 谷歌官方下拉刷新


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PullToRefreshScalingSample(onClick: () -> Unit) {
    var itemCount by remember { mutableStateOf(15) }
    var isRefreshing by remember { mutableStateOf(false) }
    val state = rememberPullToRefreshState()
    val coroutineScope = rememberCoroutineScope()


    val onRefresh: () -> Unit = {
        isRefreshing = true
        coroutineScope.launch {
            // fetch something
            delay(1500)
            itemCount += 5
            isRefreshing = false
        }
    }

    val scaleFraction = {
        if (isRefreshing) 1f
        else LinearOutSlowInEasing.transform(state.distanceFraction).coerceIn(0f, 1f)
    }

    Scaffold(modifier = Modifier.pullToRefresh(
        state = state, isRefreshing = isRefreshing, onRefresh = onRefresh
    ), topBar = {
        TopAppBar(title = { Text("TopAppBar") },
            // Provide an accessible alternative to trigger refresh.
            actions = {
                IconButton(onClick = onRefresh) {
                    Icon(Icons.Filled.Refresh, "Trigger Refresh")
                }
            })
    }, floatingActionButton = {
        FloatingActionButton(onClick = onClick) {
            Icon(
                Icons.Default.ArrowBack, contentDescription = "Add", tint = Color.Red
            )
        }
    }) {
        Box(Modifier.padding(it)) {
            LazyColumn(Modifier.fillMaxSize()) {
                if (!isRefreshing) {
                    items(itemCount) { ListItem({ Text(text = "Item ${itemCount - it}") }) }
                }
            }
            Box(
                Modifier
                    .align(Alignment.TopCenter)
                    .graphicsLayer {
                        scaleX = scaleFraction()
                        scaleY = scaleFraction()
                    }) {
                PullToRefreshDefaults.Indicator(state = state, isRefreshing = isRefreshing)
            }
        }
    }
}
  1. 谷歌官方吸顶:stickyHeader

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun StickyHeaderSample(paddingValues: PaddingValues) {
    val sections = listOf("A", "B", "C", "D", "E", "F", "G")

    LazyColumn(
        modifier = Modifier.padding(paddingValues), contentPadding = PaddingValues(6.dp)
    ) {
        sections.forEach { section ->
            stickyHeader {
                Text(
                    text = "Section $section", color = Color.White, modifier = Modifier
                        .fillMaxWidth()
                        .height(50.dp)
                        .background(Color.Red)
                        .padding(8.dp)
                )
            }
            items(10) {
                Text("Item $it from the section $section")
            }
        }
    }
}
  1. 三方库Compose 实现 CollapsableTopBarLayout效果

@Composable
fun MainScreen() {
    val state = rememberCollapsingToolbarScaffoldState()
    CollapsingToolbarScaffold(modifier = Modifier.fillMaxSize(), state = state, scrollStrategy = ScrollStrategy.ExitUntilCollapsed, toolbar = {
        val textSize = (18 + (30 - 18) * state.toolbarState.progress).sp
        Box(
            modifier = Modifier
                .background(MaterialTheme.colorScheme.primary)
                .fillMaxWidth()
                .height(150.dp)
                .pin()
        )
        Image(
            modifier = Modifier
                .parallax(0.5f)
                .height(300.dp)
                .fillMaxWidth()
                .pin()
//                .padding(16.dp)
                .graphicsLayer {
                    // change alpha of Image as the toolbar expands
                    alpha = state.toolbarState.progress
                }, contentScale = ContentScale.Crop, painter = painterResource(id = R.drawable.ava2), contentDescription = null
        )
        Text(
            text = "Title", modifier = Modifier
//                .height(81.dp)
                .road(Alignment.CenterStart, Alignment.BottomEnd)
                .padding(60.dp, 36.dp, 16.dp, 16.dp), textAlign = TextAlign.Center, color = Color.White, fontSize = textSize
        )
    }) {
        LazyColumn(
            modifier = Modifier.fillMaxWidth()
        ) {
            items(100) {
                Text(
                    text = "Item $it", modifier = Modifier.padding(8.dp)
                )
            }
        }
    }
}

四、如何直接链接到官方example?

  1. LazyVerticalGrid为例:

img_v3_02fl_70eb77d1-2529-4f7c-8034-60a4df1072eg.jpg 3. 直接点击上图2中 androidx. compose. foundation. samples. LazyVerticalGridSpanSample就可以查看相关example源代码了

五、Compose如何实现插件化开发

本文是基础介绍:与其说compose插件化,
不如说是纯代码打包成jar 怎么实现插件化?

本文先介绍如何实现?

  1. Compose界面是代码写布局,打包成纯代码jar,如果在apk包内是以dex包装形式存在的

  2. 通过ClassLoader加载外部dex文件,android加载外部dex文件涉及到classLoader是DexClassLoader

  3. 怎么通过Android Studio 制作成jar ,再制作成可以 dex文件供 DexClassLoader加载呢?

  4. 通过build-tools\33.0.0下面 d8执行ANT命令可以将jar转化成xxx_dex.jar (早些年只有java写的是用 dx

  5. 通过宿主包含接口,classloader反射加载插件包内接口实现便可以完成

  6. 本文只是简单介绍jar最简单的插件化,更多插件化,全动态插件化请参考:
    大型项目架构:全动态插件化+模块化+Kotlin+协程+Flow+Retrofit+JetPack+MVVM+极限瘦身+极限启动优化+架构示例+全网唯一
    以及头部介绍的WXDynamicPlugin系列的7篇文章

  1. Compose 插件化开搞: 先建一个依赖库 WX-Compose-IPlugin,里面只包含了一个接口入下,让宿主app工程依赖它:
interface ICompose {

    fun setComposeContent(activity: ComponentActivity)

}

2. 宿主里面自定义一个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;
        }
    }
}

3. 建一个模块工程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()
                })
            }
        }
    }
}

4. 编译 WX-Compose-PluginImpl 工程后,找到该工程下 build/intermediates/aar_main_jar/release/syncReleaseLibJars/classes.jar

  1. 拷贝classes.jar到自己电脑 \build-tools\33.0.0\下面(注意一定要33.0.0以下,需要电脑java 环境 1.8.xxx版本)然后在该环境变量下打开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)

  2. 宿主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()
    }
}
  1. 本示例我没有把插件放在assets下写copy操作,也没有写下载,查看效果先运行安装debug包,打开插件页面然后其在手机的data/data/com.wx.compose.plugin/files/d_dex/下上传 我们的 compose_plugin_lib_dex插件到手机, 就可以看到效果啦 如下图: img_v3_02fl_a5f9ebc8-9e0b-4ed3-a194-c12b3cae9fbg.jpg

________________________________________ 我是分割线君 ________________________________________

  1. 上面 4 和 5 的步骤太麻烦了,可以一键自动搞定吗? 可以的

  2. 整个项目中在 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')
}

10. 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")
    }
}
  1. 直接点击Gradle中 assembleDxCommandAndCopy,会自动把 4和5中的步骤 操作完,并把输出的compose_plugin_lib_dex文件放到 整个主工程的build下面:

img_v3_02fl_2ec33f8f-8fe2-4d14-af38-25fa04a8aeag.jpg

六、总结

本文重点用一个Demo示例工程介绍了:

  1. Compose最基础的布局写法,并输出了和传统XML常用控件对照表
  2. 如何直接链接到官方example,查看代码
  3. 纯代码jar 如何做到插件化的(以compose作为示例)
  4. 同时涉及到ANT编程,如何将jar 转化成 android上能识别的dex文件,Gradle相关Task配置用法

感谢阅读:

欢迎 关注,点赞、收藏