Android compose 的基本环境搭建

news2024/12/28 19:32:48

1.创建项目

导入版本

1.gradle/libs.versions.toml

[versions]
accompanistPermissions = "0.36.0"
agp = "8.5.0-beta01"
coilCompose = "2.7.0"
constraintlayoutComposeVersion = "1.0.1"
hiltAndroid = "2.51.1"
hiltNavigationCompose = "1.2.0"
kotlin = "1.9.25"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
activityCompose = "1.9.2"
composeBom = "2024.09.01"
kotlinxSerializationJson = "1.6.3"
lifecycleViewmodelKtx = "2.8.5"
composeRuntime = "1.7.1"
lingver = "1.3.0"
loggingInterceptorVersion = "4.12.0"
navigationCompose = "2.8.0"
retrofit = "2.11.0"
roomRuntime = "2.6.1"
rxandroid = "2.1.1"
rxandroidVersion = "3.0.1"
rxjava = "2.2.21"
rxjava3 = "3.1.9"
rxlifecycle = "3.1.0"

[libraries]

androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
#region viewmodel livedata compose
androidx-lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycleViewmodelKtx" }
constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutComposeVersion" }
runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "composeRuntime" }
androidx-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "composeRuntime" }
androidx-runtime-rxjava2 = { module = "androidx.compose.runtime:runtime-rxjava2" }
#endregion compose

#region kotlin serialization
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
#endregion

#region hilt
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
#endregion

#region navigation
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
#endregion

#region retrofit2 and okhttp3
okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptorVersion" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }

#endregion okhttp3



#region rxjava 2
adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "retrofit" }
rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" }
rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }
#endregion


#region rxjava 3
rxjava3-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroidVersion" }
rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava3" }
adapter-rxjava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit" }
rxlifecycle = { module = "com.trello.rxlifecycle3:rxlifecycle", version.ref = "rxlifecycle" }
rxlifecycle-android-lifecycle-kotlin = { module = "com.trello.rxlifecycle3:rxlifecycle-android-lifecycle-kotlin", version.ref = "rxlifecycle" }
rxlifecycle-components = { module = "com.trello.rxlifecycle3:rxlifecycle-components", version.ref = "rxlifecycle" }

#endregion


#region room
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }


#endregion



#region coil
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
#endregion


#region accompanist
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
#endregion

#region 国际化
lingver = { module = "com.github.YarikSOffice:lingver", version.ref = "lingver" }
#endregion

junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

我整理好的版本,都用过了适配

2.模块目录下的 build.gradle.kts

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    id("kotlin-kapt")
    id("com.google.dagger.hilt.android")
    kotlin("plugin.serialization")
    id("androidx.room")
}




android {
    namespace = "com.composeapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.composeapp"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }

    }
    room {
        schemaDirectory("$projectDir/schemas")
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }

    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }


}

dependencies {

    implementation(libs.androidx.core.ktx)

    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)

    implementation(libs.kotlinx.serialization.json)


    //region ViewModel Livedata compose runtime
    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(libs.androidx.lifecycle.livedata.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.lifecycle.lifecycle.runtime.compose)
    implementation(libs.androidx.lifecycle.viewmodel.savedstate)
    implementation(libs.androidx.runtime)
    implementation(libs.runtime.livedata)
    //endregion


    //    region hilt
    implementation(libs.hilt.android)
    kapt(libs.hilt.android.compiler)
    implementation(libs.androidx.hilt.navigation.compose)
    // endregion


    //region  navigation
    implementation(libs.androidx.navigation.compose)
    //endregion navigation


    //region retrofit2 okhttp

    //Retrofit 核心库
    implementation(libs.retrofit)
    //响应数据自动序列化
    //JSON
    implementation(libs.converter.gson)
    //String类型
    implementation(libs.converter.scalars)
    //拦截器 logging
    implementation(libs.okhttp3.logging.interceptor)
    //endregion


    //region rxjava2
//    implementation(libs.androidx.runtime.rxjava2)
//    implementation(libs.adapter.rxjava2)
//    implementation(libs.rxjava)
//    implementation(libs.rxandroid)
    //endregion


    //region  rxjava3
    implementation(libs.rxjava3.rxjava)
    implementation(libs.adapter.rxjava3)
    implementation(libs.rxlifecycle)
    implementation(libs.rxlifecycle.android.lifecycle.kotlin)
    implementation(libs.rxlifecycle.components)
    implementation(libs.rxjava3.rxandroid)
    //endregion


    //region room
    implementation(libs.androidx.room.runtime)
    annotationProcessor(libs.androidx.room.compiler)
    kapt(libs.androidx.room.compiler)
    //endregion


    //region coil 异步网络图片加载
    implementation(libs.coil.compose)
    //endregion


    //region accompanist
    implementation(libs.accompanist.permissions)
    //endregion


    //region 国际化
    implementation(libs.lingver)

    // endregion

    //region compose 约束布局
    implementation (libs.constraintlayout.compose)

    //endregion

}


