面试题 - Android - 综合技术

235 阅读8分钟

1. Android架构模式深度解析

让我们用一个做菜的比喻来理解这三种架构模式:

MVC (Model-View-Controller)

想象一个餐厅场景:

  • Model(厨师):负责做菜(处理数据)
  • View(服务员):负责上菜(展示界面)
  • Controller(经理):协调厨师和服务员(处理业务逻辑)
graph LR
    Controller-->Model
    Controller-->View
    Model-->Controller
    View-->Controller

问题:经理(Controller)要管太多事,容易混乱。

MVP (Model-View-Presenter)

改进后的餐厅模式:

  • Model(厨师):专注做菜
  • View(服务员):专注服务顾客
  • Presenter(传菜员):专门负责厨师和服务员之间的沟通
// MVP示例代码
interface MainContract {
    interface View {
        fun showLoading()
        fun showData(data: String)
    }
    
    interface Presenter {
        fun loadData()
    }
}

class MainPresenter(
    private val view: MainContract.View,
    private val model: DataModel
) : MainContract.Presenter {
    
    override fun loadData() {
        view.showLoading()
        model.getData { data ->
            view.showData(data)
        }
    }
}

MVVM (Model-View-ViewModel)

智能餐厅模式:

  • Model(厨师):还是负责做菜
  • View(智能餐桌):自动感知食物变化
  • ViewModel(智能系统):自动协调厨师和餐桌
class UserViewModel : ViewModel() {
    private val _userData = MutableLiveData<User>()
    val userData: LiveData<User> = _userData
    
    fun loadUser() {
        viewModelScope.launch {
            _userData.value = repository.getUser()
        }
    }
}

三种架构对比:

graph TB
    subgraph MVC
        M1[Model]-->C[Controller]
        C-->V1[View]
    end
    
    subgraph MVP
        M2[Model]-->P[Presenter]
        P-->V2[View]
        V2-->P
    end
    
    subgraph MVVM
        M3[Model]-->VM[ViewModel]
        VM<-->V3[View]
    end

2. Android存储方式详解

让我们用不同类型的储物柜来理解各种存储方式:

SharedPreferences

就像一个小型储物格:

  • 适合存储简单的键值对
  • 全部加载到内存,读取快但占内存
  • 不适合大量数据
// 最佳实践
object PreferenceManager {
    private lateinit var sp: SharedPreferences
    
    fun init(context: Context) {
        sp = context.getSharedPreferences("app_config", Context.MODE_PRIVATE)
    }
    
    fun saveString(key: String, value: String) {
        sp.edit().putString(key, value).apply() // 使用apply而不是commit
    }
}

SQLite数据库

像一个有组织的文件柜:

  • 适合结构化数据
  • 支持复杂查询
  • 性能好但使用复杂
// Room数据库示例
@Entity
data class User(
    @PrimaryKey val id: Int,
    val name: String,
    val age: Int
)

@Dao
interface UserDao {
    @Query("SELECT * FROM user")
    fun getAll(): List<User>
}

文件存储

就像一个大型储物间:

  • 适合存储大文件
  • 完全自由的存储格式
  • 需要自己管理文件
