Android开发补充内容

Android开发补充内容

fragment

通信

fragmentactivity通信的一种原生方法是使用Bundle

java 复制代码
Bundle bundle = new Bundle();
bundle.putString('msg', "数据");
BlankFragment bf = new BlankFragment();
bf.setArguments(bundle);

生命周期

  1. 第一次显示:onAttach -> onCreate -> onCreateView -> onActivityCreated -> onStart -> onResume
  2. 按home:onPause -> onStop(界面显示的fragment不变)
  3. 重新显示:onStart -> onResume
  4. 按返回:onPause -> onStop -> onDestroyView -> onDestroy -> onDetach

使用了FragmentTransactionaddToBackStack方法

  1. 切换fragment:onPause -> onStop -> onDestroyView(但没销毁时,同栈切换)
  • 返回原fragment:onCreateView -> onActivityCreated -> onStart -> onResume(同栈返回)

Okhttp

基本使用

模块级的build.gradle导入依赖:

kotlin 复制代码
implementation 'com.squareup.okhttp3:okhttp:4.9.1'

创建客户端:

kotlin 复制代码
client = OkHttpClient.Builder()
    .addInterceptor {
        val request = it.request()
        val response = it.proceed(request)
        Log.d("biluo", request.url.toString())
        response
    }
    .connectTimeout(20, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .build()

创建和使用请求:

kotlin 复制代码
val request = Request.Builder().url("http://localhost/user/list").build()
// 阻塞方式
val response = client.newCall(request).execute()
// 异步方式
val response = client.newCall(request).enqueue(object : Callback {
    override fun onFailure(call: Call, e: IOException) {
        TODO("Not yet implemented")
    }

    override fun onResponse(call: Call, response: Response) {
        TODO("Not yet implemented")
    }
})

websocket

kotlin 复制代码
val observable = Observable.create<String> { emitter ->
   val request = Request.Builder().url("${AppConstant.WS_URL}/${user.username}").build()
   val ws = client.newWebSocket(request, object : WebSocketListener() {
      override fun onOpen(webSocket: WebSocket, response: Response) {
         wsObservable.ws = webSocket
         Log.d("biluo", "socket连接成功")
      }

      override fun onMessage(webSocket: WebSocket, text: String) {
         Log.d("biluo", "接收到消息:$text")
         emitter.onNext(text)
      }

      override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
         Log.d("biluo", "socket连接准备断开")
      }

      override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
         wsObservable.ws = null
         Log.d("biluo", "socket连接已经断开")
         //emitter.onComplete()
      }

      override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
         Log.e("biluo", "异常:${t.message}", t)
         emitter.onError(t)
      }
   })
   emitter.setDisposable(object : Disposable {
      override fun dispose() {
         Log.d("biluo", "dispose...")
         ws.close(1000, "Closing by dispose")
      }

      override fun isDisposed(): Boolean {
         return ws.close(1000, null)
      }

   })
}
wsObservable.observable = observable
return wsObservable

Retrofit

基于OkHttp,同时支持了RESTful API风格设计

基本使用

模块级的build.gradle导入依赖:

kotlin 复制代码
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

创建Retrofit实例:

添加了GsonConvert后,会自动把响应回来的Json字符串转换为对象。

kotlin 复制代码
val retrofit = Retrofit.Builder()
	.baseUrl(BASE_URL)
	.addConverterFactory(GsonConverterFactory.create())
	.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
	.client(client)
	.build()

定义Api类:

kotlin 复制代码
interface UserApi {
	@GET("$USER_URL/{username}")
	fun getByUsername(@Path("username") username: String): Call<MsgResult<User>>

	@POST("$USER_URL/register")
	fun register(@Body user: User): Call<MsgResult<Nothing>>
}

创建Api类:

kotlin 复制代码
UserApi = retrofit.create(UserApi::class.java)

RxJava

可以结合Retrofit一起使用。

基本使用

前提:依赖在Retrofit中已经导入,并创建了RxJavaConvert到Retrofit中。

之前定义Api的返回值可以改为RxJava提供的类型。

它提供了:

  • Observable:流式数据类型,用于访问多个项的异步序列。

  • Flowable:与Observable类似,支持背压(backpressure)机制,即当数据生产速度超过消费速度时,能够控制数据的流动。

  • Single:单发数据类型,只能且必须发射一个数据。

  • Maybe:单发数据类型,可以发射零个或一个数据。

  • Completable:不发射任何数据,只通知流的结束。

之前的UserApi类变为:

kotlin 复制代码
interface UserApi {
	@GET("$USER_URL/{username}")
	fun getByUsername(@Path("username") username: String): Single<MsgResult<User>>

	@POST("$USER_URL/register")
	fun register(@Body user: User): Single<MsgResult<Nothing>>
}

使用:

kotlin 复制代码
val disposable: Disposable = single.subscribeOn(Schedulers.io()).observeOn(AndroidScheduler.mainScheduler)
	.subscribe({ res ->
		//成功获取结果res,做什么
	}, { e ->
		//出现异常后做些什么
	})

Retrofix下的RxJava没有AndroidScheduler类,可以自己定义一个:

kotlin 复制代码
object AndroidScheduler : Executor {

	val mainScheduler: Scheduler = Schedulers.from(this)
	private val handler: Handler = Handler(Looper.getMainLooper())

	override fun execute(command: Runnable) {
		handler.post(command)
	}
}

定时任务

kotlin 复制代码
val disposable = Observable.interval(15L, TimeUnit.MINUTES)
	.subscribe({ _ ->
        val json = Gson().toJson(Message("ping", null, appUser.username))
		wsObservable.ws?.send(json)
    Log.d("biluo", "发送了一个心跳包")
	}) { t ->
    	Log.e("biluo", "异常:", t)
        wsObservable.ws?.close(1000, "心跳续约失败")
	}

Hilt

可以进行实例的管理,并进行依赖注入。类似spring IOC

项目级build.gradle导入插件:

kotlin 复制代码
id 'com.google.dagger.hilt.android' version '2.51.1' apply false

模块级build.gradle导入插件:

kotlin 复制代码
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'

模块级build.gradle导入依赖:

kotlin 复制代码
implementation 'com.google.dagger:hilt-android:2.51.1'
    kapt 'com.google.dagger:hilt-compiler:2.51.1'

基本使用

参考教程:https://blog.csdn.net/Mr_Tony/article/details/124516871

使用Hilt必须要先用@HiltAndroidApp绑定Application

哪个类需要进行依赖注入,就得加上@AndroidEntryPoint进行绑定,然后注入的地方需要使用@Inject

  • @HiltAndroidApp:用于标注 Application 类,触发 Hilt 的代码生成操作,生成应用级别的组件。

  • @AndroidEntryPoint:用于标注 Android 组件(如 ActivityFragment 等),告知 Hilt 这些组件可以接收依赖注入。

  • @Inject

    • 用于标注需要注入的依赖项,可以是字段、构造函数或方法。
    • 对于字段,Hilt 会自动注入相应的依赖;对于构造函数,Hilt 会使用它来创建类的实例

注:@AndroidEntryPoint不仅仅可以绑定到Activity,还可以绑定到其他地方,请参考:https://developer.android.google.cn/training/dependency-injection/hilt-android

kotlin 复制代码
@HiltAndroidApp
class App: Application()

// 类似spring的 @Component
class DateFormatter @Inject constructor() {
  fun testDateFormatter(){
    Log.e("YM--->","---->获取DateFormatter")
  }
}

// 类似spring在类中进行依赖注入也需要当前类被spring管理
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
  @Inject	// 类似spring的 @Autowire
  lateinit var dateFormatter: DateFormatter

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_hilt)
    dateFormatter.testDateFormatter()
  }
}

进阶使用

  • @Module:用于标注提供依赖项的类。
  • @Provides:用于标注 Module 类中的提供依赖项的方法,Hilt 会在需要时调用这些方法。
  • @Binds:用于在Module类中绑定接口和实现类,告知 Hilt 在需要提供接口的实例时要使用哪种实现。
  • @InstallIn:用于标注 ModuleEntryPoint,指定其作用范围。
  • @Singleton:用于标注提供的依赖项为单例,即在整个应用程序的生命周期内只存在一个实例。
  • @HiltViewModel
    • 用于标注 ViewModel 类,使其可以使用 Hilt 进行依赖注入。
    • 注意:ViewModel 不能直接使用 @Inject 注解,需要使用 @HiltViewModel
  • @ActivityScoped:用于标注提供的依赖项为 Activity 级别的作用域,即依赖项的生命周期与 Activity 相同。

补充:

  • @HiltViewModel

    • 用于标注 ViewModel 类,使其可以使用 Hilt 进行依赖注入。

    • 注意:ViewModel 不能直接使用 @Inject 注解,需要使用 @HiltViewModel

  • @ViewModelScoped:用于标注提供的依赖项为 ViewModel 级别的作用域,即依赖项的生命周期与 ViewModel 相同。

  • @EntryPoint:用于获取 Hilt 提供的实例,特别是在不能直接使用 @AndroidEntryPoint 的类中(如 ContentProviderBroadcastReceiver)。

  • @Provides举例:
kotlin 复制代码
class HelloClass {
  fun hello() = "Hello"
}