3.工程目录下的 build.gradle.kts

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.jetbrains.kotlin.android) apply false
    id("com.google.dagger.hilt.android") version "2.51.1" apply false
    kotlin("kapt") version "1.9.25" apply false

    kotlin("jvm") version "1.9.25" apply false // or kotlin("multiplatform") or any other kotlin plugin
    kotlin("plugin.serialization") version "1.9.25" apply false

    val room_version = "2.6.1"
    id("androidx.room") version room_version apply false
}

4.工程目录下setting.gradle.kts

pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
    }
}

rootProject.name = "ComposeApp"
include(":app")
 

2.搭建Hilt环境

1.创建Application

@HiltAndroidApp
class MyApplication : Application() {


    companion object {

        const val SharedPreferencesFileName = "MyApplication";
        private lateinit var application: Application;
        lateinit var sharedPreferences: SharedPreferences
        fun getApplication(): Application {
            return application;
        }
    }

    @Override
    override fun onCreate() {
        super.onCreate()
//        Log.i("测试","app启动了 child="+child)
        application = this

        sharedPreferences =
            getSharedPreferences(SharedPreferencesFileName, Context.MODE_PRIVATE);

        /**
         * 国际化绑定 已经帮存入 sharedPreferences里了
         */
         Lingver.init(this)
    }

}

加上注解  @HiltAndroidApp

2.在用到注入的Activity上加上注解

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    /**
     * 将依赖注入好的viewModel放入这里
     */
    private val viewModel: XianPageViewModel by viewModels()

    @Inject
    lateinit var child: Child;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {

            val sharedPreferences = MyApplication.sharedPreferences
            Log.i("share", "这是shared  全局 $sharedPreferences")

//            sharedPreferences.edit().putString("token", "123456").apply()

            //关闭 导航状态栏
            // 隐藏状态栏
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                window.insetsController?.hide(WindowInsets.Type.statusBars())
            } else {
                @Suppress("DEPRECATION")
                window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
            }

            this.lifecycleScope
            val string = sharedPreferences.getString("token", "")
            Log.i("share", "获取token  全局 $string")
            ComposeAppTheme {
//                ScaffoldExample()
//                MyApp()
//                NamePageRxjava3()
//                NamePage()
//                RoomTestPage()
//                NfcPage()
//                NfcMain()
//                ImageIconPage()
//                PermissionPage()

//                LiveDataTest()
//                LanguageSelector()
                LanguageSelectorKuangjia()

            }
        }


//        Log.i("测试","activity启动了 this="+this)
//        Log.i("测试","activity启动了 child="+child.activity)
//        Log.i("测试","activity启动了 child="+child.application)


        Log.i("测试", "activity启动了 viewModel=" + viewModel)


    }


}

3.测试注入

/**
 * 第一种: 相当于spring @Component
 */
@ActivityScoped
class Child @Inject constructor(@ActivityContext val activity: Context,@ApplicationContext val application: Context){

    val name = "你好"
//    val applicationContext = application

}

3.注入网络模块

1.单例类

/**
 * 全局唯一网络模块
 */
@Module
@InstallIn(SingletonComponent::class)
object NetModel {


    private const val Tag = "Retrofit:";
    private const val URL = "http://192.168.202.57:8080";

    @Singleton
    @Provides
    fun provideOkHttpClient(tokenInterceptor: TokenInterceptor): OkHttpClient {
        //构建日志拦截器
        val httpLoggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
            override fun log(message: String) {
                Log.i(Tag, message)
            }
        }).setLevel(HttpLoggingInterceptor.Level.BODY)
        //构建
        return OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(tokenInterceptor)
            .addInterceptor(httpLoggingInterceptor)
            .build()
    }



    @RxJava2Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava2(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)

//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
            //添加Rxjava适配
//            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
    }

    @RxJava3Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava3(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器

            //添加Rxjava适配
            .build()
    }

    @Singleton
    @Provides
    fun provideMainTestService(@RxJava3Inject retrofit: Retrofit): MainTestService {
        return retrofit.create(MainTestService::class.java)
    }



}

2.Token拦截器

@Singleton
class TokenInterceptor @Inject constructor() : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()

        val token: String = MyApplication.sharedPreferences.getString("token", "2001").toString()
        val url = request.url.newBuilder().addQueryParameter("token1", token).build()


        val header = request.headers.newBuilder().add("token2", token).build()



        val requestNew = request.newBuilder().url(url).headers(header).build()

        return chain.proceed(requestNew);

    }
}

3.要是注入两个相同类型的

创建自定义注解

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RxJava2Inject
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RxJava3Inject

在构造的时候声明

 @RxJava2Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava2(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)

//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
            //添加Rxjava适配
//            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
    }

    @RxJava3Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava3(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器

            //添加Rxjava适配
            .build()
    }

注入的时候声明注入哪个


    @Singleton
    @Provides
    fun provideMainTestService(@RxJava3Inject retrofit: Retrofit): MainTestService {
        return retrofit.create(MainTestService::class.java)
    }

4.声明接口

interface MainTestService {

//    @GET("/phone/gis/test")
//   suspend fun getTestData(): Response<String>

    @GET("/phone/gis/test")
    suspend fun getTestDataCall(): Call<TestDto>

    @GET("/phone/gis/test")
    suspend fun getTestData(): Response<TestDto>

    @GET("/phone/gis/test/{name}")
    suspend fun getTestDataTruth(@Path("name") name: String, @Query("age") age: Int): TestDto

