Android KMP初探

news2025/2/25 22:48:57

Android KMP初探

前言:

最近线上听了Kotlin官网举行的KMP会议,感觉听神奇的,于是就把官方demo下载下来尝试了一下,下载插件和所需要的依赖都用了很久,但是发现里面的代码很少,于是尝试自己手写了一下,遇到了不少问题,这里记录一下.

1.定义:

Kotlin Multiplatform 技术可为多种平台创建应用程序并在平台之间高效重用代码,同时保留原生编程的优势。您的应用程序将在 iOS、Android、macOS、Windows、Linux 等平台上运行。

Compose Multiplatform 是 JetBrains 推出的声明式 UI 框架,可让您为 Android、iOS、桌面和 Web 开发共享 UI。将 Compose Multiplatform 集成到 Kotlin Multiplatform 项目中,更快交付应用和功能,而无需维护多个 UI 实现。

在这里插入图片描述

2.适合各类项目:

在这里插入图片描述

3.优点

使用 Compose Multiplatform 只需构建一次 UI

Compose Multiplatform 是一个基于 Kotlin 和 Jetpack Compose 的声明式框架,用于在 Android、iOS、Web 和桌面(通过 JVM)之间共享 UI。

加速 UI 开发

轻松同步多个 UI 实现,让应用更快交付到用户手中。

组件级重用

使用可在所有目标平台上使用的可自定义微件构建您的 UI。使用预设主题快速开始,或自行创建细节可精确至像素的视觉风格。

根据需要使用原生组件

轻松使用原生 UI 微件或将共享 UI 嵌入现有原生应用。

4.需要几个硬性条件:

在使用 KMP + Compose 进行开发时,需要以下条件,由于没有mac设备就暂时不跑ios项目,跑Android项目也是一样的,不用过于纠结.

  • Mac电脑(苹果开发必须mac)
  • Android Studio
  • Xcode
  • 配置 ios 开发环境(cocoapods、开发者账号等)

5.项目结构:

################## 目录结构说明 ##################
```
.
├── README.md
├── app - 主应用
│   ├── build.gradle.kts
│   ├── MainActivity
│   ├── libs
│   └── src
├── commonMain -公共组件
│   ├── app
│   ├── Greeting
│   ├── Platform
├── appleMain - ios平台
│   ├── getPlatform
├── iosMain - ios业务代码
│   ├── MainViewController
│   └── IOSPlatform
├── gradle
│   └── wrapper
├── gradle.properties
├── gradlew
├── gradlew.bat
├── build.gradle.kts
├── local.properties
└── settings.gradle.kts
################## 目录结构说明 ##################

6.App目录下的build.gradle.kts配置:

import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.idea.tcs.extras.isCommonizedKey
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.composeMultiplatform)
    alias(libs.plugins.composeCompiler)
}
kotlin {
    androidTarget {
        @OptIn(ExperimentalKotlinGradlePluginApi::class)
        compilerOptions {
            jvmTarget.set(JvmTarget.JVM_11)
        }
    }

    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "ComposeApp"
            isStatic = true
        }
    }

    sourceSets {

        androidMain.dependencies {
            implementation(compose.preview)
            implementation(libs.androidx.activity.compose)
        }
        commonMain.dependencies {
            implementation(compose.runtime)
            implementation(compose.foundation)
            implementation(compose.material)
            implementation(compose.ui)
            implementation(compose.components.resources)
            implementation(compose.components.uiToolingPreview)
            implementation(libs.androidx.lifecycle.viewmodel)
            implementation(libs.androidx.lifecycle.runtime.compose)
            implementation(libs.androidx.constraintlayout)
        }
    }
}

android {
    namespace = "com.cloud.androidkmpdemo"
    compileSdk = libs.versions.android.compileSdk.get().toInt()

    defaultConfig {
        applicationId = "com.cloud.androidkmpdemo"
        minSdk = libs.versions.android.minSdk.get().toInt()
        targetSdk = libs.versions.android.targetSdk.get().toInt()
        versionCode = 1
        versionName = "1.0"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = false
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    compose.desktop {
        application {
            mainClass = "com.example.composeApp.MainKt"

            nativeDistributions {
                targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
                packageName = "com.cloud.kmpdemo"
                packageVersion = "1.0.0"
                // 描述应用程序
                description = "A simple demo for KMP"
                // 版权信息
                copyright = "© 2024 My Name. All rights reserved."
                // 厂商信息
                vendor = "Example vendor"
                // 设置许可证文件
                licenseFile.set(project.file("LICENSE.txt"))
            }
        }
    }
}
dependencies {
    debugImplementation(compose.uiTooling)
}

7.项目的build.gradle.kts配置:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.androidApplication) apply false
    alias(libs.plugins.androidLibrary) apply false
    alias(libs.plugins.composeMultiplatform) apply false
    alias(libs.plugins.composeCompiler) apply false
    alias(libs.plugins.kotlinMultiplatform) apply false
    alias(libs.plugins.jetbrainsKotlinAndroid) apply false
}

8.统一的依赖配置:

[versions]
agp = "8.5.2"
android-compileSdk = "34"
android-minSdk = "24"
android-targetSdk = "34"
androidx-activityCompose = "1.9.3"
androidx-appcompat = "1.7.0"
androidx-constraintlayout = "2.2.0"
androidx-core-ktx = "1.15.0"
androidx-espresso-core = "3.6.1"
androidx-lifecycle = "2.8.4"
androidx-material = "1.12.0"
androidx-test-junit = "1.2.1"
compose-multiplatform = "1.7.0"
junit = "4.13.2"
kotlin = "2.1.0"
kotlinVersion = "1.9.0"

[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" }
androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-junit" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidx-espresso-core" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidx-appcompat" }
androidx-material = { group = "com.google.android.material", name = "material", version.ref = "androidx-material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "androidx-constraintlayout" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }

[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
androidLibrary = { id = "com.android.library", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinVersion" }

9.公共组件:

9.1 Platform接口:

interface Platform {
    val name: String
}

expect fun getPlatform(): Platform

9.2 Greeting类:

class Greeting {
    private val platform = getPlatform()

    fun greet(): String {
        return "Hello, ${platform.name}!"
    }
}

9.3 App类:

package com.cloud.kmpdemo

import androidkmpdemo.app.generated.resources.Res
import androidkmpdemo.app.generated.resources.compose_multiplatform
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.ui.tooling.preview.Preview


@Composable
@Preview
fun App() {
    MaterialTheme {
        var showContent by remember { mutableStateOf(false) }
        var showDialog by remember { mutableStateOf(false) }
        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            Button(onClick = { showContent = !showContent }) {
                Text("Click me")
            }
            Button(onClick = { showDialog = !showDialog }) {
                Text("show Dialog")
            }
            AnimatedVisibility(showContent) {
                val greeting = remember { Greeting().greet() }
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                    Image(painterResource(Res.drawable.compose_multiplatform), null)
                    Text("Compose: $greeting")
                }
            }
            AnimatedVisibility(showDialog) {
                Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
                    AlertDialogSample()
                }
            }
        }
    }
}

@Composable
@Preview
fun AlertDialogSample(){
    val dialog = remember { mutableStateOf(true) }
    if(dialog.value){
        AlertDialog(
            onDismissRequest = { dialog.value = false},
            title = { Text(text = "开启位置服务")},
            text = { Text(text = "这将意味着,我们会给您提供精准的位置服务,并且您将接受关于您订阅的位置信息。") },
            confirmButton = {
                TextButton(
                    onClick = { dialog.value = false}
                ){
                    Text(text = "同意")
                }
            },
            dismissButton = {
                TextButton(
                    onClick = {
                        dialog.value = false
                    }
                ){
                    Text(text = "取消")
                }
            }

        )
    }
}

10.Android代码:

本文我在原来的基础上加入了一个弹框示例AlertDialogSample()

package com.cloud.kmpdemo

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
            setContent(){
                App()
            }
    }
}

@Preview
@Composable
fun AppAndroidPreview() {
    App()
}

@Preview
@Composable
fun DialogAndroidPreview() {
    //显示dialog
    AlertDialogSample()
}
package com.cloud.kmpdemo

import android.os.Build

/**
 * 获取平台版本信息
 */
class AndroidPlatform : Platform {
    override val name: String = "Android ${Build.VERSION.SDK_INT}"
}

actual fun getPlatform(): Platform = AndroidPlatform()

11.ios平台代码:

package org.example.kmpdemo

import androidx.compose.ui.window.ComposeUIViewController
import com.cloud.kmpdemo.App

fun MainViewController() = ComposeUIViewController { App() }
package org.example.kmpdemo

import com.cloud.kmpdemo.Platform
import platform.UIKit.UIDevice

class IOSPlatform: Platform {
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

actual fun getPlatform(): Platform = IOSPlatform()

12.ios业务代码:

import UIKit
import SwiftUI
import ComposeApp

struct ComposeView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        MainViewControllerKt.MainViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

struct ContentView: View {
    var body: some View {
        ComposeView()
                .ignoresSafeArea(.keyboard) // Compose has own keyboard handler
    }
}
import SwiftUI

@main
struct iOSApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

info.plist:

和Android的build.gradle配置文件一样,都是管理依赖和第三方库的.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>CADisableMinimumFrameDurationOnPhone</key>
    <true/>
    <key>UIApplicationSceneManifest</key>
    <dict>
       <key>UIApplicationSupportsMultipleScenes</key>
       <false/>
    </dict>
    <key>UILaunchScreen</key>
    <dict/>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
       <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
       <string>UIInterfaceOrientationPortrait</string>
       <string>UIInterfaceOrientationLandscapeLeft</string>
       <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
       <string>UIInterfaceOrientationPortrait</string>
       <string>UIInterfaceOrientationPortraitUpsideDown</string>
       <string>UIInterfaceOrientationLandscapeLeft</string>
       <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
</dict>
</plist>

13.运行效果如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

14.总结:

以上就是今天的内容,KMP初探,根据不同平台的设备显示不同类型和版本号,代码非常少,再也没有创建xml的烦恼,MainActivity也不需要加载activity_main布局,简直不要太爽,当然KMP如果不熟悉的话跑起来很费劲,我是从0到1自己全部重新撸了一遍,包含Android和IOS两个平台,build插件配置等等,这里我没有Mac环境就不演示iOS的效果了.如果只是单纯写代码看效果没有太大的意义,需要搞清楚整个运行过程和实现原理,在后面进行学习和开发过程中就会事半功倍。

15.项目源码地址:

https://gitee.com/jackning_admin/android-kmpdemo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2306052.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全之Web后端PHP

目录 一、PHP基础语法 1.PHP基础 &#xff08;1&#xff09;php的优点 &#xff08;2&#xff09;PhpStorm的优点 2.PHP基本语法 3.PHP变量 4.PHP运算符 二、PHP流控与数组 1.php流程控制语句以及循环 &#xff08;1&#xff09;if 语句 &#xff08;2&#xff09;if…

Redis——用户签到BitMap,UV统计

目录 BitMap 使用场景 1. 用户签到系统 2. 用户行为标记 3. 布隆过滤器&#xff08;Bloom Filter&#xff09; BitMap介绍 Redis中的使用 Redis功能示例 添加&#xff1a; 获取&#xff1a; 批量获取&#xff1a; java中实现 统计本月连续签到次数 UV统计 UV 统计…

pycharm技巧--鼠标滚轮放大或缩小 Pycharm 字体大小

1、鼠标滚轮调整字体 设置 Ctrl 鼠标滚轮调整字体大小 备注&#xff1a; 第一个是活动窗口&#xff0c;即缩放当前窗口 第二个是所有编辑器窗口&#xff0c;即缩放所有窗口的字体 2、插件 汉化包&#xff1a; Chinese Simplified 包

数字信任的底层逻辑:密码学核心技术与现实应用

安全和密码学 --The Missing Semester of Your CS Education 目录 熵与密码强度密码散列函数密钥体系 3.1 对称加密 3.2 非对称加密信任模型对比典型应用案例安全实践建议扩展练习杂项 密码学是构建数字信任的基石。 本文浅析密码学在现实工具中的应用&#xff0c;涵盖 1&…

全面理解-深拷贝与浅拷贝

在 C 中&#xff0c;深拷贝&#xff08;Deep Copy&#xff09; 和 浅拷贝&#xff08;Shallow Copy&#xff09; 是两种完全不同的对象拷贝策略&#xff0c;主要区别在于对指针和动态分配资源的处理方式。正确理解二者的区别是避免内存泄漏、悬空指针和程序崩溃的关键。 一、核…

Redis分布式锁故障处理:当Redis不可用时的应对策略

Redis分布式锁故障处理&#xff1a;当Redis不可用时的应对策略 在分布式系统中&#xff0c;Redis因其高性能和丰富的特性常被用于实现分布式锁。但当加锁过程中Redis服务不可用时&#xff0c;系统将面临严重挑战。本文将深入探讨这一问题&#xff0c;并提供多维度解决方案。 目…

WordPress平台如何接入Deepseek,有效提升网站流量

深夜改代码到崩溃&#xff1f;《2024全球CMS生态报告》揭露&#xff1a;78%的WordPress站长因API对接复杂&#xff0c;错失AI内容红利。本文实测「零代码接入Deepseek」的保姆级方案&#xff0c;配合147SEO的智能发布系统&#xff0c;让你用3个步骤实现日均50篇EEAT合规内容自动…

ROS ur10机械臂添加140夹爪全流程记录

ROS ur10机械臂添加140夹爪 系统版本&#xff1a;Ubuntu20.04 Ros版本&#xff1a;noetic Moveit版本&#xff1a;moveit-noetic 参考博客&#xff1a; ur3robotiq ft sensorrobotiq 2f 140配置rviz仿真环境_有末端力传感器的仿真环境-CSDN博客 UR5机械臂仿真实例&#xf…

FFMPEG编码容错处理解决办法之途径----升级库文件

在qt开发环境下接收网络数据&#xff0c;调用ffmpeg解码播放视频&#xff0c;出现闪屏现象&#xff0c;具体现象可以使用操作系统自带的ffplay播放器播放原始视频流可复现&#xff1b;而使用操作系统自带的mpv播放器播放视频则不会出现闪屏&#xff1b;闪屏时会报Could not fin…

uniapp h5端和app端 使用 turn.js

前提:添加页后,添加页与当前页会重叠在一起,不知道为什么,没有找到解决办法 1.h5端 <template><view class"container"><view id"flipbook"><view class"page page1">Page 1</view><view class"page pag…

【入门音视频】音视频基础知识

&#x1f308;前言&#x1f308; 这个系列在我学习过程中&#xff0c;对音视频知识归纳总结的笔记。因为音视频相关讲解非常稀少&#xff0c;所以我希望通过这个音视频系列&#xff0c;跟大家一起学习音视频&#xff0c;希望减少初学者在学习上的压力。同时希望也欢迎指出文章的…

数据结构☞泛型

一.基础定义与应用方向 1.定义&#xff1a; 一般的类和方法&#xff0c;只能使用具体的类型 : 要么是基本类型&#xff0c;要么是自定义的类。如果要编写可以 应用于多种类型 的代码&#xff0c;这种刻板的限制对代码的束缚就会很大。----- 来源《 Java 编程思想》对泛型的介…

hot100-二叉树

二叉树 二叉树递归 相当于这个的顺序来回调换 class Solution {private List<Integer> res new ArrayList<>();public List<Integer> inorderTraversal(TreeNode root) {if(root null)return res;inorderTraversal(root.left);res.add(root.val);inorde…

嵌入式项目:STM32刷卡指纹智能门禁系统

本文详细介绍基于STM32的刷卡指纹智能门禁系统。 获取资料/指导答疑/技术交流/选题/帮助&#xff0c;请点链接&#xff1a; https://gitee.com/zengzhaorong/share_contact/blob/master/stm32.txt 1 系统功能 1.1 功能概述 本系统由STM32硬件端&#xff08;下位机&#xff09;…

短剧小程序系统源码

短剧小程序系统源码 今天我要向大家介绍的是最新作品——短剧小程序系统源码。这不仅仅是一款简单的播放工具&#xff0c;它背后蕴含的强大功能能够帮助你的短剧业务实现质的飞跃&#xff01; 为什么说这款源码很厉害&#xff1f; 首先&#xff0c;在当今竞争激烈的市场环境…

C#中级教程(2)——走进 C# 面向对象编程:从基础到进阶的深度探索

一、为什么选择面向对象编程 在软件开发的演进过程中&#xff0c;随着程序规模和复杂度的不断增加&#xff0c;传统的编程方式逐渐暴露出局限性。面向对象编程应运而生&#xff0c;它就像是一位智慧的组织者&#xff0c;将程序中的功能进行模块化划分。每个模块各司其职&#x…

基于SpringBoot的“流浪动物救助系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“流浪动物救助系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 局部E-R图 系统首页界面 系统…

基于WebRTC与AI大模型接入EasyRTC:打造轻量级、高实时、强互动的嵌入式音视频解决方案

随着物联网和嵌入式技术的快速发展&#xff0c;嵌入式设备对实时音视频通信的需求日益增长。然而&#xff0c;传统的音视频解决方案往往存在体积庞大、实时性差、互动体验不佳等问题&#xff0c;难以满足嵌入式设备的资源限制和应用场景需求。 针对以上痛点&#xff0c;本文将介…

Windows - 通过ssh打开带有图形界面的程序 - 一种通过计划任务的曲折实现方式

Windows(奇思妙想) - 通过ssh打开带有图形界面的程序 - 一种通过计划任务的曲折实现方式 前言 Windows启用OpenSSH客户端后就可以通过SSH的方式访问Windows了。但是通过SSH启动的程序&#xff1a; 无法显示图形界面会随着SSH进程的结束而结束 于是想到了一种通过执行“计划…

RT-Thread+STM32L475VET6——USB鼠标模拟

文章目录 前言一、板载资源二、具体步骤1.配置icm20608传感器2.打开CubeMX进行USB配置3. 配置USB3.1 打开USB驱动3.2 声明USB3.3 剪切stm32xxxx_hal_msp.c中的void HAL_PCD_MspInit(PCD_HandleTypeDef* hpcd)和void HAL_PCD_MspDeInit(PCD_HandleTypeDef* hpcd)函数至board.c3.…