// 文件存储最佳实践
object FileStorage {
    fun saveFile(context: Context, fileName: String, content: String) {
        try {
            context.openFileOutput(fileName, Context.MODE_PRIVATE).use {
                it.write(content.toByteArray())
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

存储方式选择流程:

flowchart TD
    A[需要存储数据] --> B{数据量大小?}
    B -->|小量简单数据| C[SharedPreferences]
    B -->|结构化数据| D[SQLite]
    B -->|大文件| E[文件存储]
    B -->|需要共享| F[ContentProvider]

3. 热修复原理深度解析

让我们用修理房子的比喻来理解热修复:

基本原理

想象Android应用是一栋房子:

  • 原始代码:房子的原始结构
  • 补丁:修复材料
  • 热修复:不拆房子就能修复问题
flowchart TD
    A[发现Bug] --> B[准备补丁]
    B --> C[下发补丁]
    C --> D[加载补丁]
    D --> E[重启生效]
    
    subgraph 补丁制作
    B1[修复代码] --> B2[生成Dex]
    B2 --> B3[差分处理]
    end

实现方案

  1. 类加载方案
class HotFixManager {
    fun loadPatch(context: Context, patchFile: File) {
        try {
            // 1. 获取PathClassLoader
            val classLoader = context.classLoader as PathClassLoader
            
            // 2. 反射获取dexElements数组
            val pathList = classLoader.javaClass
                .superclass
                .getDeclaredField("pathList")
                .apply { isAccessible = true }
                .get(classLoader)
                
            // 3. 将补丁dex插入到数组前面
            val dexElements = pathList.javaClass
                .getDeclaredField("dexElements")
                .apply { isAccessible = true }
            
            // 具体插入逻辑...
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}
  1. 底层替换方案
class NativeHotFix {
    external fun replaceMethod(
        originalMethod: Method,
        newMethod: <img src="Method" alt="" width="70%" />
    )
}

注意事项:

  1. 及时验证补丁有效性
  2. 做好补丁版本管理
  3. 设置补丁大小限制
  4. 添加补丁加载失败的容错机制

4. 机型适配全面解析

想象你在设计一件能适应不同人穿的衣服:

屏幕适配

  1. 今日头条方案
class ScreenAdaptManager {
    private val DESIGN_WIDTH = 360f // 设计稿宽度
    
    fun adapt(activity: Activity) {
        val displayMetrics = activity.resources.displayMetrics
        val targetDensity = displayMetrics.widthPixels / DESIGN_WIDTH
        val targetDensityDpi = (160 * targetDensity).toInt()
        
        displayMetrics.apply {
            density = targetDensity
            densityDpi = targetDensityDpi
            scaledDensity = targetDensity
        }
    }
}
  1. 宽高限定符
res/
    layout/               # 默认布局
    layout-sw600dp/      # 平板布局
    layout-land/         # 横屏布局
    values-xhdpi/        # 高分辨率值

系统版本适配

class VersionAdapter {
    @SuppressLint("NewApi")
    fun adaptPermission(activity: Activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // Android 6.0及以上权限处理
            activity.requestPermissions(
                arrayOf(Manifest.permission.CAMERA),
                100
            )
        } else {
            // 低版本处理
        }
    }
}

适配流程图:

flowchart TD
    A[开始适配] --> B{判断设备类型}
    B --> C[手机]
    B --> D[平板]
    
    C --> E{判断系统版本}
    D --> E
    
    E --> F[低版本处理]
    E --> G[高版本处理]
    
    F --> H[屏幕适配]
    G --> H
    
    H --> I[布局适配]
    H --> J[资源适配]

5. 多渠道打包技术

想象你在一个工厂生产同一款产品,但需要贴不同的标签:

方案一:Gradle配置

android {
    flavorDimensions "channel"
    productFlavors {
        xiaomi {
            dimension "channel"
            manifestPlaceholders = [
                CHANNEL_ID: "xiaomi",
                CHANNEL_NAME: "小米应用商店"
            ]
        }
        huawei {
            dimension "channel"
            manifestPlaceholders = [
                CHANNEL_ID: "huawei",
                CHANNEL_NAME: "华为应用市场"
            ]
        }
    }
}

方案二:美团瓦力打包

class WalleChannelWriter {
    fun writeChannel(apkFile: File, channel: String) {
        // 写入渠道信息到APK的ZIP Comment
        val commentBytes = channel.toByteArray()
        RandomAccessFile(apkFile, "rw").use { raf ->
            raf.seek(apkFile.length() - 2)
            raf.write(commentBytes)
            raf.write(commentBytes.size)
        }
    }
}

打包流程:

flowchart TD
    A[原始APK] --> B{选择打包方式}
    B --> C[Gradle多渠道]
    B --> D[美团瓦力]
    
    C --> E[编译多个APK]
    D --> F[修改ZIP Comment]
    
    E --> G[多渠道包]
    F --> G

6. MVP内存泄漏防治

让我们用租房合同的方式来理解内存泄漏:

问题本质

  • View(租客)搬走了,但Presenter(房东)还持有View的引用(合同未解除)
  • Activity销毁时,Presenter还在执行异步任务

解决方案

  1. 弱引用方案
class SafePresenter {
    // 使用弱引用持有View
    private var viewRef: WeakReference<IMainView>? = null
    
    fun attachView(view: IMainView) {
        viewRef = WeakReference(view)
    }
    
    fun getView(): IMainView? = viewRef?.get()
    
    fun detachView() {
        viewRef?.clear()
        viewRef = null
    }
}
  1. 生命周期感知
class LifecyclePresenter : LifecycleObserver {
    private var view: IMainView? = null
    private val scope = CoroutineScope(Dispatchers.Main)
    
    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        view = null
        scope.cancel() // 取消所有协程
    }
}

内存泄漏防治流程:

flowchart TD
    A[Presenter创建] --> B[使用弱引用持有View]
    B --> C[注册生命周期监听]
    C --> D{Activity是否销毁?}
    D -->|是| E[清理View引用]
    D -->|否| F[继续持有]
    E --> G[取消异步任务]

8. 64K问题解决方案

想象应用是一本书,方法数就像书的页数:

问题本质

  • 单个DEX文件最多支持65536个方法
  • 超出限制会导致构建失败

解决方案

  1. 开启MultiDex
android {
    defaultConfig {
        multiDexEnabled true
        
        // 指定主Dex包含的类
        multiDexKeepFile file('multidex-config.txt')
    }
}

dependencies {
    implementation "androidx.multidex:multidex:2.0.1"
}
  1. Application配置
class MyApplication : MultiDexApplication() {
    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)
        MultiDex.install(this)
    }
}
  1. 优化方案
class MethodOptimizer {
    // 1. 移除未使用的代码
    @Proguard(
        "-keep class com.example.main.** { *; }",
        "-dontwarn com.example.unused.**"
    )
    
    // 2. 延迟加载非必要功能
    object FeatureLoader {
        fun loadFeature(context: Context) {
            DexClassLoader(
                "feature.dex",
                context.codeCache.absolutePath,
                null,
                context.classLoader
            )
        }
    }
}

处理流程:

flowchart TD
    A[检查方法数] --> B{是否超过64K?}
    B -->|是| C[启用MultiDex]
    B -->|否| D[正常构建]
    C --> E[分析方法分布]
    E --> F[优化代码]
    F --> G[延迟加载]

9. Gradle构建速度优化

让我们把Gradle构建过程比作一条生产线:

优化方案

  1. 基础配置优化
// gradle.properties
org.gradle.parallel=true // 并行构建
org.gradle.daemon=true   // 守护进程
org.gradle.caching=true  // 构建缓存
org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=512m // 增加内存
  1. 依赖优化
dependencies {
    // 使用implementation替代api
    implementation 'androidx.appcompat:appcompat:1.3.0'
    
    // 排除重复依赖
    implementation('com.example:library') {
        exclude group: 'com.android.support'
    }
}
  1. 构建配置优化
android {
    // 开启构建缓存
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
        }
    }
    