    /**
     * 使用Rxjava 不允许加 suspend 关键字
     */
    @GET("/phone/gis/test/{name}")
    fun getTestDataRx(@Path("name") name: String, @Query("age") age: Int): Observable<TestDto>

}

RxJava写 接口,千万不要声明suspend ,要不然序列化JSON 会报错,无法实例化 Observable

5.要是发送http请求还需要权限

network_security_config.xml 

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

6.网络权限

    <uses-permission android:name="android.permission.INTERNET"/>

4.ViewModel

/**
 * viewmodel不能设置作用域,否则会报错
 *
 * ViewModel 部分中提到的 viewModel() 函数会自动使用 Hilt 通过 @HiltViewModel 注解构建的 ViewModel
 */
@HiltViewModel
class ScopePageViewModel @Inject constructor():ViewModel() {


    init {
        val job: Job = viewModelScope.launch { }//可以单独取消
    }

}

然后注入就可以了

5.LiveData

@HiltViewModel
class LiveDataTestViewModel @Inject constructor() : ViewModel() {


    val items:MutableLiveData<MutableList<Item>> = MutableLiveData(mutableListOf())

    init {
        val item1 = Item("测试模块1",true)
        items.value?.add(item1)
        val item2 = Item("测试模块2",true)
        items.value?.add(item2)
    }


   fun updateItem(item:Item){
        item.updateIsShow(!item.isShow.value!!)
    }




}

用法 

@Composable
fun LiveDataTest(viewModel: LiveDataTestViewModel = hiltViewModel()) {

    val items = viewModel.items.observeAsState(mutableListOf())



    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(30.dp)
    ) {

        item {
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) {

                val width = Modifier
                    .width(150.dp)
                    .height(50.dp)
                Button(onClick = {
                    viewModel.updateItem(items.value[0])
                }, modifier = width) {
                    Text(text = stringResource(R.string._1))
                }
                Button(onClick = { viewModel.updateItem(items.value[1]) }, modifier = width) {
                    Text(text = "隐藏或打开测试模块2")
                }

            }


        }
        items(items.value) {

            ItemComponent(item = it)

        }


    }


}

6.整合navigation


@Serializable
data class Profile(val name: String)


@Serializable
object FriendsList

@Serializable
object ProfileScreenChild2

@Serializable
data class ProfileScreenChild1(val param: String)


@Serializable
data class Aaa(val param: String)

// Define the ProfileScreen composable.
@Composable
fun ProfileScreen(
    profile: Profile,
    parentViewModel: XianPageViewModel?,
    viewModel: XianPageViewModel = hiltViewModel(),
    onNavigateToFriendsList: () -> Unit

) {


//    ProfileScreen启动了com.composeapp.ui.view.XianPageViewModel@841f9de这是viewmodel
    Log.i("测试", "ProfileScreen启动了" + viewModel.toString() + "这是viewmodel")
    Log.i(
        "测试",
        "ProfileScreen启动了  这是父级的viewModel实例" + parentViewModel?.toString() + "这是父类viewmodel"
    )

    Column {
        Text("导航参数: ${profile.name}")
        ProfileScreenChild()
        Button(onClick = { onNavigateToFriendsList() }) {
            Text("Go to Friends List")
        }
    }
}

@Composable
fun ProfileScreenChild(viewModel: XianPageViewModel = hiltViewModel()) {
//     ProfileScreenChild启动了com.composeapp.ui.view.XianPageViewModel@841f9de这是viewmodel
    Log.i("测试", "ProfileScreenChild启动了" + viewModel.toString() + "这是viewmodel") //在同一个导航中是一致的
//    Text("子组件的值: ${viewModel.data.value}")


    val childNavController = rememberNavController()
    NavHost(childNavController, startDestination = ProfileScreenChild1("你好测试子组件Pchild1")) {
        composable<ProfileScreenChild2> { backStackEntry ->
            //专门用来拿到当前导航的堆栈里的ViewModel
            val parentEntry = remember(backStackEntry) {
                //必须类型和值都一样才能找到,否则抛异常
                childNavController.getBackStackEntry(ProfileScreenChild1("你好测试子组件Pchild1"))//必须值要一样才能找到,值不一致就找不到,跟序列化JSON比
            }
//            viewmodel 当组件移除组件树的时候,将会被销毁
            val parentViewModel = hiltViewModel<XianPageViewModel>(parentEntry)

            ProfileScreenChild2(parentViewModel)

        }
        composable<ProfileScreenChild1> { backStackEntry ->
            val child1 = backStackEntry.toRoute<ProfileScreenChild1>()
            ProfileScreenChild1(profileScreenChild1 = child1) {
                childNavController.navigate(ProfileScreenChild2)
            }
        }
    }


}

@Composable
fun ProfileScreenChild2(
    parentViewModel: XianPageViewModel,
    viewModel: XianPageViewModel = hiltViewModel()
) {
    Log.i(
        "测试",
        "ProfileScreenChild2启动了" + parentViewModel.toString() + "这是parent viewmodel"
    ) //在同一个导航中是一致的
    Log.i(
        "测试",
        "ProfileScreenChild2启动了" + viewModel.toString() + "这是viewmodel"
    ) //在同一个导航中是一致的
    Log.i(
        "测试",
        "ProfileScreenChild2启动了" + (viewModel === parentViewModel) + "这是viewmodel"
    ) //在同一个导航中是一致的
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Button(onClick = { /*TODO*/ }) {
            Text(text = "ProfileScreenChild2")
        }
    }
}

