Android 开发使用 Kotlin 注意事项点
一、空安全与平台类型
1. 正确处理 Android SDK 的可空性
fun dangerousGetView(): View {
return findViewById(R.id.my_view)
}
fun safeGetView(): View? {
return findViewById(R.id.my_view)
}
fun getViewWithAssert(): View {
return findViewById(R.id.my_view)!!
}
fun getViewOrDefault(): View {
return findViewById(R.id.my_view) ?: createDefaultView()
}
fun setupView() {
val view = findViewById<View?>(R.id.my_view)
view?.setOnClickListener {
}
}
2. 使用 lateinit 的注意事项
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: MyAdapter
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recycler_view)
adapter = MyAdapter()
recyclerView.adapter = adapter
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
}
fun riskyMethod() {
}
fun safeMethod() {
if (::recyclerView.isInitialized) {
recyclerView.layoutManager = LinearLayoutManager(this)
}
}
}
二、生命周期管理
1. 避免在生命周期回调中泄露
class MyActivity : AppCompatActivity() {
private val disposables = CompositeDisposable()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Observable.interval(1, TimeUnit.SECONDS)
.subscribe { }
Observable.interval(1, TimeUnit.SECONDS)
.subscribe { }
.addTo(disposables)
}
override fun onDestroy() {
disposables.clear()
super.onDestroy()
}
}
2. 使用 ViewModel 和 LiveData
class MainViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> get() = _users
fun loadUsers() {
viewModelScope.launch {
try {
val result = repository.getUsers()
_users.value = result
} catch (e: Exception) {
}
}
}
fun updateUser(user: User) {
_users.value = _users.value?.map {
if (it.id == user.id) user else it
}
}
}
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.users.observe(this) { users ->
adapter.submitList(users)
}
}
}
三、协程使用
1. 正确管理协程作用域
class MyFragment : Fragment() {
fun loadData() {
viewLifecycleOwner.lifecycleScope.launch {
val data = withContext(Dispatchers.IO) {
repository.fetchData()
}
updateUI(data)
}
}
fun structuredConcurrency() {
lifecycleScope.launch {
try {
val deferred1 = async { fetchUser() }
val deferred2 = async { fetchPosts() }
val user = deferred1.await()
val posts = deferred2.await()
} catch (e: Exception) {
}
}
}
fun withTimeout() {
lifecycleScope.launch {
try {
val result = withTimeout(5000) {
fetchSlowData()
}
} catch (e: TimeoutCancellationException) {
showTimeoutError()
}
}
}
}
2. 线程切换与 Dispatchers
suspend fun fetchData(): Result<Data> {
return withContext(Dispatchers.IO) {
repository.getData()
}
}
fun processData() {
lifecycleScope.launch {
showLoading()
try {
val data = withContext(Dispatchers.IO) {
repository.fetchData()
}
val processedData = withContext(Dispatchers.Default) {
processLargeData(data)
}
withContext(Dispatchers.Main) {
showData(processedData)
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
showError(e)
}
} finally {
withContext(Dispatchers.Main) {
hideLoading()
}
}
}
}
四、扩展函数与属性
1. 创建实用的扩展函数
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
fun View.invisible() {
visibility = View.INVISIBLE
}
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
fun Context.dpToPx(dp: Int): Float {
return dp * resources.displayMetrics.density
}
fun Fragment.showDialog(
title: String,
message: String,
positiveAction: () -> Unit = {}
) {
MaterialAlertDialogBuilder(requireContext())
.setTitle(title)
.setMessage(message)
.setPositiveButton("确定") { _, _ -> positiveAction() }
.setNegativeButton("取消", null)
.show()
}
fun RecyclerView.setup(
layoutManager: RecyclerView.LayoutManager,
adapter: RecyclerView.Adapter<*>,
divider: RecyclerView.ItemDecoration? = null
) {
this.layoutManager = layoutManager
this.adapter = adapter
divider?.let { addItemDecoration(it) }
}
2. 避免过度使用扩展函数
fun String.toBitmap(): Bitmap? { }
val String.md5: String
get() = try {
val digest = MessageDigest.getInstance("MD5")
val bytes = digest.digest(toByteArray())
bytes.joinToString("") { "%02x".format(it) }
} catch (e: NoSuchAlgorithmException) {
""
}
fun List<Any>.logAll() {
forEach { println(it) }
}
fun List<User>.logUserNames() {
forEach { println(it.name) }
}
五、数据类与密封类
1. 数据类的正确使用
data class ApiResponse<T>(
val success: Boolean,
val data: T?,
val error: String?,
val code: Int
) {
fun isSuccessful(): Boolean = success && data != null
fun getErrorOrNull(): String? = if (!success) error else null
}
data class LoginState(
val isLoading: Boolean = false,
val isSuccess: Boolean = false,
val error: String? = null,
val user: User? = null
) {
fun loading() = copy(isLoading = true, error = null)
fun success(user: User) = copy(isLoading = false, isSuccess = true, user = user)
fun error(message: String) = copy(isLoading = false, error = message)
}
data class BadUser(
val name: String,
val permissions: MutableList<String>
)
data class GoodUser(
val name: String,
val permissions: List<String>
) {
fun addPermission(permission: String): GoodUser {
return copy(permissions = permissions + permission)
}
}
2. 密封类处理状态和事件
sealed class Resource<out T> {
object Loading : Resource<Nothing>()
data class Success<out T>(val data: T) : Resource<T>()
data class Error(val exception: Throwable) : Resource<Nothing>()
}
sealed class Event {
data class ShowToast(val message: String) : Event()
data class NavigateTo(val destination: String) : Event()
object ShowLoading : Event()
object HideLoading : Event()
}
class MyViewModel : ViewModel() {
private val _events = Channel<Event>()
val events = _events.receiveAsFlow()
fun performAction() {
viewModelScope.launch {
_events.send(Event.ShowLoading)
try {
_events.send(Event.NavigateTo("success"))
} catch (e: Exception) {
_events.send(Event.ShowToast(e.message ?: "错误"))
} finally {
_events.send(Event.HideLoading)
}
}
}
}
viewModel.events.onEach { event ->
when (event) {
is Event.ShowToast -> showToast(event.message)
is Event.NavigateTo -> navigateTo(event.destination)
Event.ShowLoading -> showLoading()
Event.HideLoading -> hideLoading()
}
}.launchIn(lifecycleScope)
六、集合操作与性能
1. 使用序列优化性能
fun processLargeList(list: List<Data>): List<Result> {
return list
.filter { it.isValid() }
.map { it.transform() }
.filter { it.isReady() }
.sortedBy { it.priority }
.take(100)
}
fun processLargeListEfficiently(list: List<Data>): List<Result> {
return list.asSequence()
.filter { it.isValid() }
.map { it.transform() }
.filter { it.isReady() }
.sortedBy { it.priority }
.take(100)
.toList()
}
fun processDataStream(dataList: List<Data>): Flow<Result> {
return dataList.asFlow()
.filter { it.isValid() }
.map { it.transform() }
.onEach { }
.flowOn(Dispatchers.Default)
}
2. 避免重复计算
fun updateUI(data: List<User>) {
val activeUsers = data.filter { it.isActive }
val inactiveUsers = data.filter { !it.isActive }
}
fun updateUI(data: List<User>) {
val (activeUsers, inactiveUsers) = data.partition { it.isActive }
}
fun processItems(items: List<String>) {
val upperItems = items.map { it.uppercase() }
val lowerItems = items.map { it.lowercase() }
}
fun processItems(items: List<String>) {
val transformedItems = items.map {
Pair(it.uppercase(), it.lowercase())
}
val upperItems = transformedItems.map { it.first }
val lowerItems = transformedItems.map { it.second }
}
七、内存管理与泄漏
1. 避免常见的内存泄漏
class MyActivity : AppCompatActivity() {
private val apiClient = ApiClient(this)
private val apiClient = ApiClient(applicationContext)
private val callback = object : SomeCallback {
override fun onResult() {
doSomething()
}
}
private val callback = WeakCallback(this)
class WeakCallback(activity: MyActivity) : SomeCallback {
private val weakActivity = WeakReference(activity)
override fun onResult() {
weakActivity.get()?.doSomething()
}
}
}
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
2. 正确使用 by lazy
class MyFragment : Fragment() {
private val viewModel: MainViewModel by lazy {
ViewModelProvider(this).get(MainViewModel::class.java)
}
private val recyclerView: RecyclerView by lazy {
requireView().findViewById(R.id.recycler_view)
}
private val adapter: MyAdapter by lazy {
MyAdapter().apply {
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
recyclerView.adapter = adapter
}
}
八、构建器模式与 DSL
1. 使用 DSL 简化 UI 构建
fun RecyclerView.setup(config: RecyclerViewConfig.() -> Unit) {
val configObj = RecyclerViewConfig().apply(config)
layoutManager = configObj.layoutManager
adapter = configObj.adapter
configObj.itemDecoration?.let { addItemDecoration(it) }
configObj.itemAnimator?.let { itemAnimator = it }
}
class RecyclerViewConfig {
var layoutManager: RecyclerView.LayoutManager? = null
var adapter: RecyclerView.Adapter<*>? = null
var itemDecoration: RecyclerView.ItemDecoration? = null
var itemAnimator: RecyclerView.ItemAnimator? = null
}
recyclerView.setup {
layoutManager = LinearLayoutManager(context)
adapter = MyAdapter()
itemDecoration = DividerItemDecoration(context, LinearLayoutManager.VERTICAL)
}
2. Intent 构建器
inline fun <reified T : Activity> Context.intentOf(
vararg pairs: Pair<String, Any?>
): Intent {
return Intent(this, T::class.java).apply {
pairs.forEach { (key, value) ->
when (value) {
is Int -> putExtra(key, value)
is String -> putExtra(key, value)
is Parcelable -> putExtra(key, value)
is Serializable -> putExtra(key, value)
}
}
}
}
val intent = intentOf<DetailActivity>(
"id" to item.id,
"name" to item.name,
"data" to item.data
)
startActivity(intent)
九、测试注意事项
1. 编写可测试的代码
class BadViewModel {
fun loadData() {
GlobalScope.launch {
val data = ApiService.getInstance().getData()
}
}
}
class GoodViewModel(
private val repository: DataRepository,
private val dispatcher: CoroutineDispatcher = Dispatchers.Main
) : ViewModel() {
private val _data = MutableLiveData<Resource<Data>>()
val data: LiveData<Resource<Data>> = _data
fun loadData() {
viewModelScope.launch(dispatcher) {
_data.value = Resource.Loading
try {
val result = withContext(Dispatchers.IO) {
repository.getData()
}
_data.value = Resource.Success(result)
} catch (e: Exception) {
_data.value = Resource.Error(e)
}
}
}
}
@Test
fun testLoadDataSuccess() = runTest {
val mockRepository = mockk<DataRepository>()
coEvery { mockRepository.getData() } returns testData
val viewModel = GoodViewModel(mockRepository, testDispatcher)
viewModel.loadData()
val result = viewModel.data.getOrAwaitValue()
assertTrue(result is Resource.Success)
}
2. 使用依赖注入
val appModule = module {
single { createApiService() }
single<DataRepository> { DataRepositoryImpl(get()) }
viewModel { MainViewModel(get()) }
}
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: DataRepository
) : ViewModel() {
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
}
十、资源管理
1. 正确使用字符串资源
textView.text = "欢迎回来,${user.name}!"
textView.text = getString(R.string.welcome_back, user.name)
val itemCount = items.size
textView.text = resources.getQuantityString(R.plurals.item_count, itemCount, itemCount)
2. Bitmap 和资源释放
fun loadBitmapSafely(context: Context, @DrawableRes resId: Int): Bitmap? {
return try {
val options = BitmapFactory.Options().apply {
inPreferredConfig = Bitmap.Config.RGB_565
inSampleSize = 2
}
BitmapFactory.decodeResource(context.resources, resId, options)
} catch (e: OutOfMemoryError) {
null
}
}
fun cleanupBitmaps(bitmaps: List<Bitmap?>) {
bitmaps.forEach { bitmap ->
bitmap?.apply {
if (!isRecycled) {
recycle()
}
}
}
}
fun readFileSafely(path: String): String? {
return try {
File(path).bufferedReader().use { reader ->
reader.readText()
}
} catch (e: Exception) {
null
}
}
十一、版本兼容性
1. API 版本检查
fun setupFeatures() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setupNotificationChannel()
} else {
setupLegacyNotifications()
}
@RequiresApi(Build.VERSION_CODES.N)
fun newApiMethod() {
}
fun Context.createShortcutCompat() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createShortcut()
}
}
}
fun checkAndRequestPermissions() {
val requiredPermissions = arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.ACCESS_FINE_LOCATION
)
val missingPermissions = requiredPermissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (missingPermissions.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
missingPermissions.toTypedArray(),
PERMISSION_REQUEST_CODE
)
}
}
十二、性能优化
1. RecyclerView 优化
class MyAdapter : ListAdapter<Item, MyViewHolder>(DiffCallback()) {
class DiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem == newItem
}
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, clickListener)
}
override fun onBindViewHolder(
holder: MyViewHolder,
position: Int,
payloads: List<Any>
) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
} else {
payloads.forEach { payload ->
if (payload is ItemUpdate) {
holder.updatePartially(payload)
}
}
}
}
}
2. 避免主线程阻塞
fun scheduleBackgroundWork() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
.build()
WorkManager.getInstance(applicationContext)
.enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, workRequest)
}
class MyWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
return try {
performLongRunningTask()
Result.success()
} catch (e: Exception) {
if (runAttemptCount < MAX_RETRY) {
Result.retry()
} else {
Result.failure()
}
}
}
private suspend fun performLongRunningTask() {
delay(5000)
}
}
十三、错误处理
1. 统一的错误处理
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
val isSuccess: Boolean get() = this is Success<T>
val isError: Boolean get() = this is Error
fun onSuccess(action: (T) -> Unit): Result<T> {
if (this is Success) action(data)
return this
}
fun onError(action: (Exception) -> Unit): Result<T> {
if (this is Error) action(exception)
return this
}
}
suspend fun fetchData(): Result<Data> {
return try {
val data = apiService.getData()
Result.Success(data)
} catch (e: IOException) {
Result.Error(NetworkException("网络错误", e))
} catch (e: Exception) {
Result.Error(e)
}
}
viewModelScope.launch {
when (val result = repository.fetchData()) {
is Result.Success -> _data.value = result.data
is Result.Error -> _error.value = result.exception.message
}
}
十四、总结要点
必须遵守的 Kotlin Android 开发原则:
- 空安全第一:始终正确处理可空类型,避免使用
!!
- 生命周期感知:所有异步操作都应与生命周期绑定
- 主线程保护:不在主线程执行耗时操作
- 内存管理:及时释放资源,避免内存泄漏
- 协程优先:使用协程替代回调,但要注意作用域
- 不可变数据:优先使用不可变数据和纯函数
- 扩展函数适度:合理使用扩展,不要过度设计
- 测试驱动:编写可测试的代码结构
- 资源优化:正确处理图片和大型资源
- 版本兼容:处理好不同 API 级别的兼容性
推荐的架构模式:
View (Activity/Fragment) → ViewModel → Repository → Data Source
↑ ↑ ↑
LiveData/Flow CoroutineScope Retrofit/Room
必备工具和库:
- Android KTX:官方 Kotlin 扩展
- Coroutines:异步处理
- ViewModel & LiveData:生命周期管理
- Room:数据库
- Retrofit:网络请求
- Koin/Hilt:依赖注入
- Coil/Glide:图片加载