作为一名从事Android开发多年的工程师,我深刻体会到框架开发在整个应用构建过程中的重要性。Android框架开发不仅仅是简单的API调用,更是一种系统性的架构思维。它能够帮助我们构建更健壮、更易维护的应用,同时提升开发效率。
在Kotlin成为Android官方推荐语言后,框架开发也迎来了新的变革。Kotlin的简洁语法和强大特性(如扩展函数、空安全等)让框架代码更加优雅。本文将基于Kotlin语言,深入探讨Android框架开发中的核心组件和最佳实践。
在res/values/strings.xml中定义默认字符串资源是Android应用国际化的基础。要添加其他语言支持:
注意:语言资源文件的命名规范为values-语言代码(如values-zh表示中文),更精确的可以加上地区代码(如values-zh-rCN表示简体中文)
系统语言改变时,应用默认会重启Activity以应用新语言。但有时我们需要更精细的控制:
kotlin复制// 创建新的配置
val config = resources.configuration
config.setLocale(Locale("zh")) // 设置为中文
// 更新配置
resources.updateConfiguration(config, resources.displayMetrics)
// 重启Activity
recreate()
实际开发中,建议将语言设置保存在SharedPreferences中,并在应用启动时应用设置。
在AndroidManifest.xml中,可以通过以下配置控制Activity的屏幕方向:
xml复制<activity
android:name=".MainActivity"
android:screenOrientation="portrait" /> <!-- 强制竖屏 -->
常用参数说明:
当屏幕方向改变时,Activity默认会重建。为了提供更好的用户体验:
经验:对于简单的布局变化,使用ConstraintLayout的Guideline和Barrier可以避免创建多套布局
重写以下方法保存临时状态:
kotlin复制override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("KEY_COUNTER", counter)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
counter = savedInstanceState.getInt("KEY_COUNTER")
}
ViewModel旨在以生命周期感知的方式存储和管理UI相关数据:
kotlin复制// 添加依赖
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
// 创建ViewModel
class CounterViewModel : ViewModel() {
var count = 0
}
// 在Activity/Fragment中使用
private val viewModel: CounterViewModel by viewModels()
ViewModel的优势:
ViewModel的生命周期比创建它的Activity/Fragment更长,但会在其所有者完全销毁时清除。这意味着:
LiveData是一种可观察的数据持有者,具有生命周期感知能力:
kotlin复制class UserViewModel : ViewModel() {
private val _userName = MutableLiveData<String>()
val userName: LiveData<String> = _userName // 对外暴露不可变版本
fun updateName(name: String) {
_userName.value = name
}
}
// 观察数据变化
viewModel.userName.observe(this) { name ->
textView.text = name
}
kotlin复制val userNameLength = Transformations.map(viewModel.userName) {
it?.length ?: 0
}
kotlin复制fun getUser(id: String): LiveData<User> {
return repository.getUser(id)
}
kotlin复制val combined = MediatorLiveData<Pair<String, Int>>()
combined.addSource(liveData1) { combined.value = it to liveData2.value }
combined.addSource(liveData2) { combined.value = liveData1.value to it }
在build.gradle中启用:
groovy复制android {
buildFeatures {
dataBinding = true
}
}
布局文件示例:
xml复制<layout>
<data>
<variable
name="user"
type="com.example.User" />
</data>
<TextView
android:text="@{user.name}"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}" />
</layout>
自定义属性绑定:
kotlin复制@BindingAdapter("imageUrl")
fun loadImage(view: ImageView, url: String?) {
Glide.with(view.context).load(url).into(view)
}
// XML中使用
<ImageView app:imageUrl="@{user.avatarUrl}" />
xml复制<EditText
android:text="@={viewModel.userName}" />
注意:双向绑定使用@=语法,会同步View和ViewModel的数据
ViewBinding为每个布局文件生成绑定类,替代findViewById:
kotlin复制// 模块级build.gradle
android {
buildFeatures {
viewBinding = true
}
}
// Activity中使用
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.textView.text = "Hello ViewBinding"
}
| 特性 | ViewBinding | DataBinding |
|---|---|---|
| 布局绑定 | ✓ | ✓ |
| 表达式支持 | ✗ | ✓ |
| 双向绑定 | ✗ | ✓ |
| 性能影响 | 小 | 中等 |
| 编译速度 | 快 | 较慢 |
选择建议:
即使使用ViewModel,当进程被系统回收时数据也会丢失。SavedStateHandle可以解决这个问题:
kotlin复制class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() {
val count = state.getLiveData<Int>("count", 0)
fun increment() {
count.value = (count.value ?: 0) + 1
}
}
// 获取实例
val viewModel: SavedStateViewModel by viewModels {
SavedStateViewModelFactory(application, this)
}
对于Parcelable对象:
kotlin复制data class User(val name: String, val age: Int) : Parcelable
// 保存
state.set("user", user)
// 获取
val user = state.get<User>("user")
AndroidViewModel是ViewModel的子类,提供Application上下文:
kotlin复制class MyAndroidViewModel(application: Application) : AndroidViewModel(application) {
private val context = application.applicationContext
fun getAppName(): String {
return context.getString(R.string.app_name)
}
}
适合需要访问以下系统服务的场景:
注意:避免在ViewModel中持有Activity/Fragment引用,这会导致内存泄漏
通过注解方式监听生命周期事件:
kotlin复制class MyLifecycleObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume() {
Log.d("Lifecycle", "Activity resumed")
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause() {
Log.d("Lifecycle", "Activity paused")
}
}
// 注册观察者
lifecycle.addObserver(MyLifecycleObserver())
kotlin复制class LocationComponent(context: Context, lifecycle: Lifecycle) : LifecycleObserver {
init {
lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
// 开始定位
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
// 停止定位
}
}
kotlin复制// 实体类
@Entity
data class User(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "first_name") val firstName: String?,
@ColumnInfo(name = "last_name") val lastName: String?
)
// DAO接口
@Dao
interface UserDao {
@Query("SELECT * FROM user")
fun getAll(): LiveData<List<User>>
@Insert
fun insertAll(vararg users: User)
}
// 数据库类
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// 创建数据库实例
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "database-name"
).build()
kotlin复制val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL DEFAULT 0")
}
}
Room.databaseBuilder(...)
.addMigrations(MIGRATION_1_2)
.build()
kotlin复制class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
@Database(entities = [User::class], version = 1)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase()
kotlin复制// 定义数据源
class UserDataSource(private val userDao: UserDao) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
val page = params.key ?: 0
val pageSize = params.loadSize
return try {
val users = userDao.getUsers(page, pageSize)
LoadResult.Page(
data = users,
prevKey = if (page == 0) null else page - 1,
nextKey = if (users.size < pageSize) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
// 创建Pager
val pager = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { UserDataSource(userDao) }
).flow
// 在ViewModel中暴露Flow
val userPagingFlow = pager.flow.cachedIn(viewModelScope)
kotlin复制lifecycleScope.launch {
viewModel.userPagingFlow.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
kotlin复制class UploadWorker(appContext: Context, workerParams: WorkerParameters)
: Worker(appContext, workerParams) {
override fun doWork(): Result {
// 执行后台任务
return try {
// 模拟上传操作
Thread.sleep(3000)
Result.success()
} catch (e: Exception) {
Result.failure()
}
}
}
kotlin复制val uploadWorkRequest = OneTimeWorkRequestBuilder<UploadWorker>()
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.build()
WorkManager.getInstance(context).enqueue(uploadWorkRequest)
kotlin复制WorkManager.getInstance(context)
.getWorkInfoByIdLiveData(uploadWorkRequest.id)
.observe(this) { workInfo ->
when (workInfo?.state) {
WorkInfo.State.SUCCEEDED -> { /* 任务成功 */ }
WorkInfo.State.FAILED -> { /* 任务失败 */ }
else -> { /* 其他状态 */ }
}
}
在res/navigation/nav_graph.xml中定义导航结构:
xml复制<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/homeFragment"
android:name="com.example.HomeFragment"
android:label="Home">
<action
android:id="@+id/action_home_to_detail"
app:destination="@id/detailFragment" />
</fragment>
<fragment
android:id="@+id/detailFragment"
android:name="com.example.DetailFragment"
android:label="Detail" />
</navigation>
使用Safe Args插件传递参数:
groovy复制plugins {
id "androidx.navigation.safeargs.kotlin"
}
xml复制<fragment android:id="@+id/detailFragment">
<argument
android:name="userId"
app:argType="integer" />
</fragment>
kotlin复制val action = HomeFragmentDirections.actionHomeToDetail(userId = 123)
findNavController().navigate(action)
kotlin复制val args: DetailFragmentArgs by navArgs()
val userId = args.userId
kotlin复制// 启动协程
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) {
// 执行IO操作
fetchDataFromNetwork()
}
// 更新UI
updateUI(result)
}
kotlin复制class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
fun fetchData() {
viewModelScope.launch {
val result = repository.fetchData()
_data.value = result
}
}
}
Room原生支持协程:
kotlin复制@Dao
interface UserDao {
@Insert
suspend fun insert(user: User)
@Query("SELECT * FROM user")
fun getAll(): Flow<List<User>>
}
// 使用
viewModelScope.launch {
userDao.insert(User("John"))
userDao.getAll().collect { users ->
// 处理用户列表
}
}
问题:在ViewModel中持有Activity/Fragment引用会导致内存泄漏。
解决方案:
问题:配置更改(如旋转屏幕)导致观察者被重复注册。
解决方案:
问题:复杂表达式导致布局渲染变慢。
解决方案:
问题:数据库结构变更导致应用崩溃。
解决方案:
ViewModel优化:
LiveData优化:
DataBinding优化:
协程优化:
kotlin复制// Model层 - Repository
class UserRepository {
suspend fun getUsers(): List<User> {
// 从网络或数据库获取数据
}
}
// ViewModel层
class UserViewModel(private val repo: UserRepository) : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
fun loadUsers() {
viewModelScope.launch {
_users.value = repo.getUsers()
}
}
}
// View层 - Activity/Fragment
binding.button.setOnClickListener {
viewModel.loadUsers()
}
viewModel.users.observe(this) { users ->
adapter.submitList(users)
}
kotlin复制// MainActivity中处理导航
class MainActivity : AppCompatActivity() {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
setupActionBarWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
kotlin复制@Test
fun viewModelTest() = runTest {
val viewModel = MyViewModel(TestRepository())
viewModel.loadData()
val value = viewModel.data.getOrAwaitValue()
assertEquals("expected", value)
}
kotlin复制fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS
): T? {
var data: T? = null
val latch = CountDownLatch(1)
val observer = Observer<T> { value ->
data = value
latch.countDown()
}
observeForever(observer)
latch.await(time, timeUnit)
removeObserver(observer)
return data
}
kotlin复制@RunWith(AndroidJUnit4::class)
class UserDaoTest {
private lateinit var database: TestDatabase
private lateinit var dao: UserDao
@Before
fun setup() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(
context, TestDatabase::class.java
).build()
dao = database.userDao()
}
@Test
fun insertAndGetUser() = runTest {
val user = User(1, "John")
dao.insert(user)
val loaded = dao.getById(1)
assertEquals(user.name, loaded?.name)
}
@After
fun cleanup() {
database.close()
}
}
kotlin复制if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 使用新API
} else {
// 回退实现
}
groovy复制implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
资源限定符:
使用ContextCompat获取兼容资源:
kotlin复制val color = ContextCompat.getColor(context, R.color.primary)
kotlin复制if (ContextCompat.checkSelfPermission(this, permission) ==
PackageManager.PERMISSION_GRANTED) {
// 有权限
} else {
// 请求权限
}
在实际项目中,框架的选择和组合应该根据项目规模、团队经验和具体需求来决定。小型项目可能只需要ViewModel和LiveData,而大型复杂项目可能需要结合Room、Paging、WorkManager等多个组件。最重要的是保持架构的一致性和可维护性。