@Composable
fun ProfileScreenChild1(
    viewModel: XianPageViewModel = hiltViewModel(),
    profileScreenChild1: ProfileScreenChild1,
    toProChild2: () -> Unit
) {
    Log.i(
        "测试",
        "ProfileScreenChild1启动了" + viewModel.toString() + "这是viewmodel  这是param${profileScreenChild1.param}"
    ) //在同一个导航中是一致的
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Button(onClick = { toProChild2() }) {
            Text(text = "ProfileScreenChild1")
        }
    }

}


// Define the FriendsListScreen composable.
@Composable
fun FriendsListScreen(
    onNavigateToProfile: (String) -> Unit,
    viewModel: XianPageViewModel = hiltViewModel()
) {
    Log.i("测试", "FriendsListScreen启动了" + viewModel.toString() + "这是viewmodel")
    val (text, setText) = remember { mutableStateOf("") }
    Column {
        Text("Friends List")
        TextField(value = text, onValueChange = setText)
        Button(onClick = { onNavigateToProfile(text) }) {
            Text("Go to Profile")
        }
    }
}

// Define the MyApp composable, including the `NavController` and `NavHost`.
@Composable
fun MyApp(viewModel: XianPageViewModel = viewModel()) {

    Log.i("测试", "MyApp启动了 viewmodel=$viewModel")//是Activity同一个
    val navController = rememberNavController()
    NavHost(navController, startDestination = FriendsList) {
        composable<Profile> { backStackEntry ->
//            获取路由对象实例
            val profile: Profile = backStackEntry.toRoute()

            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                ProfileScreen(
                    profile = profile,
                    parentViewModel = null,
                    onNavigateToFriendsList = {
                        navController.navigate(route = FriendsList)
                    }
                )
            }

        }
        composable<FriendsList> {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                FriendsListScreen(
                    onNavigateToProfile = { it ->
                        navController.navigate(
                            route = Profile(name = "传入了新的参数:${it}")
                        )
                    }
                )
            }

        }
    }
}

7.整合room

1.实体类


@Entity
@Serializable
data class User(@PrimaryKey val uid:String,
                @ColumnInfo(name = "name")
                val name:String,
                val time:Long,
               val date:String?=null,
                val age :Int
) {
}

2.dao



@Dao
interface UserDao {

    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid = :userId")
    fun loadAllByIds(userId: String): List<User>

    @Query("SELECT * FROM user WHERE name LIKE :name")
    fun findByName(name: String): List<User>

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)




}

3.数据库



@Database(entities = [User::class], version = 3, exportSchema = true)
abstract class AppDataBase: RoomDatabase() {


    companion object{
        private const val DB_NAME = "my_app.db"

        fun getInstance(context: Context): AppDataBase{
            val db = Room.databaseBuilder(
                context,
                AppDataBase::class.java, DB_NAME
            )
                .addMigrations(MIGRATION_1_2,MIGRATION_2_3)
                .build()
            return db;
        }


        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(db: SupportSQLiteDatabase) {
                // 执行 SQL 语句来迁移数据库
                db.execSQL("ALTER TABLE user ADD COLUMN date TEXT")
            }
        }


        val MIGRATION_2_3 = object : Migration(2, 3) {
            override fun migrate(db: SupportSQLiteDatabase) {
                // 执行 SQL 语句来迁移数据库
                db.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL default 1")
            }
        }

    }
    /**
     * 数据库当中的一张表
     */
    abstract fun UserDao():UserDao


}

4.Dao实例注入


/**
 * 全局唯一数据库模块
 */
@Module
@InstallIn(SingletonComponent::class)
object DataBaseModel {


    @Singleton
    @Provides
    fun provideAppDataBase(@ApplicationContext context:Context): AppDataBase {
        return AppDataBase.getInstance(context)
    }


    @Singleton
    @Provides
    fun provideUserDao(appDataBase: AppDataBase): UserDao {
        return appDataBase.UserDao()
    }



}

8.Permission



@SuppressLint("CheckResult")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionPage(){


    // Camera permission state
    val cameraPermissionState = rememberPermissionState(
        android.Manifest.permission.WRITE_EXTERNAL_STORAGE
    )

    Observable.fromCallable {
        Log.i("执行","执行在"+Thread.currentThread().name)
        "true"
    }.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({
            Log.i("执行","执行成功$it  "+Thread.currentThread().name)
        },{
            Log.i("执行","执行失败"+it.message)
        })

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
        if (cameraPermissionState.status.isGranted) {
            Text("相机权限授予成功")
        } else {
            Column {
                val textToShow = if (cameraPermissionState.status.shouldShowRationale) {
                    // If the user has denied the permission but the rationale can be shown,
                    // then gently explain why the app requires this permission
                    "相机权限是核心. Please grant the permission."
                } else {
                    // If it's the first time the user lands on this feature, or the user
                    // doesn't want to be asked again for this permission, explain that the
                    // permission is required
                    "必须给予相机权限. " +
                            "Please grant the permission"
                }
                Text(textToShow)
                Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
                    Text("Request permission")
                }
            }
        }
    }



}