    // 动态配置变体
    variantFilter { variant ->
        if (variant.buildType.name == 'debug' &&
            variant.getFlavors().get(0).name == 'mock') {
            variant.setIgnore(true)
        }
    }
}

优化流程图:

flowchart TD
    A[分析构建时间] --> B[配置优化]
    B --> C[依赖优化]
    C --> D[资源优化]
    
    B --> E[开启并行构建]
    B --> F[启用构建缓存]
    B --> G[增加内存配置]
    
    C --> H[移除无用依赖]
    C --> I[使用implementation]
    
    D --> J[移除未使用资源]
    D --> K[开启资源压缩]

10. Android设备唯一标识获取

想象这是给每个设备分配一个独特的身份证:

获取方案

  1. 多重标识获取
class DeviceIdGenerator {
    fun getDeviceId(context: Context): String {
        return buildDeviceId(
            getImei(context),
            getAndroidId(context),
            getSerialNumber(),
            getMacAddress(context)
        )
    }
    
    private fun buildDeviceId(vararg ids: String?): String {
        return ids.filterNotNull()
            .firstOrNull()
            ?: UUID.randomUUID().toString()
    }
    
    @SuppressLint("HardwareIds")
    private fun getImei(context: Context): String? {
        return try {
            (context.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager)
                ?.getImei()
        } catch (e: Exception) {
            null
        }
    }
}
  1. 持久化存储
class DeviceIdStorage {
    private val DEVICE_ID_KEY = "device_id"
    
