Android开发补充内容

发布于:2025-05-09 ⋅ 阅读:(19) ⋅ 点赞:(0)

fragment

通信

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

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导入依赖:

implementation 'com.squareup.okhttp3:okhttp:4.9.1'

创建客户端:

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()

创建和使用请求:

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

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导入依赖:

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字符串转换为对象。

val retrofit = Retrofit.Builder()
	.baseUrl(BASE_URL)
	.addConverterFactory(GsonConverterFactory.create())
	.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
	.client(client)
	.build()

定义Api类:

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类:

UserApi = retrofit.create(UserApi::class.java)

RxJava

可以结合Retrofit一起使用。

基本使用

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

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

它提供了:

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

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

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

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

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

之前的UserApi类变为:

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>>
}

使用:

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

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

object AndroidScheduler : Executor {

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

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

定时任务

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导入插件:

id 'com.google.dagger.hilt.android' version '2.51.1' apply false

模块级build.gradle导入插件:

id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'

模块级build.gradle导入依赖:

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

@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举例:
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 的实例。
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例子:
@Module
@InstallIn(ActivityComponent::class)
object ActivityModule {
  @Provides
  @ActivityScoped
  fun provideActivityDependency(): ActivityDependency {
    return ActivityDependency()
  }
}

例子

Application类:

@HiltAndroidApp
class MyApplication : Application() {}

模块类:

@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)
}

使用:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
	lateinit var userApi: UserApi
}

组件库

Material Components

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

  • 特点
    • 完全遵循 Material Design 规范。
    • 提供丰富的组件(如 ButtonCardViewBottomNavigationView 等)。
    • 与 AndroidX 库无缝集成。
  • 适用场景:需要遵循 Material Design 规范的项目。
  • GitHub:https://github.com/material-components/material-components-android

举例:BottomNavigationView——底部导航栏

  1. 编写菜单xml文件bottom_nav_menu.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。

    <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

    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 组件库。

举例:

@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("登     录")
			}
		}
	}
}

网站公告

今日签到

点亮在社区的每一天
去签到