9.Kotlin json 序列化



import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    // Serializing objects
    val data = Project("kotlinx.serialization", "Kotlin")
    val string = Json.encodeToString(data)
    println(string) // {"name":"kotlinx.serialization","language":"Kotlin"}
    // Deserializing back into objects
    val obj = Json.decodeFromString<Project>(string)
    println(obj) // Project(name=kotlinx.serialization, language=Kotlin)
}

10.kotlin 冷流



import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {

    runBlocking {

      val coldFlow =  flow {
            for (i in 1..5){
                delay(3000)
                emit(i)
            }
        }



        // 订阅者 1
        launch {
            coldFlow.collect { value ->
                println("Subscriber 1 received: $value")
            }
        }

        // 订阅者 2
        launch {
            delay(2000) // 延迟订阅,确保第二个订阅者在第一个订阅者之后
            coldFlow.collect { value ->

                println("Subscriber 2 received${Thread.currentThread().name}: $value")
            }
        }

        println("执行")






    }





}

11.kotlin 热流



import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {

runBlocking {
    // 创建一个 StateFlow 实例,初始状态为 0
    val stateFlow = MutableStateFlow(0)

    // 启动一个协程来更新 StateFlow 的状态
    launch {
        repeat(5) {
            delay(500) // 模拟数据生成
            stateFlow.value += 1

        }
    }

    // 订阅 StateFlow 并打印最新状态
    launch {
        stateFlow.collect { value ->
            println("Subscriber received: $value")
        }
    }
}

}


import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    runBlocking {
        // 创建一个 SharedFlow
        val sharedFlow = MutableSharedFlow<Int>()

        // 启动一个协程来发射数据
        launch {
            repeat(10) {
                delay(100) // 模拟异步操作
                sharedFlow.emit(it) // 发射数据

            }
        }

        // 订阅 SharedFlow
        launch {
            sharedFlow.collect { value ->
                println("Subscriber 1 received: $value")
            }
        }

        // 另一个订阅者
        launch {
            sharedFlow.collect { value ->
                println("Subscriber 2 received: $value")
            }
        }
    }
}

12.全局语言切换

在 Application created 方法 

    /**
         * 国际化绑定 已经帮存入 sharedPreferences里了
         */
         Lingver.init(this)



@Composable
fun LanguageSelectorKuangjia(localeViewmodel: LocaleViewmodel= hiltViewModel()) {

//    val locale = MyApplication.sharedPreferences.getString("locale",null)
//
//    val localeState = localeViewmodel.locale.observeAsState(
        if (locale == null) Locale.getDefault() else Locale(locale)
//        locale?.let { Locale(it) }?:Locale.getDefault()
//    )
    val current = LocalContext.current

    Column(modifier = Modifier
        .fillMaxSize()
        .padding(30.dp)) {
        // Example buttons to switch languages
        Row {
            Button(onClick = {
                Lingver.getInstance().setLocale(current,Locale.SIMPLIFIED_CHINESE)
                (current as Activity).recreate()
            }) {
                Text("中文")
            }
            Button(onClick = {
                Lingver.getInstance().setLocale(current,Locale.ENGLISH)
                (current as Activity).recreate()
            }) {
                Text("英文")
            }
        }
        // Apply the selected language
//        LanguageSwitcherKuangjia(localeState.value) {
            // Your UI content here, which will reflect the selected language
            Text(text = stringResource(id = R.string._1))
//        }
    }

}



自己写的扩展函数

/**
 * 转变语言扩展函数 会保留在当前的Navigation 导航里
 */
fun Context.changeLocale(locale: Locale) {
    Lingver.getInstance().setLocale(this,locale)
    (this as Activity).recreate()
}

在 导航中 重创建,也会跟随导航目的地,不会重置

13.使用 阿里矢量图

iconfont-阿里巴巴矢量图标库

1.存一个地方

2.下载这个插件

3.右键一个空文件夹

4.操作

 

14.文件分享

1.文件提供者

     <provider
            android:authorities="${applicationId}.fileprovider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

2. 外部存储空间权限文件

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

3. MediaStore



import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

public class ExportMedia {
    public static Uri saveExcelFileToMediaStoreInExternalDownloadExport(Context context, String fileName) {
        // 创建 ContentValues 对象
        ContentValues values = new ContentValues();
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
        values.put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS+"/EXPORT"); // 下载目录

        // 插入文件到 MediaStore
       return context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
    }



    public Uri queryExcelFiles(Context context,  String fileName) {
        String[] projection = {
                MediaStore.Files.FileColumns._ID,
                MediaStore.Files.FileColumns.DISPLAY_NAME,
                MediaStore.Files.FileColumns.MIME_TYPE
        };

        String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
        String[] selectionArgs = {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}; // .xlsx 的 MIME 类型

        Cursor cursor = context.getContentResolver().query(
                MediaStore.Files.getContentUri("external"),
                projection,
                selection,
                selectionArgs,
                null
        );

        try {
            while (cursor.moveToNext()) {
                long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
                String displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
                if (displayName.equals(fileName)){
                    // 处理查询结果,例如分享文件
                    return ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id);
                }
            }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }finally {
            if (cursor != null){
                cursor.close();
            }
        }
        return null;
    }
}
public class ShareUtils {