    fun saveDeviceId(context: Context, deviceId: String) {
        context.getSharedPreferences("device", Context.MODE_PRIVATE)
            .edit()
            .putString(DEVICE_ID_KEY, deviceId)
            .apply()
    }
}

获取流程:

flowchart TD
    A[开始获取] --> B{检查存储的ID}
    B -->|存在| C[返回存储ID]
    B -->|不存在| D{尝试获取IMEI}
    D -->|成功| E[使用IMEI]
    D -->|失败| F{尝试AndroidId}
    F -->|成功| G[使用AndroidId]
    F -->|失败| H[生成UUID]
    E --> I[存储ID]
    G --> I
    H --> I

11. Android P HTTP限制处理

想象这是给应用添加安全门禁:

解决方案

  1. 网络安全配置
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </base-config>
    
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <trust-anchors>
            <certificates src="@raw/my_cert"/>
        </trust-anchors>
    </domain-config>
</network-security-config>
  1. OkHttp配置
class HttpClient {
    fun createOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .apply {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
                    // 配置信任所有证书(仅测试环境)
                    val trustAllCerts = arrayOf<TrustManager>(
                        object : X509TrustManager {
                            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
                            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
                            override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
                        }
                    )
                    
                    val sslContext = SSLContext.getInstance("SSL")
                    sslContext.init(null, trustAllCerts, SecureRandom())
                    sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
                }
            }
            .build()
    }
}

适配流程:

flowchart TD
    A[检查Android版本] --> B{是否Android P+}
    B -->|是| C[配置网络安全]
    B -->|否| D[使用原有配置]
    C --> E[添加证书配置]
    C --> F[处理HTTP请求]
    E --> G[测试网络连接]
    F --> G

12. AOP(面向切面编程)在Android中的应用

让我们把AOP比作一个自动化的检查站:

常见应用场景

  1. 权限检查
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class PermissionCheck(vararg val permissions: String)

@Aspect
class PermissionAspect {
    @Around("@annotation(permissionCheck)")
    fun checkPermission(joinPoint: ProceedingJoinPoint, permissionCheck: PermissionCheck) {
        val context = joinPoint.getContext()
        if (hasPermissions(context, permissionCheck.permissions)) {
            joinPoint.proceed()
        } else {
            requestPermissions(context, permissionCheck.permissions)
        }
    }
}
  1. 性能监控
@Aspect
class PerformanceAspect {
    @Around("execution(* com.example.app.*.*(..))") 
    fun monitorMethod(joinPoint: ProceedingJoinPoint): Any? {
        val startTime = System.nanoTime()
        val result = joinPoint.proceed()
        val endTime = System.nanoTime()
        
        Log.d("Performance", 
            "${joinPoint.signature.name} took ${(endTime - startTime)/1000000}ms")
        return result
    }
}
  1. 埋点统计
@Target(AnnotationTarget.FUNCTION)
annotation class Track(val eventName: String)

@Aspect
class TrackingAspect {
    @Around("@annotation(track)")
    fun trackMethod(joinPoint: ProceedingJoinPoint, track: Track) {
        // 执行方法前的埋点
        TrackManager.track("${track.eventName}_start")
        
        val result = joinPoint.proceed()
        
        // 执行方法后的埋点
        TrackManager.track("${track.eventName}_end")
        return result
    }
}

AOP实现流程:

flowchart TD
    A[定义切面] --> B[确定切入点]
    B --> C[编写通知逻辑]
    C --> D[织入代码]
    D --> E[运行时处理]
    
    subgraph 切面类型
    F[Before通知]
    G[After通知]
    H[Around通知]
    end
    
    subgraph 应用场景
    I[权限检查]
    J[性能监控]
    K[日志记录]
    L[埋点统计]
    end

13. MVVM深入实践

把MVVM比作一个智能家居系统:

基础架构

// 数据模型
data class User(
    val id: Int,
    val name: String,
    val age: Int
)

// ViewModel
class UserViewModel : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> = _user
    
    private val _loading = MutableLiveData<Boolean>()
    val loading: LiveData<Boolean> = _loading
    
    fun loadUser() {
        viewModelScope.launch {
            _loading.value = true
            try {
                _user.value = repository.getUser()
            } finally {
                _loading.value = false
            }
        }
    }
}