@Module
// 表示整个应用程序的生命周期内都是单例
@InstallIn(SingletonComponent::class)
object AppModule {
  @Provides
  @Singleton	// 单例
  fun provideSomeOtherClass(): HelloClass {
    return HelloClass()
  }
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
  @Inject
  lateinit var helloClass: HelloClass

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    helloClass.hello()
  }
}
  • @Binds举例:在这个示例中,@Binds 注解用于将 ApiServiceImpl 绑定到 ApiService 接口上,这样当需要 ApiService 的实例时,Hilt 会提供 ApiServiceImpl 的实例。
kotlin 复制代码
interface ApiService {
  fun doSomething()
}

class ApiServiceImpl @Inject constructor() : ApiService {
  override fun doSomething() {
    println("Doing something in ApiServiceImpl")
  }
}

@Module
@InstallIn(SingletonComponent::class)
abstract class ApiServiceModule {
  @Binds
  abstract fun bindApiService(apiServiceImpl: ApiServiceImpl): ApiService
}
  • @ActivityScope例子:
kotlin 复制代码
@Module
@InstallIn(ActivityComponent::class)
object ActivityModule {
  @Provides
  @ActivityScoped
  fun provideActivityDependency(): ActivityDependency {
    return ActivityDependency()
  }
}

例子

Application类:

kotlin 复制代码
@HiltAndroidApp
class MyApplication : Application() {}

模块类:

kotlin 复制代码
@InstallIn(SingletonComponent::class)
@Module
class AppModule {
	@Provides
	@Singleton
	fun okHttpClient(): OkHttpClient {
		return OkHttpClient.Builder()
			.addInterceptor {
				val request = it.request()
				val response = it.proceed(request)
				Log.d("biluo", request.url.toString())
				response
			}
			.connectTimeout(20, TimeUnit.SECONDS)
			.readTimeout(30, TimeUnit.SECONDS)
			.retryOnConnectionFailure(false)
			.build()
	}

	@Provides
	@Singleton
	fun retrofit(client: OkHttpClient): Retrofit {
		return Retrofit.Builder()
			.baseUrl(BASE_URL)
			.addConverterFactory(GsonConverterFactory.create())
			.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
			.client(client)
			.build()
	}

	@Provides
	@Singleton
	fun userApi(retrofit: Retrofit): UserApi = retrofit.create(UserApi::class.java)
}

使用:

kotlin 复制代码
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
	lateinit var userApi: UserApi
}

组件库

Material Components

Material Components for Android:Google 官方提供的 Material Design 组件库。

举例:BottomNavigationView------底部导航栏

  1. 编写菜单xml文件bottom_nav_menu.xml

    xml 复制代码
    <?xml version="1.0" encoding="utf-8" ?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
        <item
            android:id="@+id/nav_home"
            android:icon="@drawable/home"
            android:title="@string/tab1" />
        <item
                android:id="@+id/nav_tab2"
                android:icon="@drawable/tab2"
                android:title="@string/tab2" />
        <item
                android:id="@+id/nav_tb3"
                android:icon="@drawable/tb3"
                android:title="@string/tab3" />
    </menu>
  2. 在相应的布局文件中引入BottomNavigationView,例如MainActivity。

    xml 复制代码
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
     
        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/fragment_container"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" />
      
    	<com.google.android.material.bottomnavigation.BottomNavigationView
                android:id="@+id/bottomNavigationView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom"
                android:background="@color/purple_200"
                app:menu="@menu/bottom_nav_menu"/>
    </LinearLayout>
  3. 准备好相应的Fragment,为BottomNavigationView设置点击事件,根据点击的项切换Fragment

    kotlin 复制代码
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottom_navigation)
            val fragmentContainer = supportFragmentManager.beginTransaction()
    
            // 默认加载 HomeFragment
            fragmentContainer.replace(R.id.fragment_container, HomeFragment())
            fragmentContainer.commit()
    
            bottomNavigationView.setOnItemSelectedListener { item ->
                when (item.itemId) {
                    R.id.nav_home -> {
                        supportFragmentManager.beginTransaction()
                            .replace(R.id.fragment_container, HomeFragment())
                            .commit()
                        true
                    }
                    R.id.nav_tb2 -> {
                        supportFragmentManager.beginTransaction()
                            .replace(R.id.fragment_container, NavTb2Fragment())
                            .commit()
                        true
                    }
                    R.id.nav_tb3 -> {
                        supportFragmentManager.beginTransaction()
                            .replace(R.id.fragment_container, NavTb3Fragment())
                            .commit()
                        true
                    }
                    else -> false
                }
            }
        }
    }

Jetpack Compose

Jetpack Compose Material:Jetpack Compose 的 Material Design 组件库。

举例:

kotlin 复制代码
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
	private lateinit var shared: SharedPreferences
	@Inject lateinit var userApi: VehicleOwnerApi

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		shared = getSharedPreferences("user", MODE_PRIVATE)
		val id = shared.getString("id", "")!!
		val password = shared.getString("password", "")!!
		setContent {
			MaterialTheme { loginScreen(id, password) }
		}
	}

	@Composable
	fun loginScreen(originId: String, originPwd: String) {
		val ID_REGEX = Regex("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$")
		val PWD_REGEX = Regex("^[a-zA-Z\\d]{4,16}$")

		var id by remember { mutableStateOf(originId) }
		var password by remember { mutableStateOf(originPwd) }
		var idErrorMsg by remember { mutableStateOf("") }
		var pwdErrorMsg by remember { mutableStateOf("") }

		var checked by remember { mutableStateOf(false) }

		fun checkIdFormat(): Boolean {
			idErrorMsg = if (id.isEmpty()) "身份证号不能为空" else {
				if (!ID_REGEX.matches(id)) "身份证号格式错误" else ""
			}
			return idErrorMsg.isEmpty()
		}

		fun checkPwdFormat(): Boolean {
			pwdErrorMsg = if (password.isEmpty()) "密码不能为空" else {
				if (!PWD_REGEX.matches(password)) "密码必须由4-16位的字母或数字组成" else ""
			}
			return pwdErrorMsg.isEmpty()
		}

		fun login() {
			if (!checkIdFormat() || !checkPwdFormat()) return
			Utils.handleSingle(this, "token", userApi.login(VehicleOwner(id, password))) {
				GlobalData.token = it.toString()
				// 如果选择了记住用户,将身份证号和密码写入sharePreferences
				if (checked) {
					shared.edit().putString("id", id).apply()
					shared.edit().putString("password", password).apply()
				} else {
					shared.edit().remove("id").apply()
					shared.edit().remove("password").apply()
				}
				// 登录成功,跳转到主页
				Utils.showToast(this, "登录成功")
				val intent = Intent(this, MainActivity::class.java)
				intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
				startActivity(intent)
			}
		}

		Column(modifier = Modifier.padding(16.dp)) {
			TextField(  // 身份证输入框
				value = id,
				onValueChange = { id = it },
				label = { Text("身份证号") },
				placeholder = { Text("请输入身份证号") },
				isError = idErrorMsg.isNotEmpty(),
				trailingIcon = {
					if (idErrorMsg.isNotEmpty()) {    // 如果有错误提示,则显示错误图标
						Icon(Icons.Filled.Error, contentDescription = "Error")
					}
				},
				supportingText = { Text(idErrorMsg) },    // 显示在下方的错误提示
				modifier = Modifier.fillMaxWidth()
				/*.onFocusChanged { focusState -> // 进入页面时会直接显示错误提示,不友好;改为点击登录时再检查格式
                if (!focusState.isFocused) {
                    idErrorMsg = if (id.isEmpty()) "身份证号不能为空" else {
                        if (!ID_REGEX.matches(id)) "身份证号格式错误" else ""
                    }
                }
            }*/
			)
			TextField(  // 密码输入框
				value = password,
				onValueChange = { password = it },
				label = { Text("密码") },
				placeholder = { Text("请输入密码") },
				isError = pwdErrorMsg.isNotEmpty(),
				trailingIcon = {
					if (pwdErrorMsg.isNotEmpty()) {    // 如果有错误提示,则显示错误图标
						Icon(Icons.Filled.Error, contentDescription = "Error")
					}
				},
				supportingText = { Text(pwdErrorMsg) },    // 显示在下方的错误提示
				modifier = Modifier.fillMaxWidth().padding(top = 4.dp)
			)
			Row(
				verticalAlignment = Alignment.CenterVertically, // 行内元素垂直居中
				horizontalArrangement = Arrangement.spacedBy((-6).dp),  // 行内元素间距
				modifier = Modifier.clickable(
					interactionSource = remember { MutableInteractionSource() },
					indication = null   // 移除点击效果
				) { // 点击整个 Row 切换选中状态
					checked = !checked
				}
			) {
				Checkbox(checked = checked, onCheckedChange = { checked = it })	// 复选框
				Text("记住用户")	// 文本
			}
			Button(	// 按钮
				onClick = { login() },
				modifier = Modifier.fillMaxWidth()
			) {
				Text("登     录")
			}
		}
	}
}
相关推荐
大G哥23 分钟前
Kotlin Lambda语法错误修复
android·java·开发语言·kotlin
鸿蒙布道师4 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
androidwork4 小时前
Kotlin Android工程Mock数据方法总结
android·开发语言·kotlin
xiangxiongfly9156 小时前
Android setContentView()源码分析
android·setcontentview
人间有清欢8 小时前
Android开发报错解决
android
每次的天空9 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin
每次的天空11 小时前
Android学习总结之Binder篇
android·学习·binder
峥嵘life11 小时前
Android 有线网开发调试总结
android
是店小二呀13 小时前
【算法-链表】链表操作技巧:常见算法
android·c++·算法·链表