    public static void shareExcel(Context context,String path){
        // 文件路径,确保文件存在
        File fileToShare = new File(path);

        // 创建分享意图
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // MIME 类型

        // 使用 FileProvider 获取 content URI
        Uri fileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", fileToShare);
        shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);

        // 允许临时读取 URI 权限
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        // 启动分享对话框
        context.startActivity(Intent.createChooser(shareIntent, "分享一个Excel"));

    }
    public static void shareExcel(Context context,Uri uri){


        // 创建分享意图
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // MIME 类型

        // 使用 FileProvider 获取 content URI
        shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

        // 允许临时读取 URI 权限
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        // 启动分享对话框
        context.startActivity(Intent.createChooser(shareIntent, "分享一个Excel"));

    }
}
    <!--在sdcard中创建/删除文件的权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

15. NFC

1.开启权限

 <uses-permission android:name="android.permission.NFC" />

2.创建工具类



import android.nfc.tech.NfcA
import java.security.MessageDigest


object NfcHelper {


    private fun readUid(nfcA: NfcA): ByteArray {
        val command = byteArrayOf(xxxx.toByte(), xxxxx.toByte())
        val uid = nfcA.transceive(command)
        return byteArrayOf(uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7])
    }

    /**
     * 处理密码
     *
     * @param nfcA
     * @return
     */
    private fun handlePwd(nfcA: NfcA): ByteArray {
        val uidByte = readUid(nfcA).copyOf(7)
        val secret = byteArrayOf(xxxxxxxxxxxxxxxxxx)
        //两个数组重载运算符 拼接在一起
        val byteUnique = uidByte + secret

        //加密SHA -256
        val digest = MessageDigest.getInstance("SHA-256")
        // 更新 MessageDigest 实例,传入要哈希的数据
        digest.update(byteUnique)
        // 计算哈希值并返回 取前四位
        val pwdBytes = digest.digest().copyOf(4)


        return pwdBytes

    }

    /**
     * 新nfc芯片验证密码
     *
     * @param nfcA
     * @return
     */
    fun authNew(nfcA: NfcA) {
        val pwd = handlePwd(nfcA)
        //拼接两个数组
        val command = byteArrayOf(0x1B.toByte()) + pwd
        nfcA.transceive(command)
    }


    /**
     * 新nfc 芯片读方法
     *
     * @param nfcA
     * @param startPage
     * @param endPage
     * @return
     */
    fun readTag(nfcA: NfcA, startPage: Int, endPage: Int): List<Byte> {
        val list = arrayListOf<Byte>()
        for (i in startPage..endPage) {
            val data = nfcA.transceive(byteArrayOf(0x30, i.toByte()))
            list.addAll(data.asList().subList(0,4))
        }
        return list;
    }

    /**
     * 新nfc 芯片写方法
     *
     * @param nfcA
     * @param writeByte
     * @param block     扇区,也就是页
     */
    fun writeTag(nfcA: NfcA, writeByte: ByteArray, page: Int) {
        val cmd = byteArrayOf(0xA2.toByte(), page.toByte()) + writeByte
        nfcA.transceive(cmd)
    }


}

3.Model

/**
 * 全局唯一NFC模块
 */
@Module
@InstallIn(SingletonComponent::class)
object NfcModel {


    @Singleton
    @Provides
    fun provideNfcAdapter(@ApplicationContext context: Context): NfcAdapter {
        return NfcAdapter.getDefaultAdapter(context)
    }

}

4.viewModel

import android.nfc.NfcAdapter
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ActivityScoped
import javax.inject.Inject

/**
 * viewmodel不能设置作用域,否则会报错
 */
@HiltViewModel
class NfcViewModel @Inject constructor(val nfcAdapter: NfcAdapter):ViewModel() {

    // 使用 MutableLiveData 来持有数据



}

5.页面

@Composable
fun NfcMain(){

    var open by remember { mutableStateOf(false) }


   Column(Modifier.fillMaxSize()) {

       Box(modifier = Modifier
           .fillMaxWidth()
           .height(100.dp), contentAlignment = Alignment.Center){
           Button(onClick = { open = !open }) {
               Text(text = if (open) "点击关闭NFC" else "点击开启NFC")
           }
       }

       if (open){
           NfcPage()
       }else{
           Box(modifier = Modifier
               .fillMaxSize().background(Color.Blue)
               , contentAlignment = Alignment.Center){
               Text(text = "NFC未开启")
           }
       }




   }






}