数据绑定

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.example.UserViewModel" />
    </data>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{viewModel.user.name}" />
            
        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="@{viewModel.loading ? View.VISIBLE : View.GONE}" />
    </LinearLayout>
</layout>

MVVM工作流程:

flowchart TD
    A[View层] -->|用户操作| B[ViewModel层]
    B -->|数据更新| C[LiveData]
    C -->|通知变化| A
    B -->|请求数据| D[Model层]
    D -->|返回数据| B
    
    subgraph 数据绑定
    E[XML布局]
    F[DataBinding]
    G[双向绑定]
    end

14. 数据埋点实现方案

把埋点比作在路上设置检查站:

代码埋点

class TrackManager {
    companion object {
        private val events = mutableListOf<TrackEvent>()
        
        fun track(eventName: String, params: Map<String, Any> = emptyMap()) {
            val event = TrackEvent(
                name = eventName,
                params = params,
                timestamp = System.currentTimeMillis()
            )
            events.add(event)
            
            // 达到一定数量后上传
            if (events.size >= 100) {
                uploadEvents()
            }
        }
        
        private fun uploadEvents() {
            viewModelScope.launch(Dispatchers.IO) {
                try {
                    api.uploadEvents(events)
                    events.clear()
                } catch (e: Exception) {
                    Log.e("Track", "Upload failed", e)
                }
            }
        }
    }
}

无侵入式埋点

// 使用AOP实现自动埋点
@Aspect
class AutoTrackAspect {
    @Around("execution(* android.app.Activity.onCreate(..))")
    fun trackPageView(joinPoint: ProceedingJoinPoint) {
        val activity = joinPoint.target as Activity
        TrackManager.track("page_view", mapOf(
            "page_name" to activity.javaClass.simpleName
        ))
        joinPoint.proceed()
    }
}

埋点流程:

flowchart TD
    A[触发埋点] --> B{埋点类型}
    B -->|代码埋点| C[手动调用]
    B -->|无侵入埋点| D[AOP处理]
    C --> E[收集数据]
    D --> E
    E --> F[本地缓存]
    F --> G{达到上传条件?}
    G -->|是| H[批量上传]
    G -->|否| F

17. 单元测试实践

MVP测试

@RunWith(MockitoJUnitRunner::class)
class UserPresenterTest {
    @Mock
    private lateinit var view: UserContract.View
    
    @Mock
    private lateinit var repository: UserRepository
    
    private lateinit var presenter: UserPresenter
    
    @Before
    fun setup() {
        presenter = UserPresenter(view, repository)
    }
    
    @Test
    fun `test load user success`() = runBlocking {
        // Given
        val user = User(1, "Test")
        `when`(repository.getUser(1)).thenReturn(user)
        
        // When
        presenter.loadUser(1)
        
        // Then
        verify(view).showLoading()
        verify(view).showUser(user)
        verify(view).hideLoading()
    }
}

MVVM测试

@RunWith(AndroidJUnit4::class)
class UserViewModelTest {
    @get:Rule
    val instantExecutorRule = InstantTaskExecutorRule()
    
    @Mock
    private lateinit var repository: UserRepository
    
    private lateinit var viewModel: UserViewModel
    
    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        viewModel = UserViewModel(repository)
    }
    
    @Test
    fun `test load user data`() = runBlocking {
        // Given
        val user = User(1, "Test")
        `when`(repository.getUser(1)).thenReturn(user)
        
        // When
        viewModel.loadUser(1)
        
        // Then
        assertEquals(user, viewModel.user.value)
        assertEquals(false, viewModel.loading.value)
    }
}

测试流程:

flowchart TD
    A[编写测试用例] --> B[准备测试数据]
    B --> C[执行测试]
    C --> D{检查结果}
    D -->|通过| E[记录覆盖率]
    D -->|失败| F[修复问题]
    F --> C

18. Android 9.0反射限制绕过方案

把反射限制绕过比作找到后门钥匙:

核心实现

class ReflectionHelper {
    fun bypassAndroidPRestriction() {
        try {
            // 1. 获取forName方法
            val forName = Class::class.java.getDeclaredMethod("forName", String::class.java)
            
            // 2. 获取getDeclaredMethod方法
            val getDeclaredMethod = Class::class.java.getDeclaredMethod(
                "getDeclaredMethod",
                String::class.java,
                Array<Class<*>>::class.java
            )
            
            // 3. 反射获取VMRuntime类
            val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") as Class<*>
            
            // 4. 获取VMRuntime单例
            val getRuntime = getDeclaredMethod.invoke(
                vmRuntimeClass,
                "getRuntime",
                null
            ) as Method
            
            val runtime = getRuntime.invoke(null)
            
            // 5. 设置豁免
            val setHiddenApiExemptions = getDeclaredMethod.invoke(
                vmRuntimeClass,
                "setHiddenApiExemptions",
                arrayOf(Array<String>::class.java)
            ) as Method
            
            setHiddenApiExemptions.invoke(runtime, arrayOf("L"))
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

绕过流程:

flowchart TD
    A[检测Android版本] --> B{是否>=9.0?}
    B -->|是| C[获取VMRuntime]
    B -->|否| D[直接使用反射]
    C --> E[设置豁免规则]
    E --> F[执行反射操作]
    D --> F

20. AndroidX迁移详解

迁移步骤实现

// 项目级build.gradle
buildscript {
    ext {
        androidx_version = '1.0.0'
    }
}

// app模块build.gradle
android {
    defaultConfig {
        android.useAndroidX = true
        android.enableJetifier = true
    }
}

dependencies {
    // 更新依赖
    implementation "androidx.appcompat:appcompat:$androidx_version"
    implementation "androidx.core:core-ktx:$androidx_version"
}

代码迁移工具

class AndroidXMigrationTool {
    fun migrateToAndroidX(project: Project) {
        // 1. 扫描所有Java/Kotlin文件
        project.files.filter { it.extension in listOf("java", "kt") }
            .forEach { file ->
                // 2. 替换import语句
                val content = file.readText()
                val newContent = content.replace(
                    "android.support.",
                    "androidx."
                )
                file.writeText(newContent)
            }
        
        // 3. 扫描XML文件
        project.files.filter { it.extension == "xml" }
            .forEach { file ->
                val content = file.readText()
                val newContent = replaceXmlSupport(content)
                file.writeText(newContent)
            }
    }
}

迁移流程:

flowchart TD
    A[开始迁移] --> B[备份项目]
    B --> C[更新Gradle配置]
    C --> D[更新依赖]
    D --> E[代码迁移]
    E --> F[XML迁移]
    F --> G[测试验证]
    G --> H{是否正常?}
    H -->|是| I[完成迁移]
    H -->|否| J[修复问题]
    J --> G

21. Android屏幕适配技巧

今日头条适配方案

class ScreenAdapter {
    companion object {
        private const val WIDTH_DESIGN = 360f  // 设计稿宽度
        
        fun adaptScreen(activity: Activity) {
            val application = activity.application
            val displayMetrics = application.resources.displayMetrics
            
            val targetDensity = displayMetrics.widthPixels / WIDTH_DESIGN
            val targetDensityDpi = (160 * targetDensity).toInt()
            
            displayMetrics.density = targetDensity
            displayMetrics.scaledDensity = targetDensity
            displayMetrics.densityDpi = targetDensityDpi
        }
    }
}

自定义百分比布局

class PercentFrameLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : FrameLayout(context, attrs) {
    
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val screenWidth = context.resources.displayMetrics.widthPixels
        val screenHeight = context.resources.displayMetrics.heightPixels
        
        children.forEach { child ->
            val params = child.layoutParams as MarginLayoutParams
            // 计算百分比宽高
            if (params.width > 0) {
                params.width = (screenWidth * (params.width / 1000f)).toInt()
            }
            if (params.height > 0) {
                params.height = (screenHeight * (params.height / 1000f)).toInt()
            }
        }
        
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}

适配流程:

flowchart TD
    A[分析设计稿] --> B[选择适配方案]
    B --> C[密度适配]
    B --> D[百分比适配]
    B --> E[约束布局]
    C --> F[验证效果]
    D --> F
    E --> F
    F --> G{是否完美适配?}
    G -->|是| H[完成适配]
    G -->|否| I[调整方案]
    I --> B