@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NfcPage(modifier: Modifier = Modifier, nfcViewModel: NfcViewModel = hiltViewModel()) {
    var presses by remember { mutableIntStateOf(0) }


    val readOrWrite = remember {
        mutableStateOf(false)
    }


    val (text, setText) = remember { mutableStateOf("") }

    val nfcAdapter = nfcViewModel.nfcAdapter

    val list = remember {
        mutableStateListOf<String>()
    }
    val context = LocalContext.current
    LaunchedEffect(key1 = Unit) {


        val pendingIntent = PendingIntent.getActivity(
            context, 0, Intent(
                MyApplication.getApplication(), context::class.java
            )
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_IMMUTABLE
        )
        val filters = arrayOf(IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED))
        val arrayOf = arrayOf(arrayOf<String>(NfcA::class.java.name))

        nfcAdapter.enableReaderMode(context as Activity?, { tag ->
            val nfcA = NfcA.get(tag)
            Log.i("NfcA", "NfcA: $nfcA")

            nfcA.timeout = 3000
            try {
                nfcA.connect()



                Log.i("测试","nfc超时时间 ${nfcA.timeout} ")
                Log.i("测试","nfc最大长度 ${nfcA.maxTransceiveLength} ")

                NfcHelper.authNew(nfcA)

//                NewNfcHelper.authNew(nfcA)


                if (readOrWrite.value){
                    val readTag = NfcHelper.writeTag(nfcA, byteArrayOf(2,3,4,1), 4)
                    Log.i("测试","写入成功 $readTag")
                }else{
                    val readTag = NfcHelper.readTag(nfcA, 4, 4)
                    Log.i("测试","读取成功 $readTag")
                }

            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                nfcA.close()
            }




        }, NfcAdapter.FLAG_READER_NFC_A, null);


        nfcAdapter.enableForegroundDispatch(context, pendingIntent, filters, arrayOf)

    }

    DisposableEffect(key1 = Unit) {

        this.onDispose {

            nfcAdapter.disableForegroundDispatch(context as Activity)
            nfcAdapter.disableReaderMode(context as Activity)

        }
    }

    Scaffold(
        topBar = {
            TopAppBar(
                colors = topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.primaryContainer,
                    titleContentColor = MaterialTheme.colorScheme.primary,
                ),
                title = {
                    Box(
                        modifier = Modifier
                            .fillMaxWidth(),
                        contentAlignment = Alignment.Center // 设置 Box 的内容居中
                    ) {
                        TextField(
                            value = text, onValueChange = setText,
                            modifier = Modifier
                                .width(200.dp)
                                .height(50.dp),// 调整宽度
                            // 调整高度
                            textStyle = MaterialTheme.typography.bodyLarge.copy(
                                fontSize = 16.sp,
                                lineHeight = TextUnit(200f, TextUnitType.Sp)
                            ), // 调整字体大小
                        ) // 调整内边距
                    }


                }
            )
        },
        bottomBar = {
            BottomAppBar(
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                contentColor = MaterialTheme.colorScheme.primary,
            ) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 16.dp),
                    horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Button(modifier = Modifier.weight(1f), onClick = {


                    }) {
                        Text(text = "添加")
                    }
                    Button(modifier = Modifier.weight(1f), onClick = {
                        readOrWrite.value = !readOrWrite.value
                    }) {
                        Text(text = if (readOrWrite.value) "写入" else "读取")
                    }
                    Button(modifier = Modifier.weight(1f), onClick = {

                        list.clear()

                    }) {
                        Text(text = "清空")
                    }
                }

            }
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { presses++ }) {
                Icon(Icons.Default.Build, contentDescription = "Add")
            }
        }
    ) { innerPadding ->

        LazyColumn(modifier = Modifier.padding(innerPadding)) {
            items(items = list, key = { it }) {
                Text(
                    text = it,
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(40.dp),
                    textAlign = TextAlign.Center
                )
            }

        }
    }
}

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

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

相关文章

git其他人有改动,自己这边不要pull,先stash一下,pull了然后apply自己的stash。

git其他人有改动&#xff0c;自己这边不要pull&#xff0c;先stash一下&#xff0c;pull了然后apply自己的stash&#xff0c; git 冲突了怎么处理处理&#xff1f; git会提示冲突的文件 On branch experiments You have unmerged paths.(fix conflicts and run "git comm…

代码随想录 -- 回溯 -- 子集II

90. 子集 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 题目中说明nums中可能包含重复元素&#xff0c;所以要去重。 去重的前提是将数组nums排序&#xff01; 递归参数&#xff1a;nums&#xff0c;index&#xff0c;path递归出口&#xff1a;当遍历完num…

编译和链接笔记

翻译环境和运⾏环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第1种是翻译环境&#xff0c;在这个环境中源代码被转换为可执⾏的机器指令&#xff08;⼆进制指令&#xff09;。 第2种是执⾏环境&#xff0c;它⽤于实际执⾏代码。 1.翻译环境 其实翻译环境…

这3个证书在手,失业了也不怕.

在忙碌的职场生活中&#xff0c;考取一两个证书已成为众多职场人士的热门选择。 拥有这些证书不仅能为个人职业发展带来机遇&#xff0c;还能为职业转型铺平道路&#xff0c;特别是在主业遇到波折时。 接下来&#xff0c;让我们一同探索三个适合上班族的热门证书。 PMP认证&…

Unity 新NavMesh演示(1)

新版Navmash 导航寻路 保姆级入门讲解-CSDN博客 演示&#xff1a; 第一步 给场景中的BK添加导航网格表面组件 并设置详细参数 第二步 为player添加导航网格代理 并编写脚本设置target public class Text : MonoBehaviour {private NavMeshAgent agent;public Transform targe…

【从0开始自动驾驶】ros2编写自定义消息 msg文件和msg文件嵌套

【从0开始自动驾驶】ros2编写自定义消息 msg文件和msg文件嵌套 在工作空间内新建一个功能包在msg内创建对应的msg文件创建名为TestMsg.msg的文件创建名为TestSubMsg.msg的文件&#xff08;在前一个msg文件中引用&#xff09;修改CmakeList.txt修改package.xml文件编译 在工作空…

获取交易软件【热度排行数据】2024年9月26日,一股淡淡的牛味

2024年9月26日&#xff0c;一股淡淡的牛味 概念热度的排行榜和行业热度排行榜。 像是这种类型的数据&#xff0c;能不能加到我们的量化模型里面&#xff0c;作为选股和下单指令的判断条件之一呢&#xff1f; 下面图片&#xff0c;有很多数据接口&#xff0c;可以1对1帮助您解…

水面巡检船垃圾漂浮物检测系统源码分享

水面巡检船垃圾漂浮物检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of …

由于找不到vcruntime140.dll的原因分析及6种解决方法分享

在计算机使用过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是找不到vcruntime140.dll文件。那么&#xff0c;究竟vcruntime140.dll是什么&#xff1f;为什么会出现找不到的情况呢&#xff1f;本文将详细解析vcruntime140.dll的作用以及丢失的原因&#…

MySql Explain优化命令使用

MySql Explain优化命令使用 truncate table student // 自增id 从 0 开始 delete from student // 自增id 会保留 &#xff0c; 108 区别&#xff1a; 1&#xff1a;自增id 2&#xff1a;delete 可以恢复 truncate 无法恢复 前言 EXPLAIN 是一个用于获取 SQL 语句执行计划的…

从碳基到硅基,个人记忆留存方兴未艾!

关注我们 - 数字罗塞塔计划 - 说到记忆&#xff0c;我们可能会想到海马体&#xff0c;海马体是人类大脑中负责将短时记忆向长期存储转换的部分。科学家指出&#xff0c;海马体能够帮助大脑建立信息归档系统&#xff0c;并在需要的时候&#xff0c;快速将有用的信息检索出来。因…

HarmonyOS鸿蒙开发实战( Beta5.0)图片压缩实践方案

鸿蒙HarmonyOS NEXT开发实战往期文章必看&#xff08;持续更新......&#xff09; HarmonyOS NEXT应用开发性能实践总结 HarmonyOS NEXT应用开发案例实践总结合集 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门…

JavaJUnit包 JUnit5断言assert用法

慢慢来&#xff0c;一切都会解决的 —— 24.9.26 在Java中使用JUnit包下断言assert&#xff0c;要区分JUnit4和JUnit5的区别 JUnit4不支持一些断言句 需要引入JUnit5的支持 引入步骤&#xff1a; ① ② 在settings中点击plugins插件&#xff0c;搜索JUnit&#xff0c;选择…

89个H5小游戏源码

下载地址&#xff1a;https://download.csdn.net/download/w2sft/89791650 亲测可用&#xff0c;代码完整&#xff0c;都是htmljs&#xff0c;保存到本地即可。 游戏截图&#xff1a;

uniapp出现 下拉框等组件被遮挡 的分析

目录 1. 问题所示2. 代码复现3. 解决方法3.1 下拉框被遮挡3.2 uni-collapse-item 无法下拉的问题 1. 问题所示 下拉框被遮挡的问题&#xff1a; uni-collapse-item组件无法下拉的问题&#xff1a; 2. 代码复现 博主的代码精简如下&#xff1a; <template><view>…

基于Springboot+微信小程序 的高校社团管理小程序(含源码+数据库+lw)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 系统定…

Transcipher:从对称加密到同态加密

摘要 本文介绍了Transcipher的概念。在Transcipher的框架下&#xff0c;用户使用高效的对称加密&#xff0c;对自己的数据进行加密&#xff0c;然后将密文和私钥的同态加密密文传输给服务器。服务器进行同态解密&#xff0c;得到用户数据同态加密的密文。Transcipher通过将计算…

研控伺服驱动器通过EtherCAT转profinet网关与西门子PLC1200连接

本案例涉及西门子1200PLC与研控总线型混合伺服驱动器的连接&#xff0c;其在正常工作时需支持EtherCAT协议并作为EtherCAT从站&#xff0c;而监控系统中的PLC支持PROFINET协议。在本案例中采用EtherCat转ProfiNet网关&#xff0c;以实现PLC和伺服驱动器的整体交互。下图呈现了自…

andon安灯系统让生产异常快速高效解决

在现代化工厂的生产过程中&#xff0c;设备故障、物料短缺、工艺异常等突发事件屡见不鲜。这些异常情况不仅会直接影响生产效率&#xff0c;还会造成生产线停滞、产品质量下降&#xff0c;甚至引发安全事故。如何快速有效地解决这些异常&#xff0c;是提升生产效率和产品质量的…

java 解析excel

在Java中解析Excel文件&#xff0c;可以使用Apache POI库。以下是一个简单的例子&#xff0c;展示如何使用Apache POI读取一个Excel文件&#xff08;假设为.xlsx格式&#xff09;的内容。 首先&#xff0c;确保你的项目中包含了Apache POI的依赖。如果你使用Maven&#xff0c;…