记录 Android 面试题, 有时间过来翻翻。

博主博客

目录

  • 七十一、支付宝支付接入流程?
  • 七十二、如何实现线程安全的单例?
  • 七十三、如何保证Service不被杀死?
  • 七十四、ContentProvider、ContentResolver、ContentObserver的关系?
  • 七十五、如何导入外部数据库?
  • 七十六、LinearLayout、RelativeLayout、FrameLayout性能对比?
  • 七十七、什么是Scheme协议?
  • 七十八、HandlerThread的原理及优缺点?
  • 七十九、IntentService的特点?
  • 八十、如何将Activity设置为窗口样式?

七十一、支付宝支付接入流程?

(一)前期准备工作

1. 商户注册与认证

  • 平台注册:访问支付宝开放平台使用企业支付宝账号登录注册
  • 创建应用:根据支付场景选择应用类型(如:APP支付、手机网站支付、PC网站支付等)
  • 完成签约:根据业务需求签约相应的支付产品
  • 获取关键信息
    • APPID:应用的唯一标识
    • PID:商户号
    • 账户体系:区分不同的收款账户(如:ISV服务商模式需特别注意)

2. 密钥配置与安全设置

  • 密钥类型选择

    • 推荐使用RSA2(SHA256WithRSA,更安全)
    • 兼容支持:RSA(部分老系统)
  • 密钥生成步骤

    # 使用支付宝工具或OpenSSL生成
    # 生成RSA私钥
    openssl genrsa -out app_private_key.pem 2048
    # 生成RSA公钥
    openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem
    
  • 平台配置

    • 上传应用公钥到支付宝开放平台
    • 获取支付宝公钥用于验签

(二)客户端集成(Android为例)

1. SDK集成方式

  • 官方SDK依赖(最新推荐):
    // 基础支付能力
    implementation 'com.alipay.sdk:alipaysdk-android:15.8.11@aar'
    
    // 或使用Maven
    dependencies {
        implementation('com.alipay.sdk:alipaysdk-android:15.8.11') {
            exclude(group: 'com.android.support')
        }
    }
    

2. 客户端支付流程

// 1. 从服务端获取订单信息(签名后的订单字符串)
val orderInfo = getOrderInfoFromServer()

// 2. 调用支付SDK
val alipay = PayTask(activity)
val resultMap = alipay.payV2(orderInfo, true)

// 3. 处理支付结果
val payResult = PayResult(resultMap)
val resultStatus = payResult.resultStatus
when (resultStatus) {
    "9000" -> {
        // 支付成功
        // 注意:需向服务端确认支付状态
    }
    "8000" -> {
        // 支付处理中,需等待异步通知
    }
    "4000" -> {
        // 支付失败
    }
    "5000" -> {
        // 重复请求
    }
    "6001" -> {
        // 用户取消
    }
    "6002" -> {
        // 网络连接出错
    }
    "6004" -> {
        // 支付结果未知,需查询
    }
    else -> {
        // 其他错误
    }
}

3. Android配置注意事项

<!-- AndroidManifest.xml 权限配置 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 11+ 需要添加外部存储兼容性 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    android:minSdkVersion="30"
    tools:ignore="ScopedStorage" />

(三)服务端开发核心流程

1. 创建订单与签名

// 1. 组装业务参数
Map<String, String> bizParams = new HashMap<>();
bizParams.put("out_trade_no", "商户订单号");
bizParams.put("total_amount", "订单总金额");
bizParams.put("subject", "订单标题");
bizParams.put("product_code", "QUICK_MSECURITY_PAY"); // APP支付固定值

// 2. 公共参数
Map<String, String> params = new HashMap<>();
params.put("app_id", APP_ID);
params.put("method", "alipay.trade.app.pay");
params.put("charset", "utf-8");
params.put("sign_type", "RSA2");
params.put("timestamp", "2023-01-01 12:00:00");
params.put("version", "1.0");
params.put("notify_url", "https://your-domain.com/notify"); // 异步通知地址
params.put("biz_content", JsonUtil.toJson(bizParams));

// 3. 生成签名
String sign = generateRSASign(params, PRIVATE_KEY);

// 4. 返回给客户端
params.put("sign", sign);
return URLEncoder.encode(buildQueryString(params), "UTF-8");

2. 异步通知处理(关键)

/**
 * 支付宝异步通知回调处理
 */
@PostMapping("/alipay/notify")
public String handleNotify(HttpServletRequest request) {
    try {
        // 1. 将异步通知中收到的所有参数都存放到map中
        Map<String, String> params = convertRequestParams(request);
        
        // 2. 验证签名
        boolean signVerified = AlipaySignature.rsaCheckV1(
            params, 
            ALIPAY_PUBLIC_KEY, 
            "UTF-8", 
            "RSA2"
        );
        
        if (!signVerified) {
            return "failure";
        }
        
        // 3. 验证通知中的商户信息
        String appId = params.get("app_id");
        String sellerId = params.get("seller_id");
        if (!APP_ID.equals(appId) || !SELLER_ID.equals(sellerId)) {
            return "failure";
        }
        
        // 4. 验证交易状态
        String tradeStatus = params.get("trade_status");
        if ("TRADE_SUCCESS".equals(tradeStatus) || 
            "TRADE_FINISHED".equals(tradeStatus)) {
            // 支付成功逻辑
            
            // 5. 验证金额等信息
            String totalAmount = params.get("total_amount");
            String outTradeNo = params.get("out_trade_no");
            String tradeNo = params.get("trade_no"); // 支付宝交易号
            
            // 6. 处理业务逻辑(注意幂等性)
            boolean processed = processOrder(outTradeNo, tradeNo, totalAmount);
            
            if (processed) {
                return "success"; // 必须返回success,否则支付宝会重试
            }
        }
        
        return "failure";
    } catch (Exception e) {
        log.error("支付宝回调处理异常", e);
        return "failure";
    }
}

3. 订单查询与关单

// 订单查询(用于补单或状态确认)
public TradeQueryResponse queryOrder(String outTradeNo) {
    // 调用支付宝查询接口
    // 重要:对于支付结果未知的情况,需主动查询
}

// 关闭订单(用户长时间未支付)
public TradeCloseResponse closeOrder(String outTradeNo) {
    // 调用支付宝关单接口
    // 注意:已支付的订单不能关闭
}

(四)安全防护措施

1. 签名验证必须项

  • 验证时机:异步通知、同步返回、订单查询响应
  • 验证内容
    • 支付宝公钥的正确性
    • 签名算法的一致性(RSA2)
    • 商户信息的匹配(app_id、seller_id)

2. 防重复处理

// 使用数据库事务+唯一索引防重
@Transactional
public boolean processOrder(String outTradeNo, String tradeNo, String amount) {
    // 检查订单是否已处理过
    Order order = orderDao.findByOutTradeNo(outTradeNo);
    if (order != null && order.isPaid()) {
        return true; // 幂等返回
    }
    
    // 处理订单
    orderDao.updateOrderStatus(outTradeNo, OrderStatus.PAID);
    paymentDao.insertPayment(tradeNo, outTradeNo, amount);
    
    return true;
}

3. 金额验证

// 验证支付金额与订单金额一致
BigDecimal notifyAmount = new BigDecimal(params.get("total_amount"));
BigDecimal orderAmount = order.getAmount();

if (notifyAmount.compareTo(orderAmount) != 0) {
    log.warn("金额不一致,订单金额:{},通知金额:{}", 
        orderAmount, notifyAmount);
    return "failure";
}

(五)监控与运维

1. 关键监控指标

  • 支付成功率:统计支付成功/失败比例
  • 异步通知成功率:监控支付宝回调的成功率
  • 平均支付时长:从发起支付到完成的时间
  • 失败原因分析:分类统计各类失败原因

2. 异常处理机制

// 支付异常重试机制
@Retryable(value = {NetworkException.class}, maxAttempts = 3)
public PayResult retryPay(String orderInfo) {
    // 支付失败后的重试逻辑
}

// 异步通知补单机制
@Scheduled(fixedDelay = 300000) // 每5分钟执行
public void repairOrders() {
    // 查询支付中但未收到通知的订单
    List<Order> pendingOrders = orderDao.findPendingOrders();
    
    for (Order order : pendingOrders) {
        // 主动查询支付宝订单状态
        TradeQueryResponse response = queryOrder(order.getOutTradeNo());
        if ("TRADE_SUCCESS".equals(response.getTradeStatus())) {
            // 触发本地订单处理
            processOrderSuccess(order);
        }
    }
}

(六)最新规范与注意事项

1. 最新SDK规范

  • 最小化权限:Android 11+注意存储权限适配

  • 64位支持:确保SDK支持64位架构

  • 隐私合规:遵循《个人信息保护法》,明示用户信息收集

  • 混淆配置

    # 支付宝SDK混淆规则
    -keep class com.alipay.android.phone.mrpc.core.** { *; }
    -keep class com.alipay.apmobilesecuritysdk.** { *; }
    -keep class com.alipay.mobile.framework.** { *; }
    

2. 业务安全要点

  • 禁止客户端生成订单:所有订单必须在服务端生成
  • 禁止客户端保存密钥:私钥必须存储在服务端
  • 支付回调验证:必须验证回调的签名和商户信息
  • 金额校验:必须校验回调金额与订单金额的一致性
  • 防重放攻击:检查通知的notify_id是否已处理过

(七)常见问题与解决方案

1. 支付结果处理

问题场景 解决方案
客户端返回成功但服务端未收到通知 1. 启动定时任务查询订单状态
2. 引导用户点击"已完成支付"手动同步
异步通知多次接收 1. 实现幂等处理
2. 使用notify_id去重
网络异常导致支付中断 1. 提供重新支付入口
2. 保存支付中间状态

2. 兼容性考虑

  • 多渠道支付:考虑与微信支付等渠道的兼容设计
  • 国际化:海外版需接入支付宝国际版(Alipay+)
  • 多场景适配:H5支付、SDK支付、小程序支付的不同处理

(八)总结要点

  1. 核心流程:商户注册→密钥配置→服务端下单→客户端调起支付→异步通知处理
  2. 安全核心:私钥服务端保存、双重签名验证、金额校验、防重处理
  3. 最新规范:使用RSA2签名、适配Android新权限系统、隐私合规
  4. 容错机制:支付状态查询、异步通知补单、异常重试
  5. 监控运维:支付成功率监控、失败原因分析、定期对账

重要提醒:实际开发中请务必参考支付宝官方文档,支付接口和规范可能更新,本文基于当前最新实践整理。

七十二、如何实现线程安全的单例?

(一)基本概念与要求

单例模式(Singleton)确保一个类只有一个实例,并提供全局访问点。线程安全的单例需满足:

  • 唯一性:无论多少线程访问,都返回同一个实例
  • 延迟加载:实例在首次使用时才创建
  • 线程安全:多线程环境下不创建多个实例
  • 序列化/反序列化安全:序列化后反序列化不破坏单例
  • 反射安全:防止通过反射创建新实例

(二)Java中线程安全单例的实现方案

1. 双重检查锁定(Double-Checked Locking, DCL)✅

public class Singleton {
    // volatile确保指令不重排序,防止读到未初始化的对象
    private static volatile Singleton instance;
    
    private Singleton() {
        // 防止反射攻击
        if (instance != null) {
            throw new IllegalStateException("Already initialized");
        }
    }
    
    public static Singleton getInstance() {
        // 第一次检查:避免不必要的同步
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次检查:确保只有一个线程能创建实例
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
    
    // 防止序列化破坏单例
    protected Object readResolve() {
        return getInstance();
    }
}

适用场景:Java 5+ 环境,要求高性能的延迟加载场景

2. 静态内部类(Holder)✅ ✅(推荐)

public class Singleton {
    private Singleton() {
        // 防止反射攻击
        if (Holder.INSTANCE != null) {
            throw new IllegalStateException("Already initialized");
        }
    }
    
    // 静态内部类在第一次使用时才加载
    private static class Holder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return Holder.INSTANCE;
    }
    
    protected Object readResolve() {
        return getInstance();
    }
}

优点

  • 延迟加载(调用getInstance()时才加载Holder类)
  • 线程安全(JVM保证类加载过程的线程安全)
  • 无需同步,性能好

3. 枚举(Enum)✅ ✅ ✅(最佳实践)

public enum Singleton {
    INSTANCE;
    
    // 可以添加任意方法
    public void doSomething() {
        System.out.println("Singleton operation");
    }
    
    // Java 16+ 支持枚举中的静态成员初始化
    private final String data = loadData();
    
    private String loadData() {
        return "Initialized data";
    }
}

优点

  • 绝对线程安全(JVM保证枚举实例的唯一性)
  • 天然防止序列化/反序列化破坏
  • 天然防止反射攻击(JDK禁止通过反射创建枚举)
  • 代码最简洁
  • 支持枚举特性(如values()valueOf()

4. 饿汉式(Eager Initialization)

public class Singleton {
    // 类加载时就初始化,浪费内存
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

缺点:即使不使用也会创建实例,浪费内存

5. 同步方法(Synchronized Method)

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    // 每次获取实例都要同步,性能差
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

缺点:性能低下,不推荐使用

(三)Kotlin中线程安全单例的实现

1. object关键字(最简单)✅ ✅ ✅

object Singleton {
    init {
        println("Singleton initialized")
    }
    
    fun doSomething() {
        println("Doing something")
    }
    
    // 支持属性
    val data: String by lazy {
        "Lazy initialized data"
    }
}

// 使用
Singleton.doSomething()
println(Singleton.data)

底层实现:Kotlin的object编译为:

  • 一个私有构造函数
  • 一个静态的INSTANCE字段
  • 静态代码块中初始化,保证线程安全

2. 带参数的单例(使用伴生对象+懒加载)

class Singleton private constructor(private val config: String) {
    companion object {
        @Volatile
        private var instance: Singleton? = null
        
        fun getInstance(config: String = "default"): Singleton {
            return instance ?: synchronized(this) {
                instance ?: Singleton(config).also { instance = it }
            }
        }
    }
    
    fun showConfig() {
        println("Config: $config")
    }
}

// 使用
Singleton.getInstance("custom").showConfig()

3. 使用Kotlin的lazy委托✅ ✅

class Singleton private constructor() {
    companion object {
        // 默认线程安全(LazyThreadSafetyMode.SYNCHRONIZED)
        val instance: Singleton by lazy {
            Singleton()
        }
        
        // 自定义懒加载
        val unsafeInstance: Singleton by lazy(LazyThreadSafetyMode.NONE) {
            Singleton()
        }
        
        // 使用自定义的锁
        val customLockInstance: Singleton by lazy(LazyThreadSafetyMode.PUBLICATION) {
            Singleton()
        }
    }
}

LazyThreadSafetyMode选项

  • SYNCHRONIZED:线程安全(默认)
  • PUBLICATION:允许多个线程初始化,但只返回第一个结果
  • NONE:非线程安全,适合单线程环境

(四)特殊情况与高级用法

1. 防止反射攻击

public class ReflectionSafeSingleton {
    private static volatile ReflectionSafeSingleton instance;
    private static boolean initialized = false;
    
    private ReflectionSafeSingleton() {
        synchronized (ReflectionSafeSingleton.class) {
            if (initialized) {
                throw new IllegalStateException("Already initialized");
            }
            initialized = true;
        }
    }
    
    public static ReflectionSafeSingleton getInstance() {
        if (instance == null) {
            synchronized (ReflectionSafeSingleton.class) {
                if (instance == null) {
                    instance = new ReflectionSafeSingleton();
                }
            }
        }
        return instance;
    }
}

2. 防止序列化攻击

import java.io.Serializable;

public class SerializableSingleton implements Serializable {
    private static final long serialVersionUID = 1L;
    private static volatile SerializableSingleton instance;
    
    private SerializableSingleton() {}
    
    public static SerializableSingleton getInstance() {
        if (instance == null) {
            synchronized (SerializableSingleton.class) {
                if (instance == null) {
                    instance = new SerializableSingleton();
                }
            }
        }
        return instance;
    }
    
    // 关键:防止序列化创建新实例
    protected Object readResolve() {
        return getInstance();
    }
}

3. 使用Java 16+的Records(实验性)

// Java 16+,Records天生不可变,但不适合传统单例
public record SingletonRecord(String data) {
    private static final SingletonRecord INSTANCE = new SingletonRecord("default");
    
    public static SingletonRecord getInstance() {
        return INSTANCE;
    }
}

(五)性能比较与选择建议

1. 性能对比

实现方式 线程安全 延迟加载 性能 代码复杂度 推荐度
枚举 ❌(类加载时初始化) ⭐⭐⭐⭐⭐ ✅✅✅
静态内部类 ⭐⭐⭐⭐ ⭐⭐ ✅✅
双重检查锁定 ✅(Java 5+) ⭐⭐⭐ ⭐⭐⭐
Kotlin object ⭐⭐⭐⭐⭐ ✅✅✅
饿汉式 ⭐⭐⭐⭐⭐ ⚠️
同步方法 ⭐⭐

2. 选择指南

  • Java项目
    • 首选枚举:无需延迟加载,需要绝对安全
    • 次选静态内部类:需要延迟加载,代码简洁
    • 特定场景用DCL:需要更精细控制初始化过程
  • Kotlin项目
    • 首选object:简单场景
    • 次选伴生对象+lazy:需要参数或复杂初始化
  • Android开发
    • 轻量级用object:简单工具类
    • 重量级用DCL或静态内部类:需要依赖Context等参数
    • 避免饿汉式:减少应用启动时间

(六)实际应用示例

1. Android中的单例(带Context)

class AppPrefs private constructor(context: Context) {
    companion object {
        @Volatile
        private var instance: AppPrefs? = null
        
        fun getInstance(context: Context): AppPrefs {
            return instance ?: synchronized(this) {
                instance ?: AppPrefs(context.applicationContext).also { instance = it }
            }
        }
    }
    
    private val sharedPrefs: SharedPreferences = 
        context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
    
    // 业务方法...
}

2. 依赖注入框架中的单例

// 使用Koin
val appModule = module {
    single { Repository(get()) }  // 单例
    factory { ViewModel(get()) }  // 每次新建
}

// 使用Hilt
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides
    @Singleton  // 注解标记单例
    fun provideRepository(): Repository {
        return RepositoryImpl()
    }
}

3. 线程池单例(最佳实践)

public class ThreadPoolManager {
    private static class Holder {
        private static final ThreadPoolExecutor INSTANCE = new ThreadPoolExecutor(
            4,  // 核心线程数
            10, // 最大线程数
            60L, TimeUnit.SECONDS, // 空闲线程存活时间
            new LinkedBlockingQueue<>(100), // 工作队列
            new ThreadFactoryBuilder().setNameFormat("worker-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );
    }
    
    private ThreadPoolManager() {}
    
    public static ThreadPoolExecutor getInstance() {
        return Holder.INSTANCE;
    }
}

(七)常见问题与陷阱

1. 序列化陷阱

// 错误:缺少readResolve方法
Singleton s1 = Singleton.getInstance();
// 序列化然后反序列化
Singleton s2 = deserialize(serialize(s1));
System.out.println(s1 == s2); // false!破坏了单例

2. 类加载器陷阱

// 不同类加载器加载会导致多个实例
ClassLoader cl1 = new URLClassLoader(urls);
ClassLoader cl2 = new URLClassLoader(urls);
Class<?> c1 = cl1.loadClass("Singleton");
Class<?> c2 = cl2.loadClass("Singleton");
// c1和c2的实例不同

3. 克隆陷阱

public class CloneableSingleton implements Cloneable {
    // ... 单例实现 ...
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 必须重写clone方法,防止克隆
        throw new CloneNotSupportedException("Singleton cannot be cloned");
    }
}

(八)面试回答要点

  1. 理解各种实现的原理:知道每种方式如何保证线程安全
  2. 掌握优缺点:能对比不同方案的性能、安全性、复杂度
  3. 结合实际场景:能根据具体需求选择合适方案
  4. 了解最新发展:知道Kotlin、Java新特性对单例的影响
  5. 注意陷阱:反射、序列化、克隆等潜在问题

核心总结

  • Java中:枚举 > 静态内部类 > 双重检查锁定
  • Kotlin中:object关键字是最佳选择
  • 关键:根据实际需求(延迟加载、性能、安全性)选择合适方案

七十三、如何保证Service不被杀死?

(一)Service被杀死的原因分析

1. 系统资源回收机制

  • 内存压力:系统内存不足时,Android系统会根据优先级回收后台进程
  • 进程优先级:Android系统为进程分配不同优先级,低优先级进程更易被回收
  • 省电策略:现代Android系统(尤其是国产ROM)有严格的省电策略

2. 用户主动行为

  • 任务管理器清理:用户主动清理后台应用
  • 应用信息页强制停止:用户在设置中强制停止应用
  • 系统优化工具:第三方清理工具或系统自带的内存清理

3. 厂商定制限制

  • 国产ROM限制:华为、小米、OPPO、vivo等厂商对后台服务有严格限制
  • 电池优化:各厂商的电池优化策略会限制后台服务
  • 自启动管理:需要用户手动允许应用自启动

(二)提高Service存活率的有效方法

1. 前台服务(Foreground Service)

  • 核心原理:通过显示通知提升进程优先级

  • 实现代码

    // Android 8.0+ 需要创建通知渠道
    private fun createNotificationChannel() {
    	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    	    val channel = NotificationChannel(
    	        CHANNEL_ID,
    	        "服务通知",
    	        NotificationManager.IMPORTANCE_LOW
    	    )
    	    val manager = getSystemService(NotificationManager::class.java)
    	    manager.createNotificationChannel(channel)
    	}
    }
    
    // 启动前台服务
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    	val notification = NotificationCompat.Builder(this, CHANNEL_ID)
    	    .setContentTitle("服务运行中")
    	    .setContentText("后台服务正在运行")
    	    .setSmallIcon(R.drawable.ic_notification)
    	    .setPriority(NotificationCompat.PRIORITY_LOW)
    	    .build()
    
    	// API 34+ 需要指定前台服务类型
    	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    	    startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_SPECIAL_USE)
    	} else {
    	    startForeground(NOTIFICATION_ID, notification)
    	}
    
    	return START_STICKY
    }
    
  • 注意事项

    • Android 8.0+ 必须创建通知渠道
    • Android 12+ 需要声明android:foregroundServiceType属性
    • Android 14+ 需要请求前台服务权限

2. 进程保活策略

(1)双进程守护(效果有限,不推荐)
// 在独立的进程中运行Service
<service
    android:name=".GuardService"
    android:process=":guard" />

// 互相监听,一个进程被杀时重启另一个
class GuardService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 监听主进程
        watchMainProcess()
        return START_STICKY
    }
    
    private fun watchMainProcess() {
        // 定期检查主进程状态
        // 注意:此方法在Android 8.0+上效果有限
    }
}
(2)利用系统机制复活
// 返回START_STICKY,系统会尝试重启Service
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    // 执行任务...
    return START_STICKY // 或 START_REDELIVER_INTENT
}

// 在onDestroy()中尝试重启
override fun onDestroy() {
    super.onDestroy()
    // 延迟重启,避免立即重启被系统检测到
    Handler(Looper.getMainLooper()).postDelayed({
        startService(Intent(this, MyService::class.java))
    }, 2000)
}

3. 白名单与权限引导

(1)引导用户关闭电池优化
// 检查是否在电池优化白名单中
fun checkBatteryOptimization(context: Context): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
        powerManager.isIgnoringBatteryOptimizations(context.packageName)
    } else {
        true
    }
}

// 引导用户关闭电池优化
fun requestIgnoreBatteryOptimizations(context: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
            data = Uri.parse("package:${context.packageName}")
        }
        context.startActivity(intent)
    }
}
(2)引导用户开启自启动权限(国产ROM)
// 检测并引导用户开启自启动权限
fun checkAutoStartPermission(context: Context) {
    val brand = Build.BRAND.lowercase()
    
    when {
        brand.contains("xiaomi") || brand.contains("redmi") -> {
            // 小米自启动设置
            try {
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                intent.data = Uri.parse("package:${context.packageName}")
                context.startActivity(intent)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        brand.contains("huawei") || brand.contains("honor") -> {
            // 华为自启动管理
            try {
                val intent = Intent().apply {
                    component = ComponentName(
                        "com.huawei.systemmanager",
                        "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity"
                    )
                }
                context.startActivity(intent)
            } catch (e: Exception) {
                // 备选方案
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                intent.data = Uri.parse("package:${context.packageName}")
                context.startActivity(intent)
            }
        }
        // 其他厂商...
    }
}

4. 使用WorkManager替代(推荐)

// WorkManager更适合后台任务调度
class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
    override fun doWork(): Result {
        // 执行后台任务
        performBackgroundTask()
        return Result.success()
    }
}

// 安排定期任务
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresBatteryNotLow(true)
    .build()

val workRequest = PeriodicWorkRequestBuilder<MyWorker>(
    15, TimeUnit.MINUTES  // 最小间隔15分钟
).setConstraints(constraints)
    .addTag("my_background_work")
    .build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "my_work",
    ExistingPeriodicWorkPolicy.KEEP,
    workRequest
)

5. 使用JobScheduler(Android 5.0+)

class MyJobService : JobService() {
    override fun onStartJob(params: JobParameters?): Boolean {
        // 执行任务
        performTask()
        
        // 返回true表示任务还在执行,完成后需要调用jobFinished
        // 返回false表示任务已经完成
        return true
    }
    
    override fun onStopJob(params: JobParameters?): Boolean {
        // 返回true表示需要重新调度
        return true
    }
}

// 调度任务
fun scheduleJob(context: Context) {
    val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    
    val jobInfo = JobInfo.Builder(JOB_ID, ComponentName(context, MyJobService::class.java))
        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
        .setPersisted(true)  // 重启后继续
        .setPeriodic(15 * 60 * 1000)  // 15分钟
        .setBackoffCriteria(3000, JobInfo.BACKOFF_POLICY_LINEAR)
        .build()
    
    jobScheduler.schedule(jobInfo)
}

(三)Android版本适配策略

1. Android 8.0(API 26)适配

// 后台执行限制:应用进入后台后,有几分钟时间窗口可以使用后台服务
// 必须使用前台服务才能长时间运行
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    // 使用startForegroundService启动,然后在5秒内调用startForeground
    val intent = Intent(this, MyService::class.java)
    startForegroundService(intent)
}

2. Android 9.0(API 28)适配

<!-- 需要添加前台服务权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

3. Android 10.0(API 29)适配

// 后台位置权限限制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    // 需要ACCESS_BACKGROUND_LOCATION权限才能在后台获取位置
}

4. Android 12.0(API 31)适配

<!-- 需要声明前台服务类型 -->
<service
    android:name=".MyService"
    android:foregroundServiceType="location|microphone|camera|...">
</service>

5. Android 14.0(API 34)适配

<!-- 需要声明前台服务类型 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

(四)最佳实践与建议

1. 合理设计后台任务

  • 使用WorkManager:处理可延迟的后台任务
  • 区分任务类型
    • 即时任务:使用前台服务
    • 延迟任务:使用WorkManager或AlarmManager
    • 定时任务:使用JobScheduler或WorkManager
  • 减少后台运行时间:完成任务后及时停止服务

2. 省电优化策略

// 使用省电模式检测
fun isBatterySaverMode(context: Context): Boolean {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
        powerManager.isPowerSaveMode
    } else {
        false
    }
}

// 在省电模式下减少后台任务
if (isBatterySaverMode(context)) {
    // 降低任务频率或暂停非必要任务
}

3. 用户体验考虑

  • 前台服务必须显示通知:让用户知道服务在运行
  • 提供关闭选项:允许用户停止后台服务
  • 说明后台运行原因:在隐私政策或应用介绍中说明
  • 遵守Google Play政策:避免后台服务滥用导致应用下架

4. 监控与调试

// 监控服务状态
class MyService : Service() {
    override fun onTaskRemoved(rootIntent: Intent?) {
        // 从最近任务中移除时的回调
        Log.d("MyService", "Task removed")
    }
    
    override fun onLowMemory() {
        // 内存不足时的回调
        super.onLowMemory()
        Log.w("MyService", "Low memory warning")
    }
    
    override fun onTrimMemory(level: Int) {
        // 内存调整时的回调
        super.onTrimMemory(level)
        Log.d("MyService", "Trim memory level: $level")
    }
}

(五)不同场景的解决方案

1. 音乐播放器(需要持续运行)

class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 创建媒体播放通知
        val notification = createMediaNotification()
        startForeground(NOTIFICATION_ID, notification)
        
        // 使用MediaSession与系统集成
        mediaSession = MediaSession(this, "MusicService")
        mediaSession.isActive = true
        
        return START_STICKY
    }
}

2. 位置跟踪应用

class LocationService : Service() {
    override fun onCreate() {
        super.onCreate()
        
        // 请求位置权限
        if (checkLocationPermission()) {
            // 使用FusedLocationProviderClient获取位置
            locationClient = LocationServices.getFusedLocationProviderClient(this)
            
            val locationRequest = LocationRequest.create().apply {
                interval = 10000  // 10秒
                fastestInterval = 5000
                priority = LocationRequest.PRIORITY_HIGH_ACCURACY
            }
            
            locationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper())
        }
    }
}

3. 即时通讯应用

// 使用WebSocket或长连接保持在线
// 结合前台服务和WorkManager
class ChatService : Service() {
    private val socket: WebSocket? = null
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 建立WebSocket连接
        connectWebSocket()
        
        // 前台服务通知
        startForeground(NOTIFICATION_ID, createChatNotification())
        
        return START_STICKY
    }
    
    private fun connectWebSocket() {
        // WebSocket连接逻辑
        // 断开时尝试重连
    }
}

(六)总结与建议

1. 核心原则

  • 最小化原则:只在必要时运行后台服务
  • 透明原则:让用户知道服务在运行
  • 合规原则:遵守各Android版本限制和商店政策

2. 技术选择

场景 推荐方案 说明
持续运行服务 前台服务 + 通知 Android 8.0+必需,用户可见
定时任务 WorkManager 兼容性好,系统统一调度
即时任务 前台服务 优先级高,不易被杀死
国产ROM适配 白名单引导 + 厂商适配 需要特殊处理

3. 避免的做法

  • ❌ 频繁重启服务(容易被系统检测并限制)
  • ❌ 隐藏通知(违反Android政策)
  • ❌ 滥用系统漏洞(可能导致应用被下架)
  • ❌ 过度保活(影响用户体验和设备性能)

4. 未来趋势

  • 后台限制越来越严格:Android系统持续加强对后台服务的限制
  • 用户控制权增强:用户更容易控制和管理后台应用
  • 更智能的调度:系统会根据使用习惯智能调度后台任务

最终建议:在设计和实现后台服务时,始终以用户体验和系统合规为前提,合理使用各种技术手段,避免过度追求"保活"而导致应用被限制或下架。

七十四、ContentProvider、ContentResolver、ContentObserver的关系?

(一)核心概念定义

1. ContentProvider:数据提供者

  • 数据封装层:封装数据访问接口,统一数据操作方式
  • 跨进程/跨应用:支持应用间安全的数据共享
  • 抽象数据源:可以封装SQLite、文件、网络等各种数据源
  • 标准CRUD接口
    interface ContentProvider {
        fun query(uri: Uri, ...): Cursor?
        fun insert(uri: Uri, values: ContentValues?): Uri?
        fun update(uri: Uri, values: ContentValues?, ...): Int
        fun delete(uri: Uri, ...): Int
        fun getType(uri: Uri): String?
    }
    

2. ContentResolver:数据调用者

  • 统一访问接口:应用通过ContentResolver与所有ContentProvider交互

  • 进程间通信代理:实际通过Binder机制调用ContentProvider

  • 权限控制:通过URI权限机制实现安全访问

  • 使用方式

    val resolver: ContentResolver = context.contentResolver
    
    // 查询数据
    val cursor = resolver.query(uri, projection, selection, selectionArgs, sortOrder)
    
    // 插入数据
    val newUri = resolver.insert(uri, values)
    
    // 更新数据
    val rowsUpdated = resolver.update(uri, values, selection, selectionArgs)
    
    // 删除数据
    val rowsDeleted = resolver.delete(uri, selection, selectionArgs)
    

3. ContentObserver:数据观察者

  • 观察者模式实现:监听ContentProvider数据变化
  • 异步通知:数据变更时通过Handler异步通知
  • URI粒度控制:可以监听特定URI或URI前缀的所有变化
  • 生命周期感知:需要及时注册和注销

(二)工作原理与交互流程

1. 核心交互流程

graph TD
    A[应用程序A] -->|ContentResolver| B[ContentProvider]
    C[应用程序B] -->|ContentResolver| B
    B -->|notifyChange| D[ContentObserver A]
    B -->|notifyChange| E[ContentObserver B]
    
    subgraph "数据变更通知"
        F[数据变更] --> B
        B --> G[调用notifyChange]
        G --> D
        G --> E
    end

2. 详细工作流程

// 1. ContentProvider实现数据操作和通知
class MyContentProvider : ContentProvider() {
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // 插入数据到数据库
        val id = database.insert(TABLE_NAME, null, values)
        val newUri = ContentUris.withAppendedId(uri, id)
        
        // 关键:通知所有观察者数据已变更
        context?.contentResolver?.notifyChange(newUri, null)
        
        return newUri
    }
}

// 2. 其他应用通过ContentResolver访问
class OtherApp {
    fun queryData(context: Context) {
        val uri = Uri.parse("content://com.example.provider/data")
        val cursor = context.contentResolver.query(
            uri, 
            arrayOf("id", "name"), 
            null, null, null
        )
    }
}

// 3. 应用注册ContentObserver监听变化
class MyActivity : AppCompatActivity() {
    private val observer = object : ContentObserver(Handler(Looper.getMainLooper())) {
        override fun onChange(selfChange: Boolean) {
            // 数据变化,更新UI
            refreshData()
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 注册观察者
        contentResolver.registerContentObserver(
            Uri.parse("content://com.example.provider/data"),
            true,  // 监听所有子URI
            observer
        )
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // 注销观察者
        contentResolver.unregisterContentObserver(observer)
    }
}

(三)现代Android中的演进与替代方案

1. ContentProvider的现代使用场景

// 主要用于系统组件或跨应用数据共享
class MyContentProvider : ContentProvider() {
    // Room + ContentProvider结合
    private val database: AppDatabase by lazy {
        Room.databaseBuilder(
            context!!.applicationContext,
            AppDatabase::class.java,
            "app.db"
        ).build()
    }
    
    override fun query(uri: Uri, ...): Cursor? {
        return when (matcher.match(uri)) {
            DATA_ITEMS -> {
                // 使用Room的RawQuery
                database.query("SELECT * FROM items", null)
            }
            else -> throw IllegalArgumentException("Unknown URI")
        }
    }
}

2. Jetpack替代方案(单应用内推荐)

(1)使用Room + LiveData/Flow(推荐)
@Dao
interface ItemDao {
    @Query("SELECT * FROM items")
    fun getAllItems(): Flow<List<Item>>  // 使用Flow自动观察
    
    @Insert
    suspend fun insert(item: Item)
}

// ViewModel中观察
class ItemViewModel(private val itemDao: ItemDao) : ViewModel() {
    val items: Flow<List<Item>> = itemDao.getAllItems()
    
    fun addItem(item: Item) {
        viewModelScope.launch {
            itemDao.insert(item)
            // Room自动通知Flow更新
        }
    }
}
(2)使用WorkManager进行后台数据同步
// 替代ContentProvider + SyncAdapter
class DataSyncWorker(context: Context, params: WorkerParameters) 
    : CoroutineWorker(context, params) {
    
    override suspend fun doWork(): Result {
        // 同步数据
        syncDataFromServer()
        
        // 通知UI更新
        withContext(Dispatchers.Main) {
            // 使用ViewModel更新
        }
        
        return Result.success()
    }
}

(四)权限与安全机制

1. URI权限机制

<!-- AndroidManifest.xml中的权限声明 -->
<provider
    android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:exported="true"
    android:readPermission="com.example.READ_DATA"
    android:writePermission="com.example.WRITE_DATA"
    android:grantUriPermissions="true">
    
    <!-- 路径权限控制 -->
    <path-permission
        android:path="/public"
        android:readPermission="com.example.READ_PUBLIC" />
</provider>

2. Android 11+的包可见性适配

<!-- Android 11+需要声明要访问的ContentProvider -->
<queries>
    <!-- 明确声明要查询的Provider -->
    <provider android:authorities="com.example.provider" />
    
    <!-- 或使用intent-filter -->
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="content" />
    </intent>
</queries>

(五)实际应用示例

1. 自定义ContentProvider完整实现

class MyContentProvider : ContentProvider() {
    private lateinit var databaseHelper: DatabaseHelper
    private lateinit var uriMatcher: UriMatcher
    
    companion object {
        const val AUTHORITY = "com.example.app.provider"
        private const val PATH_ITEMS = "items"
        private const val PATH_ITEMS_ID = "items/#"
        
        val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY/$PATH_ITEMS")
        
        private const val CODE_ITEMS = 1
        private const val CODE_ITEMS_ID = 2
    }
    
    override fun onCreate(): Boolean {
        databaseHelper = DatabaseHelper(context!!)
        
        uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
            addURI(AUTHORITY, PATH_ITEMS, CODE_ITEMS)
            addURI(AUTHORITY, PATH_ITEMS_ID, CODE_ITEMS_ID)
        }
        
        return true
    }
    
    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        return when (uriMatcher.match(uri)) {
            CODE_ITEMS -> {
                // 查询所有
                databaseHelper.readableDatabase.query(
                    "items", projection, selection, selectionArgs,
                    null, null, sortOrder
                )
            }
            CODE_ITEMS_ID -> {
                // 查询单条
                val id = ContentUris.parseId(uri)
                databaseHelper.readableDatabase.query(
                    "items", projection,
                    "_id = ?", arrayOf(id.toString()),
                    null, null, sortOrder
                )
            }
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
    }
    
    override fun getType(uri: Uri): String? {
        return when (uriMatcher.match(uri)) {
            CODE_ITEMS -> "vnd.android.cursor.dir/vnd.com.example.item"
            CODE_ITEMS_ID -> "vnd.android.cursor.item/vnd.com.example.item"
            else -> null
        }
    }
    
    // 其他CRUD方法类似实现...
}

2. 跨应用数据共享示例

// 应用A:提供联系人数据
class ContactsProvider : ContentProvider() {
    // 提供联系人数据访问
}

// 应用B:访问联系人并监听变化
class ContactSyncService : Service() {
    private val contactObserver = object : ContentObserver(Handler()) {
        override fun onChange(selfChange: Boolean, uri: Uri?) {
            // 联系人变化,同步到服务器
            syncContactsToServer()
        }
    }
    
    override fun onCreate() {
        super.onCreate()
        
        // 监听系统联系人变化
        contentResolver.registerContentObserver(
            ContactsContract.Contacts.CONTENT_URI,
            true,
            contactObserver
        )
    }
}

(六)性能优化与最佳实践

1. 批量操作优化

// 使用ContentProviderOperation批量操作
val operations = ArrayList<ContentProviderOperation>()

operations.add(
    ContentProviderOperation.newInsert(uri)
        .withValue("name", "Item 1")
        .build()
)

operations.add(
    ContentProviderOperation.newInsert(uri)
        .withValue("name", "Item 2")
        .build()
)

try {
    val results = contentResolver.applyBatch(AUTHORITY, operations)
    // 批量操作成功
} catch (e: Exception) {
    // 处理异常
}

2. 异步查询与线程管理

// 使用CursorLoader(已弃用)或现代替代方案
class MyFragment : Fragment() {
    // 使用ViewModel + LiveData + Room
    private val viewModel: ItemViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 观察数据变化
        viewModel.items.observe(viewLifecycleOwner) { items ->
            // 更新UI
            adapter.submitList(items)
        }
    }
}

3. 内存管理

// 正确处理Cursor
fun queryData(): List<Item> {
    val cursor = contentResolver.query(uri, ...)
    
    return try {
        cursor?.use { // 自动关闭
            val items = mutableListOf<Item>()
            while (it.moveToNext()) {
                items.add(Item.fromCursor(it))
            }
            items
        } ?: emptyList()
    } catch (e: Exception) {
        emptyList()
    }
}

(七)常见问题与解决方案

1. 权限问题

问题 解决方案
权限被拒绝 检查Manifest声明,运行时请求权限
URI权限不足 使用Intent.FLAG_GRANT_READ_URI_PERMISSION
Android 11包可见性 在Manifest中添加<queries>声明

2. 性能问题

  • 大数据集分页:使用limitoffset参数
  • 频繁更新优化:批量操作,减少通知频率
  • 索引优化:数据库表添加合适索引

3. 生命周期问题

// 正确处理ContentObserver生命周期
class MyActivity : AppCompatActivity() {
    private var observerRegistered = false
    
    override fun onResume() {
        super.onResume()
        if (!observerRegistered) {
            contentResolver.registerContentObserver(uri, true, observer)
            observerRegistered = true
        }
    }
    
    override fun onPause() {
        super.onPause()
        if (observerRegistered) {
            contentResolver.unregisterContentObserver(observer)
            observerRegistered = false
        }
    }
}

(八)总结与对比

1. 三者关系总结

  • ContentProvider:数据源抽象层,定义数据访问接口
  • ContentResolver:客户端代理,统一访问所有Provider
  • ContentObserver:监听机制,实现数据变化通知

2. 现代Android开发建议

场景 推荐方案 说明
单应用数据访问 Room + LiveData/Flow 类型安全,生命周期感知
跨应用数据共享 ContentProvider 系统标准方案
后台数据同步 WorkManager + Room 替代 SyncAdapter
系统数据访问 ContentResolver + ContentObserver 访问联系人、短信等

3. 核心要点记忆

  1. ContentProvider是基础:提供标准数据接口,支持跨进程
  2. ContentResolver是桥梁:所有数据访问都通过它
  3. ContentObserver是监听器:实现数据驱动的UI更新
  4. 权限控制是关键:通过URI权限机制保证安全
  5. 生命周期管理重要:及时注册和注销观察者

演进方向:在新应用中,优先考虑使用Room + LiveData/Flow进行应用内数据管理,仅在需要跨应用共享数据时才使用ContentProvider。ContentObserver逐渐被LiveData、Flow等响应式编程模型替代,但在访问系统数据时仍有其价值。

七十五、如何导入外部数据库?

(一)数据库导入的基本流程

1. 准备数据库文件

  • 文件格式:SQLite数据库文件(.db.sqlite扩展名)
  • 存放位置
    • assets/目录:适合较大的数据库文件
    • res/raw/目录:适合较小的数据库文件(自动压缩)
  • 数据库要求
    • 必须包含名为android_metadata的表,其中locale字段默认值为'en_US'
    • 表结构和数据需要预先准备好

2. 核心实现步骤

/**
 * 数据库导入管理类
 */
class DatabaseImporter(private val context: Context) {
    
    companion object {
        private const val DATABASE_NAME = "my_database.db"
        private const val DATABASE_VERSION = 1
        
        // 数据库存放路径
        fun getDatabasePath(context: Context): String {
            return context.getDatabasePath(DATABASE_NAME).absolutePath
        }
    }
    
    /**
     * 导入数据库的主要流程
     */
    fun importDatabaseIfNeeded(): Boolean {
        return try {
            // 1. 检查数据库是否存在
            if (!isDatabaseExists()) {
                // 2. 创建目录
                createDatabaseDirectory()
                // 3. 从assets复制数据库
                copyDatabaseFromAssets()
                // 4. 验证数据库完整性
                validateDatabase()
                true
            } else {
                // 5. 检查数据库版本,决定是否需要更新
                checkAndUpdateDatabase()
            }
        } catch (e: Exception) {
            Log.e("DatabaseImporter", "导入数据库失败", e)
            false
        }
    }
}

(二)详细实现代码

1. 从assets复制数据库(完整示例)

class DatabaseHelper(context: Context) : SQLiteOpenHelper(
    context, DATABASE_NAME, null, DATABASE_VERSION
) {
    
    private val context: Context = context.applicationContext
    
    companion object {
        private const val DATABASE_NAME = "preloaded_data.db"
        private const val DATABASE_VERSION = 1
        private const val BUFFER_SIZE = 8192
        
        // 用于避免重复复制的标记文件
        private const val COPIED_MARKER = ".db_copied"
    }
    
    override fun onCreate(db: SQLiteDatabase) {
        // 空实现,因为数据库已预创建
    }
    
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 处理数据库版本升级
        handleDatabaseUpgrade(oldVersion, newVersion)
    }
    
    override fun getWritableDatabase(): SQLiteDatabase {
        // 确保数据库已正确复制
        ensureDatabaseCopied()
        return super.getWritableDatabase()
    }
    
    override fun getReadableDatabase(): SQLiteDatabase {
        // 确保数据库已正确复制
        ensureDatabaseCopied()
        return super.getReadableDatabase()
    }
    
    /**
     * 确保数据库已从assets复制到应用目录
     */
    private fun ensureDatabaseCopied() {
        val dbFile = context.getDatabasePath(DATABASE_NAME)
        val markerFile = File(dbFile.parent, COPIED_MARKER)
        
        synchronized(this) {
            // 检查是否已经复制过
            if (markerFile.exists() && dbFile.exists()) {
                // 验证数据库是否完整
                if (isDatabaseValid(dbFile)) {
                    return
                } else {
                    // 数据库损坏,重新复制
                    markerFile.delete()
                    dbFile.delete()
                }
            }
            
            // 复制数据库
            copyDatabase(dbFile, markerFile)
        }
    }
    
    /**
     * 复制数据库文件
     */
    private fun copyDatabase(dbFile: File, markerFile: File) {
        var input: InputStream? = null
        var output: FileOutputStream? = null
        
        try {
            // 确保父目录存在
            dbFile.parentFile?.mkdirs()
            
            // 打开assets中的数据库文件
            input = context.assets.open("databases/$DATABASE_NAME")
            output = FileOutputStream(dbFile)
            
            // 使用缓冲区提高复制效率
            val buffer = ByteArray(BUFFER_SIZE)
            var length: Int
            
            while (input.read(buffer).also { length = it } > 0) {
                output.write(buffer, 0, length)
            }
            
            output.flush()
            
            // 创建标记文件
            markerFile.createNewFile()
            
            Log.i("DatabaseHelper", "数据库复制完成: ${dbFile.absolutePath}")
            
        } catch (e: IOException) {
            // 删除可能已损坏的文件
            if (dbFile.exists()) dbFile.delete()
            if (markerFile.exists()) markerFile.delete()
            throw RuntimeException("复制数据库失败", e)
            
        } finally {
            try {
                input?.close()
                output?.close()
            } catch (e: IOException) {
                Log.w("DatabaseHelper", "关闭流失败", e)
            }
        }
    }
    
    /**
     * 验证数据库完整性
     */
    private fun isDatabaseValid(dbFile: File): Boolean {
        return try {
            // 尝试打开数据库验证
            SQLiteDatabase.openDatabase(
                dbFile.absolutePath,
                null,
                SQLiteDatabase.OPEN_READONLY
            ).use { db ->
                // 检查必要的表是否存在
                val cursor = db.rawQuery(
                    "SELECT name FROM sqlite_master WHERE type='table' AND name='android_metadata'",
                    null
                )
                val tableExists = cursor?.use { it.count > 0 } ?: false
                cursor?.close()
                tableExists
            }
        } catch (e: Exception) {
            Log.w("DatabaseHelper", "数据库验证失败", e)
            false
        }
    }
    
    /**
     * 处理数据库升级
     */
    private fun handleDatabaseUpgrade(oldVersion: Int, newVersion: Int) {
        // 根据版本号执行相应的迁移逻辑
        Log.i("DatabaseHelper", "数据库从版本 $oldVersion 升级到 $newVersion")
        
        // 这里可以实现复杂的升级逻辑
        // 例如:备份旧数据 -> 复制新数据库 -> 恢复数据
    }
}

2. 从raw资源复制数据库

class RawDatabaseHelper(context: Context) : SQLiteOpenHelper(
    context, DATABASE_NAME, null, DATABASE_VERSION
) {
    
    private val context: Context = context.applicationContext
    
    companion object {
        private const val DATABASE_NAME = "preloaded.db"
        private const val DATABASE_VERSION = 1
        private const val RAW_RESOURCE_ID = R.raw.preloaded_database
    }
    
    override fun onCreate(db: SQLiteDatabase) {
        // 从raw资源导入初始数据
        importInitialData(db)
    }
    
    private fun importInitialData(db: SQLiteDatabase) {
        try {
            // 读取raw资源中的SQL文件
            val inputStream = context.resources.openRawResource(RAW_RESOURCE_ID)
            val sqlStatements = inputStream.bufferedReader().use { it.readText() }
            
            // 执行SQL语句
            db.beginTransaction()
            try {
                // 分割并执行SQL语句
                sqlStatements.split(";").forEach { statement ->
                    val trimmed = statement.trim()
                    if (trimmed.isNotEmpty()) {
                        db.execSQL(trimmed)
                    }
                }
                db.setTransactionSuccessful()
            } finally {
                db.endTransaction()
            }
            
        } catch (e: Exception) {
            Log.e("RawDatabaseHelper", "导入初始数据失败", e)
        }
    }
    
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 升级逻辑
    }
}

(三)现代Android最佳实践

1. 使用Room Persistence Library

// 1. 定义Entity
@Entity(tableName = "items")
data class Item(
    @PrimaryKey val id: Int,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "description") val description: String?
)

// 2. 定义Dao
@Dao
interface ItemDao {
    @Query("SELECT * FROM items")
    fun getAll(): Flow<List<Item>>
    
    @Insert
    suspend fun insert(item: Item)
}

// 3. 使用预打包数据库的Room Database
@Database(entities = [Item::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun itemDao(): ItemDao
    
    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null
        
        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = createDatabase(context)
                INSTANCE = instance
                instance
            }
        }
        
        private fun createDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(
                context.applicationContext,
                AppDatabase::class.java,
                "my_database.db"
            ).createFromAsset("databases/preloaded.db") // 从assets导入预打包数据库
              .fallbackToDestructiveMigration() // 降级时清空数据
              .build()
        }
    }
}

2. 使用AssetSQLiteOpenHelper(第三方库简化)

// build.gradle
dependencies {
    implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
}
import com.readystatesoftware.sqliteasset.SQLiteAssetHelper

class MyAssetHelper(context: Context) : SQLiteAssetHelper(
    context, 
    DATABASE_NAME, 
    null, 
    DATABASE_VERSION
) {
    companion object {
        private const val DATABASE_NAME = "my_database.db"
        private const val DATABASE_VERSION = 2 // 更新版本以触发重新复制
    }
    
    // 库会自动处理数据库复制和版本更新
}

(四)数据库版本管理策略

1. 版本检测与升级策略

class VersionAwareDatabaseHelper(context: Context) : SQLiteOpenHelper(
    context, DATABASE_NAME, null, DATABASE_VERSION
) {
    
    companion object {
        private const val DATABASE_NAME = "app.db"
        private const val DATABASE_VERSION = 3
        private const val PREF_VERSION_KEY = "database_version"
    }
    
    private val sharedPrefs = context.getSharedPreferences("database", Context.MODE_PRIVATE)
    
    override fun onCreate(db: SQLiteDatabase) {
        // 首次安装时执行
        copyPreloadedDatabase()
        updateVersionInPrefs()
    }
    
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 版本升级处理
        when (oldVersion) {
            1 -> upgradeFromV1ToV2(db)
            2 -> upgradeFromV2ToV3(db)
            else -> copyPreloadedDatabase() // 无法升级时重新复制
        }
        updateVersionInPrefs()
    }
    
    override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // 处理降级(通常重新复制数据库)
        copyPreloadedDatabase()
        updateVersionInPrefs()
    }
    
    private fun copyPreloadedDatabase() {
        // 复制逻辑...
    }
    
    private fun upgradeFromV1ToV2(db: SQLiteDatabase) {
        // 执行从版本1到版本2的迁移
        db.execSQL("ALTER TABLE items ADD COLUMN created_at INTEGER DEFAULT 0")
    }
    
    private fun upgradeFromV2ToV3(db: SQLiteDatabase) {
        // 执行从版本2到版本3的迁移
        db.execSQL("CREATE INDEX idx_created_at ON items(created_at)")
    }
    
    private fun updateVersionInPrefs() {
        sharedPrefs.edit().putInt(PREF_VERSION_KEY, DATABASE_VERSION).apply()
    }
}

2. 增量更新策略

class IncrementalUpdateHelper(context: Context) {
    
    fun checkAndApplyUpdates(currentVersion: Int, targetVersion: Int) {
        // 应用增量SQL更新
        val updates = getUpdateScripts(currentVersion, targetVersion)
        
        updates.forEach { update ->
            applyUpdateScript(update)
        }
    }
    
    private fun getUpdateScripts(fromVersion: Int, toVersion: Int): List<String> {
        // 根据版本范围返回需要执行的SQL脚本
        val scripts = mutableListOf<String>()
        
        for (version in fromVersion + 1..toVersion) {
            val scriptName = "update_v${version - 1}_to_v$version.sql"
            scripts.add(scriptName)
        }
        
        return scripts
    }
    
    private fun applyUpdateScript(scriptName: String) {
        // 从assets读取并执行SQL脚本
        try {
            context.assets.open("database_updates/$scriptName").use { input ->
                val sql = input.bufferedReader().use { it.readText() }
                executeSQL(sql)
            }
        } catch (e: IOException) {
            Log.w("UpdateHelper", "更新脚本不存在: $scriptName")
        }
    }
}

(五)性能优化与注意事项

1. 内存与性能优化

// 1. 使用连接池
object DatabaseManager {
    private val helper: DatabaseHelper by lazy {
        DatabaseHelper(MyApplication.context)
    }
    
    private val connectionPool = Executors.newFixedThreadPool(4)
    
    fun queryAsync(query: String, callback: (Cursor?) -> Unit) {
        connectionPool.submit {
            val db = helper.readableDatabase
            val cursor = db.rawQuery(query, null)
            
            // 切换到主线程回调
            Handler(Looper.getMainLooper()).post {
                callback(cursor)
            }
        }
    }
}

// 2. 使用事务批量插入
fun importLargeDataset(items: List<Item>) {
    val db = helper.writableDatabase
    db.beginTransaction()
    try {
        items.forEach { item ->
            val values = ContentValues().apply {
                put("id", item.id)
                put("name", item.name)
                put("description", item.description)
            }
            db.insert("items", null, values)
        }
        db.setTransactionSuccessful()
    } finally {
        db.endTransaction()
    }
}

2. 安全注意事项

class SecureDatabaseHelper(context: Context) : SQLiteOpenHelper(
    context, DATABASE_NAME, null, DATABASE_VERSION
) {
    
    companion object {
        private const val DATABASE_NAME = "secure_data.db"
        private const val DATABASE_VERSION = 1
    }
    
    init {
        // 启用Write-Ahead Logging提高并发性能
        val db = writableDatabase
        db.enableWriteAheadLogging()
        
        // 设置更严格的数据库配置
        db.execSQL("PRAGMA journal_mode = WAL")
        db.execSQL("PRAGMA synchronous = NORMAL")
        
        // 如果是敏感数据,可以考虑加密
        // 使用SQLCipher或Room的加密支持
    }
    
    // 防止SQL注入
    fun searchItemsSafe(keyword: String): List<Item> {
        return readableDatabase.rawQuery(
            "SELECT * FROM items WHERE name LIKE ?",
            arrayOf("%$keyword%")
        ).use { cursor ->
            // 处理结果
            mutableListOf<Item>().apply {
                while (cursor.moveToNext()) {
                    add(Item.fromCursor(cursor))
                }
            }
        }
    }
}

(六)常见问题与解决方案

1. 数据库文件过大处理

object LargeDatabaseManager {
    
    fun importLargeDatabaseInBackground(context: Context) {
        WorkManager.getInstance(context).enqueueUniqueWork(
            "database_import",
            ExistingWorkPolicy.KEEP,
            OneTimeWorkRequestBuilder<DatabaseImportWorker>()
                .setConstraints(
                    Constraints.Builder()
                        .setRequiredNetworkType(NetworkType.UNMETERED)
                        .setRequiresCharging(true)
                        .build()
                )
                .setBackoffCriteria(
                    BackoffPolicy.LINEAR,
                    10,
                    TimeUnit.SECONDS
                )
                .build()
        )
    }
    
    class DatabaseImportWorker(context: Context, params: WorkerParameters) 
        : CoroutineWorker(context, params) {
        
        override suspend fun doWork(): Result {
            return try {
                // 分块下载和解压大数据库文件
                downloadAndImportDatabase()
                Result.success()
            } catch (e: Exception) {
                if (runAttemptCount < 3) {
                    Result.retry()
                } else {
                    Result.failure()
                }
            }
        }
    }
}

2. 多线程访问问题

// 使用单例确保数据库Helper唯一实例
object ThreadSafeDatabase {
    
    @Volatile
    private var instance: SQLiteOpenHelper? = null
    
    fun getInstance(context: Context): SQLiteOpenHelper {
        return instance ?: synchronized(this) {
            instance ?: createHelper(context).also { instance = it }
        }
    }
    
    private fun createHelper(context: Context): SQLiteOpenHelper {
        return object : SQLiteOpenHelper(
            context.applicationContext,
            DATABASE_NAME,
            null,
            DATABASE_VERSION
        ) {
            override fun onCreate(db: SQLiteDatabase) {
                // 使用ReentrantLock处理并发
                val lock = ReentrantLock()
                lock.lock()
                try {
                    if (!isDatabaseCopied()) {
                        copyDatabaseFromAssets()
                    }
                } finally {
                    lock.unlock()
                }
            }
            
            override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
                // 版本升级处理
            }
        }
    }
}

(七)测试与验证

1. 单元测试数据库导入

@RunWith(AndroidJUnit4::class)
class DatabaseImportTest {
    
    @get:Rule
    val temporaryFolder = TemporaryFolder()
    
    private lateinit var context: Context
    
    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
    }
    
    @Test
    fun testDatabaseCopyFromAssets() {
        // 测试数据库复制功能
        val helper = DatabaseHelper(context)
        val db = helper.readableDatabase
        
        // 验证数据库已正确打开
        assertThat(db.isOpen).isTrue()
        
        // 验证必要表存在
        val cursor = db.rawQuery(
            "SELECT name FROM sqlite_master WHERE type='table'",
            null
        )
        assertThat(cursor.count).isGreaterThan(0)
        cursor.close()
        
        db.close()
    }
    
    @Test
    fun testDatabaseVersionUpgrade() {
        // 测试版本升级逻辑
        val oldDb = File(context.getDatabasePath("test.db").parent, "test_v1.db")
        val newDb = File(context.getDatabasePath("test.db").parent, "test_v2.db")
        
        // 创建旧版本数据库
        createOldVersionDatabase(oldDb)
        
        // 测试升级逻辑
        val success = upgradeDatabase(oldDb, newDb)
        assertThat(success).isTrue()
    }
}

(八)总结与最佳实践

1. 实现方案对比

方法 优点 缺点 适用场景
传统SQLiteOpenHelper 控制灵活,兼容性好 代码复杂,需手动处理版本 需要精细控制的老项目
Room + createFromAsset 代码简洁,类型安全 需要迁移到Room架构 新项目,现代架构
SQLiteAssetHelper 简单易用,自动处理版本 第三方依赖,停止维护 快速原型,简单应用

2. 核心要点总结

  1. 文件准备:确保数据库文件包含android_metadata
  2. 复制时机:应用首次启动或数据库更新时复制
  3. 版本管理:使用SharedPreferences或数据库内部版本表跟踪版本
  4. 性能优化:大文件分块处理,使用事务批量操作
  5. 错误处理:网络异常、文件损坏等情况的重试机制
  6. 安全考虑:防止SQL注入,敏感数据加密

3. 现代Android开发建议

  • 优先使用Room:提供更好的类型安全和编译时检查
  • 响应式编程:结合Flow或LiveData实现数据观察
  • 后台处理:使用WorkManager处理大文件导入
  • 测试覆盖:确保数据库迁移和导入逻辑的可靠性

最终建议:对于新项目,强烈推荐使用Room的createFromAsset()方法导入预打包数据库,这是目前最简单、最安全的方案。对于现有项目,可以根据具体情况选择最合适的迁移策略。

七十六、LinearLayout、RelativeLayout、FrameLayout性能对比?

(一)布局测量的基本原理

在对比性能前,需要了解Android布局渲染的核心流程:

  1. 测量(Measure):计算View的大小
  2. 布局(Layout):确定View的位置
  3. 绘制(Draw):将View绘制到屏幕上

性能关键:测量阶段的复杂度直接影响布局性能,测量次数越多、嵌套越深,性能消耗越大。

(二)各布局性能详细分析

1. RelativeLayout(相对布局)

(1)测量机制
// RelativeLayout的测量特点
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 第一轮测量:测量所有子View
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    
    // 根据相对关系调整后,进行第二轮测量
    measureChildren(widthMeasureSpec, heightMeasureSpec);
    
    // 特殊情况可能需要更多轮测量
}

核心问题:由于子View之间存在复杂的相对依赖关系(如layout_toRightOflayout_alignParentTop等),通常需要两次测量才能确定最终尺寸。

(2)性能痛点
  • 双重测量:默认情况下至少测量两次
  • 依赖解析:需要解析和处理所有相对关系约束
  • 嵌套灾难:多层RelativeLayout嵌套时,测量次数呈指数级增长
(3)使用场景
<!-- 应避免的嵌套写法 -->
<RelativeLayout>
    <RelativeLayout>
        <RelativeLayout>
            <!-- 深度嵌套,性能极差 -->
        </RelativeLayout>
    </RelativeLayout>
</RelativeLayout>

<!-- 相对布局适合简单相对定位 -->
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <Button
        android:id="@+id/btn_left"
        android:layout_alignParentStart="true" />
    
    <Button
        android:id="@+id/btn_right"
        android:layout_alignParentEnd="true" />
</RelativeLayout>

2. LinearLayout(线性布局)

(1)测量机制
// LinearLayout的测量优化
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mWeightSum > 0 || hasWeightedChild()) {
        // 包含weight属性时需要特殊处理(性能较差)
        measureHorizontalWithWeight();
    } else {
        // 无weight时单次遍历测量(性能较好)
        measureHorizontal();
    }
}
(2)性能分析

无weight属性时

  • 单次测量遍历即可确定所有子View位置
  • 性能优于RelativeLayout

使用weight属性时

  • 需要额外计算权重分配
  • 至少需要两次测量(类似RelativeLayout)
  • 性能显著下降
(3)实际性能对比示例
<!-- 性能好:无weight属性 -->
<LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <TextView android:layout_width="match_parent" />
    <Button android:layout_width="match_parent" />
</LinearLayout>

<!-- 性能差:使用weight属性 -->
<LinearLayout
    android:orientation="horizontal"
    android:layout_width="match_parent">
    
    <TextView
        android:layout_width="0dp"
        android:layout_weight="1" /> <!-- 需要计算权重 -->
    
    <Button
        android:layout_width="0dp"
        android:layout_weight="1" /> <!-- 需要计算权重 -->
</LinearLayout>

3. FrameLayout(帧布局)

(1)测量机制
// FrameLayout测量简单直接
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 只需考虑最大的子View尺寸
    int maxWidth = 0;
    int maxHeight = 0;
    
    for (View child : children) {
        measureChild(child);
        maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
        maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
    }
    
    setMeasuredDimension(maxWidth, maxHeight);
}
(2)性能优势
  • 单次测量:只需考虑最大的子View尺寸
  • 无复杂计算:没有相对关系或权重计算
  • 内存占用少:状态简单,占用资源少
(3)适用场景
<!-- 叠加布局或占位容器 -->
<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <!-- 背景层 -->
    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    
    <!-- 内容层 -->
    <LinearLayout
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
        
        <TextView android:text="居中内容" />
    </LinearLayout>
    
    <!-- 悬浮按钮 -->
    <Button
        android:layout_gravity="end|bottom"
        android:layout_margin="16dp" />
</FrameLayout>

(三)现代布局方案:ConstraintLayout

1. 性能优势

<!-- ConstraintLayout示例 -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <Button
        android:id="@+id/button1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
    <Button
        android:id="@+id/button2"
        app:layout_constraintStart_toEndOf="@id/button1"
        app:layout_constraintTop_toTopOf="@id/button1"
        app:layout_constraintEnd_toEndOf="parent" />
    
    <TextView
        app:layout_constraintTop_toBottomOf="@id/button1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

性能特点

  • 扁平化结构:减少嵌套层级
  • 单次测量:类似RelativeLayout但更高效
  • 灵活约束:支持复杂的响应式布局

2. 与传统布局对比

<!-- 传统多层嵌套 -->
<LinearLayout orientation="vertical">
    <RelativeLayout>
        <LinearLayout orientation="horizontal">
            <!-- 3层嵌套 -->
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

<!-- ConstraintLayout扁平化 -->
<ConstraintLayout>
    <!-- 所有View在同一层级 -->
</ConstraintLayout>

(四)性能对比总结

1. 测量复杂度对比表

布局类型 平均测量次数 嵌套性能损耗 适用场景 现代推荐度
FrameLayout 1次 叠加视图、简单容器 ⭐⭐⭐⭐
LinearLayout 1次(无weight)
2+次(有weight)
线性排列、列表项 ⭐⭐⭐
RelativeLayout 2+次 简单相对定位 ⭐⭐
ConstraintLayout 1-2次 复杂布局、扁平化 ⭐⭐⭐⭐⭐

2. 实测性能数据(参考)

布局层级测试结果(100次测量平均):
1. FrameLayout嵌套3层:12ms
2. LinearLayout嵌套3层(无weight):18ms
3. RelativeLayout嵌套3层:35ms
4. ConstraintLayout(扁平化):15ms

(五)性能优化最佳实践

1. 减少布局层级

<!-- 避免过度嵌套 -->
<!-- 错误示例:4层嵌套 -->
<LinearLayout>
    <RelativeLayout>
        <LinearLayout>
            <FrameLayout>
                <TextView />
            </FrameLayout>
        </LinearLayout>
    </RelativeLayout>
</LinearLayout>

<!-- 正确示例:使用merge标签 -->
<!-- parent_layout.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView android:id="@+id/text1" />
    <Button android:id="@+id/button1" />
</merge>

<!-- 使用include引入 -->
<LinearLayout>
    <include layout="@layout/parent_layout" />
</LinearLayout>

2. 使用ViewStub延迟加载

<!-- 延迟加载不立即显示的视图 -->
<ViewStub
    android:id="@+id/stub_advanced_settings"
    android:inflatedId="@+id/advanced_settings"
    android:layout="@layout/advanced_settings_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

// 代码中按需加载
findViewById<ViewStub>(R.id.stub_advanced_settings)?.inflate()

3. 复用布局组件

<!-- 使用include标签复用布局 -->
<include
    layout="@layout/common_header"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

<!-- common_header.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize">
    
    <!-- 公共头部内容 -->
</LinearLayout>

4. 工具检测与优化

(1)使用Layout Inspector
  • Android Studio内置工具
  • 可视化查看布局层级
  • 识别不必要的嵌套
(2)GPU过度绘制检测
设置 -> 开发者选项 -> 调试GPU过度绘制
颜色说明:
- 无颜色:无过度绘制(最优)
- 蓝色:1次过度绘制
- 绿色:2次过度绘制
- 粉色:3次过度绘制
- 红色:4次以上过度绘制(需要优化)
(3)使用Lint检查
android {
    lintOptions {
        // 检查布局性能问题
        check 'LayoutPerformance'
        // 检查未使用的命名空间
        check 'UnusedResources'
    }
}

(六)实际开发选型建议

1. 不同场景的布局选择

场景 推荐布局 理由 示例
简单列表项 LinearLayout 性能足够,代码简单 设置项列表
叠加视图 FrameLayout 天然适合叠加场景 地图标记+控件
复杂响应式 ConstraintLayout 扁平化,响应式好 详情页、表单
传统项目维护 原有布局 避免大规模重构 老项目局部优化
RecyclerView项 ConstraintLayout 测量高效,适配性好 复杂列表项

2. 代码示例对比

// 性能敏感的View创建
fun createOptimizedView(context: Context): View {
    // 使用ConstraintLayout替代多层嵌套
    return ConstraintLayout(context).apply {
        layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.WRAP_CONTENT
        )
        
        val textView = TextView(context).apply {
            id = View.generateViewId()
            // 设置约束
        }
        
        val button = Button(context).apply {
            id = View.generateViewId()
            // 设置约束
        }
        
        addView(textView)
        addView(button)
        
        // 设置约束关系
        val constraintSet = ConstraintSet()
        constraintSet.clone(this)
        constraintSet.connect(textView.id, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START)
        constraintSet.connect(button.id, ConstraintSet.START, textView.id, ConstraintSet.END)
        constraintSet.applyTo(this)
    }
}

(七)常见误区与纠正

1. 误区:RelativeLayout已完全过时

纠正:RelativeLayout在简单相对定位场景下仍可用,但应避免多层嵌套。

2. 误区:LinearLayout的weight总是性能差

纠正:weight属性确实影响性能,但在简单等分布局中,性能影响可接受。

3. 误区:ConstraintLayout总是最优

纠正:对于简单线性布局,LinearLayout代码更简洁;ConstraintLayout学习成本较高。

4. 误区:越少层级一定越好

纠正:过度追求扁平化可能导致约束过于复杂,需要在层级和约束复杂度间平衡。

(八)最新发展趋势

1. Jetpack Compose

// Compose声明式UI,无需关心布局测量细节
@Composable
fun MyScreen() {
    Column(
        modifier = Modifier.fillMaxSize()
    ) {
        Text("标题", style = MaterialTheme.typography.h5)
        Spacer(Modifier.height(16.dp))
        Button(onClick = { /* 处理点击 */ }) {
            Text("按钮")
        }
    }
}

优势

  • 完全声明式,无布局测量性能问题
  • 自动优化重组
  • 代码更简洁

2. 性能监控工具进化

  • Perfetto:替代Systrace,更强大的性能分析工具
  • Android Studio Profiler:实时监控布局渲染性能
  • Baseline Profiles:Android 9+的布局预编译优化

(九)面试回答要点总结

  1. 理解测量原理:解释为什么不同布局测量次数不同
  2. 掌握性能排序:FrameLayout > LinearLayout(无weight) > ConstraintLayout > RelativeLayout > LinearLayout(有weight)
  3. 知道优化手段:减少嵌套、使用ViewStub、merge标签等
  4. 了解现代方案:ConstraintLayout的优势和适用场景
  5. 平衡开发效率:根据实际场景选择,不盲目追求性能

核心回答框架

  1. 从布局测量原理入手
  2. 对比各布局的测量复杂度和性能特点
  3. 强调ConstraintLayout的现代优势
  4. 给出具体的优化建议
  5. 提及未来趋势(Compose)

通过这样的结构化回答,既能展示技术深度,又能体现实际工程经验。

七十七、什么是Scheme协议?

(一)Scheme协议基本概念

1. 定义与作用

Scheme协议(URI Scheme)是Android中的一种**深度链接(Deep Linking)**机制,允许通过自定义URL格式直接打开应用的特定页面或功能。

主要作用

  • 应用内页面跳转:统一应用内路由方案
  • 外部H5跳转应用:从网页跳转到原生App页面
  • 应用间跳转:不同App之间的页面跳转和数据传递
  • 广告推广:通过特定链接引导用户下载或打开应用
  • 场景化入口:创建特定场景的快速入口(如扫码、通知等)

2. URI Scheme格式

scheme://host:port/path?query=value#fragment
  • scheme:协议名称(必填),如:myappweixinalipay
  • host:主机地址,如:www.example.compage
  • port:端口号(通常省略)
  • path:路径,指定具体页面,如:/detail/user/profile
  • query:查询参数,传递数据,如:id=123&name=test
  • fragment:片段标识,通常用于页面内锚点

(二)Android中的配置与使用

1. AndroidManifest.xml配置

<activity android:name=".MainActivity">
    <intent-filter>
        <!-- 必须包含此action -->
        <action android:name="android.intent.action.VIEW" />
        
        <!-- 必须包含此category -->
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        
        <!-- 定义Scheme -->
        <data
            android:scheme="myapp"
            android:host="www.example.com"
            android:pathPrefix="/user"
            android:pathPattern="/.*" />
        
        <!-- 支持多个data配置 -->
        <data
            android:scheme="myapp"
            android:host="product"
            android:pathPrefix="/detail" />
    </intent-filter>
</activity>

2. 支持的data属性

<data
    android:scheme="string"        <!-- 协议,如:http、https、myapp -->
    android:host="string"          <!-- 主机,如:www.example.com -->
    android:port="string"          <!-- 端口,如:8080 -->
    android:path="string"          <!-- 完整路径,如:/user/profile -->
    android:pathPrefix="string"    <!-- 路径前缀,如:/user -->
    android:pathPattern="string"   <!-- 路径模式,支持通配符,如:/.* -->
    android:mimeType="string" />   <!-- MIME类型 -->

3. 接收和处理Scheme跳转

class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // 处理Scheme跳转
        handleIntent(intent)
    }
    
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        // Activity已存在时,onNewIntent会被调用
        setIntent(intent)
        handleIntent(intent)
    }
    
    private fun handleIntent(intent: Intent?) {
        val action = intent?.action
        val data = intent?.data
        
        if (Intent.ACTION_VIEW == action && data != null) {
            // 解析URI
            val scheme = data.scheme        // "myapp"
            val host = data.host            // "www.example.com"
            val path = data.path            // "/user/profile"
            val query = data.query          // "id=123&name=test"
            val fragment = data.fragment    // "section1"
            
            // 解析查询参数
            val id = data.getQueryParameter("id")      // "123"
            val name = data.getQueryParameter("name")  // "test"
            
            // 根据URI跳转到对应页面
            navigateToTargetPage(scheme, host, path, queryParams)
        }
    }
    
    private fun navigateToTargetPage(
        scheme: String?,
        host: String?,
        path: String?,
        queryParams: Map<String, String>
    ) {
        // 路由分发逻辑
        when {
            path?.startsWith("/user") == true -> {
                // 跳转到用户页面
                val userId = queryParams["id"] ?: ""
                startActivity(Intent(this, UserActivity::class.java).apply {
                    putExtra("user_id", userId)
                })
            }
            path?.startsWith("/product") == true -> {
                // 跳转到商品详情页
                val productId = queryParams["id"] ?: ""
                startActivity(Intent(this, ProductActivity::class.java).apply {
                    putExtra("product_id", productId)
                })
            }
            else -> {
                // 默认首页
            }
        }
    }
}

(三)Scheme调用方式

1. 从H5页面跳转

<!-- 网页中调用 -->
<a href="myapp://www.example.com/user/profile?id=123">打开App用户页面</a>
<a href="myapp://product/detail?id=456">打开商品详情</a>

<!-- 使用JavaScript检测是否安装App -->
<script>
function openApp() {
    // 尝试打开App
    window.location.href = 'myapp://open';
    
    // 如果未安装App,跳转到下载页
    setTimeout(function() {
        if (!document.hidden) {
            window.location.href = 'https://play.google.com/store/apps/details?id=com.example.app';
        }
    }, 2000);
}
</script>

2. 从其他App跳转

// 在其他App中调用
fun openMyAppFromOtherApp(context: Context) {
    val uri = Uri.parse("myapp://www.example.com/user/profile?id=123")
    val intent = Intent(Intent.ACTION_VIEW, uri)
    
    try {
        context.startActivity(intent)
    } catch (e: ActivityNotFoundException) {
        // 未安装App的处理
        Toast.makeText(context, "请先安装App", Toast.LENGTH_SHORT).show()
        // 跳转到应用商店
        val marketIntent = Intent(Intent.ACTION_VIEW).apply {
            data = Uri.parse("market://details?id=com.example.app")
        }
        context.startActivity(marketIntent)
    }
}

3. 从App内部跳转

// 应用内部跳转
fun navigateWithinApp() {
    // 方式1:使用Intent
    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = Uri.parse("myapp://internal/home")
    }
    startActivity(intent)
    
    // 方式2:使用统一的Router
    Router.navigate("myapp://product/detail?id=1001")
}

(四)安全防护措施

1. 验证Scheme来源

class SecureSchemeActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 验证Scheme来源
        if (!isSchemeFromTrustedSource(intent)) {
            finish()
            return
        }
        
        handleIntent(intent)
    }
    
    /**
     * 验证Scheme来源是否可信
     */
    private fun isSchemeFromTrustedSource(intent: Intent?): Boolean {
        val callingPackage = intent?.`package`
        val data = intent?.data
        
        // 1. 检查是否来自系统浏览器
        if (callingPackage == "com.android.browser" || 
            callingPackage == "com.android.chrome") {
            return true
        }
        
        // 2. 检查是否来自可信应用白名单
        val trustedPackages = listOf(
            "com.trusted.app1",
            "com.trusted.app2"
        )
        if (callingPackage != null && trustedPackages.contains(callingPackage)) {
            return true
        }
        
        // 3. 验证URI签名(高级安全方案)
        if (data != null && verifyUriSignature(data)) {
            return true
        }
        
        // 4. 检查referrer(H5跳转时)
        val referrer = intent?.getStringExtra(Intent.EXTRA_REFERRER)
        if (referrer != null && isTrustedReferrer(referrer)) {
            return true
        }
        
        return false
    }
    
    /**
     * 验证参数完整性,防止篡改
     */
    private fun verifyParameters(data: Uri): Boolean {
        val id = data.getQueryParameter("id") ?: return false
        val sign = data.getQueryParameter("sign") ?: return false
        
        // 重新计算签名并比对
        val expectedSign = calculateMD5("${id}${SECRET_KEY}")
        return sign == expectedSign
    }
}

2. 防止恶意调用

// 限制Scheme调用的频率
object SchemeCallLimiter {
    private val callRecords = mutableMapOf<String, Long>()
    private const val MIN_INTERVAL = 1000L // 1秒内不能重复调用
    
    fun canCallScheme(scheme: String): Boolean {
        val lastCallTime = callRecords[scheme] ?: 0
        val currentTime = System.currentTimeMillis()
        
        if (currentTime - lastCallTime < MIN_INTERVAL) {
            return false
        }
        
        callRecords[scheme] = currentTime
        return true
    }
}

// 使用时检查
if (!SchemeCallLimiter.canCallScheme("myapp")) {
    Log.w("Security", "Scheme调用过于频繁")
    return
}

3. Android 12+的安全适配

<!-- Android 12+ 需要显式声明与其他App的交互 -->
<queries>
    <!-- 声明要查询的包名 -->
    <package android:name="com.trusted.app" />
    
    <!-- 或声明要查询的intent-filter -->
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="myapp" />
    </intent>
</queries>

(五)高级功能与最佳实践

1. 支持App Links(Android 6.0+)

App Links是增强版Scheme,支持自动验证和应用关联,提升用户体验。

<!-- 配置Android App Links -->
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    
    <!-- 使用HTTPS协议 -->
    <data
        android:scheme="https"
        android:host="www.example.com"
        android:pathPrefix="/app" />
</intent-filter>

服务器配置(需要托管assetlinks.json):

// https://www.example.com/.well-known/assetlinks.json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.app",
    "sha256_cert_fingerprints": ["指纹信息"]
  }
}]

2. 统一路由框架

// 使用ARouter或其他路由框架
class SchemeRouter {
    
    companion object {
        private const val SCHEME = "myapp"
        private const val HOST = "www.example.com"
    }
    
    /**
     * 注册所有Scheme路由
     */
    fun registerRoutes() {
        // 使用ARouter注册
        ARouter.getInstance().addRouteGroup { group ->
            group["/user/profile"] = RouteMeta.build(
                path = "/user/profile",
                destination = UserActivity::class.java
            )
            
            group["/product/detail"] = RouteMeta.build(
                path = "/product/detail",
                destination = ProductActivity::class.java
            )
        }
    }
    
    /**
     * 处理Scheme跳转
     */
    fun handleScheme(context: Context, uri: Uri): Boolean {
        return when {
            uri.scheme == SCHEME && uri.host == HOST -> {
                val path = uri.path ?: ""
                ARouter.getInstance()
                    .build(path)
                    .with(uriToBundle(uri))
                    .navigation(context)
                true
            }
            else -> false
        }
    }
    
    private fun uriToBundle(uri: Uri): Bundle {
        return Bundle().apply {
            uri.queryParameterNames?.forEach { key ->
                putString(key, uri.getQueryParameter(key))
            }
        }
    }
}

3. 降级处理策略

class SchemeFallbackHandler {
    
    /**
     * Scheme跳转降级策略
     */
    fun handleSchemeWithFallback(
        context: Context,
        uri: Uri,
        fallbackUrl: String? = null
    ) {
        try {
            // 尝试Scheme跳转
            val intent = Intent(Intent.ACTION_VIEW, uri)
            context.startActivity(intent)
        } catch (e: ActivityNotFoundException) {
            // 未安装App,执行降级策略
            handleFallback(context, fallbackUrl, uri)
        }
    }
    
    private fun handleFallback(
        context: Context,
        fallbackUrl: String?,
        originalUri: Uri
    ) {
        when {
            // 1. 跳转到应用商店
            fallbackUrl == null -> {
                openAppStore(context)
            }
            // 2. 跳转到H5页面
            fallbackUrl.startsWith("http") -> {
                openWebPage(context, fallbackUrl)
            }
            // 3. 显示提示对话框
            else -> {
                showInstallDialog(context, originalUri)
            }
        }
    }
    
    private fun openAppStore(context: Context) {
        val marketIntent = Intent(Intent.ACTION_VIEW).apply {
            data = Uri.parse("market://details?id=${context.packageName}")
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        
        try {
            context.startActivity(marketIntent)
        } catch (e: ActivityNotFoundException) {
            // 应用商店不存在,跳转到网页版
            val webIntent = Intent(Intent.ACTION_VIEW).apply {
                data = Uri.parse("https://play.google.com/store/apps/details?id=${context.packageName}")
            }
            context.startActivity(webIntent)
        }
    }
}

(六)测试与调试

1. ADB命令测试

# 测试Scheme跳转
adb shell am start -a android.intent.action.VIEW -d "myapp://www.example.com/user/profile?id=123"

# 测试App Links
adb shell am start -a android.intent.action.VIEW -d "https://www.example.com/app/user/123"

# 查看可处理某个Scheme的应用
adb shell dumpsys package uri | grep "myapp"

2. 测试代码

class SchemeTest {
    
    @Test
    fun testSchemeParsing() {
        val uri = Uri.parse("myapp://www.example.com/user/profile?id=123&name=test")
        
        assertEquals("myapp", uri.scheme)
        assertEquals("www.example.com", uri.host)
        assertEquals("/user/profile", uri.path)
        assertEquals("123", uri.getQueryParameter("id"))
        assertEquals("test", uri.getQueryParameter("name"))
    }
    
    @Test
    fun testSchemeIntent() {
        val intent = Intent(Intent.ACTION_VIEW).apply {
            data = Uri.parse("myapp://open")
        }
        
        // 验证Intent是否可以处理
        val packageManager = InstrumentationRegistry.getInstrumentation().targetContext.packageManager
        val activities = packageManager.queryIntentActivities(intent, 0)
        
        assertTrue(activities.isNotEmpty())
    }
}

3. 调试工具

  • Android Studio Deep Links Assistant:可视化配置和测试
  • App Links Assistant:验证App Links配置
  • Charles/Fiddler:拦截和修改网络请求测试H5跳转

(七)常见问题与解决方案

1. 多App注册相同Scheme冲突

// 解决方案1:使用更特定的Scheme
// 使用公司域名作为host部分:mycompanyapp://

// 解决方案2:询问用户选择
val intent = Intent(Intent.ACTION_VIEW, uri)
val chooser = Intent.createChooser(intent, "选择打开方式")
startActivity(chooser)

2. Chrome Intent Scheme限制

<!-- Chrome禁止从非用户操作的跳转 -->
<!-- 解决方案:必须在用户操作后触发 -->
<button onclick="openApp()">打开App</button>

<script>
// 正确:用户点击后触发
function openApp() {
    window.location.href = 'myapp://open';
}

// 错误:自动跳转会被拦截
window.onload = function() {
    // Chrome会拦截此跳转
    window.location.href = 'myapp://open';
};
</script>

3. 国际化和本地化

// 支持多语言的Scheme参数
class LocalizedSchemeHandler {
    
    fun handleLocalizedScheme(uri: Uri) {
        val language = uri.getQueryParameter("lang") ?: getSystemLanguage()
        val country = uri.getQueryParameter("country") ?: getSystemCountry()
        
        // 根据语言显示不同内容
        updateUIForLocale(language, country)
    }
}

(八)现代替代方案与发展趋势

1. Android App Links(推荐)

  • 优势:自动验证,无选择对话框,提升用户体验
  • 要求:HTTPS域名,服务器配置验证文件
  • 场景:适用于拥有自己域名的成熟应用
// Google提供的智能深度链接服务
Firebase.dynamicLinks
    .getDynamicLink(intent)
    .addOnSuccessListener { pendingDynamicLinkData ->
        val deepLink = pendingDynamicLinkData?.link
        
        deepLink?.let { link ->
            // 处理深度链接
            handleDeepLink(link)
        }
    }

优势

  • 跨平台(iOS/Android/Web)
  • 智能降级(未安装App跳转网页版)
  • 数据分析(跟踪链接效果)
// 使用Navigation组件处理深度链接
val navController = findNavController(R.id.nav_host_fragment)

// 在导航图中定义深度链接
<navigation 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/homeFragment">
    
    <fragment
        android:id="@+id/userFragment"
        android:name="com.example.UserFragment">
        
        <deepLink
            android:id="@+id/deepLink"
            app:uri="myapp://www.example.com/user/{userId}" />
    </fragment>
</navigation>

(九)总结与最佳实践

1. 核心要点

  • Scheme是自定义URI协议,用于App深度链接
  • 配置在Manifest的intent-filter中,指定data元素
  • 通过Intent.ACTION_VIEW触发,可以携带参数
  • 必须考虑安全性:验证来源,防止恶意调用

2. 选型建议

方案 适用场景 优点 缺点
自定义Scheme 简单跳转,内部使用 简单灵活,无需服务器 有安全风险,可能冲突
Android App Links 拥有域名的成熟应用 用户体验好,自动验证 配置复杂,需要HTTPS
Firebase Dynamic Links 跨平台推广,需要分析 智能降级,数据跟踪 依赖Google服务
Navigation Deep Links 使用Navigation组件的应用 与导航集成,类型安全 仅限于Navigation

3. 安全最佳实践

  1. 验证来源:检查调用者包名或签名
  2. 参数签名:重要参数添加签名防止篡改
  3. 频率限制:防止恶意频繁调用
  4. 权限控制:敏感操作需要用户确认
  5. 日志记录:记录所有Scheme调用用于审计

4. 未来发展

  • 隐私保护加强:Android对跨应用跳转的限制越来越多
  • 生态整合:与Instant Apps、App Bundles等技术的整合
  • 标准化:行业逐渐形成统一的深度链接标准

最终建议:新项目推荐使用Android App Links(如果有自有域名)或Navigation Deep Links(如果使用Navigation组件),老项目逐步迁移,并始终把安全放在第一位。

七十八、HandlerThread的原理及优缺点?

(一)HandlerThread的核心原理

1. 内部实现机制

public class HandlerThread extends Thread {
    Looper mLooper;
    Handler mHandler;
    
    @Override
    public void run() {
        // 1. 准备当前线程的Looper
        Looper.prepare();
        
        // 2. 同步获取Looper对象(确保初始化完成)
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        
        // 3. 设置线程优先级
        Process.setThreadPriority(mPriority);
        
        // 4. 启动消息循环(无限循环处理消息)
        Looper.loop();
    }
    
    // 获取Looper(会阻塞直到Looper创建完成)
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
}

2. 工作原理流程图

graph TD
    A[创建HandlerThread] --> B[start启动线程]
    B --> C[run方法执行]
    C --> D[Looper.prepare 创建MessageQueue]
    D --> E[获取当前线程Looper]
    E --> F[notifyAll 通知等待线程]
    F --> G[Looper.loop 启动消息循环]
    G --> H[等待消息]
    H --> I[处理Message]
    I --> H

(二)HandlerThread的详细分析

1. 创建与使用示例

// 1. 创建HandlerThread
val handlerThread = HandlerThread("MyWorkerThread").apply {
    // 可以设置线程优先级
    priority = Process.THREAD_PRIORITY_BACKGROUND
    start()
}

// 2. 获取Looper并创建Handler
val handler = Handler(handlerThread.looper) { msg ->
    // 在工作线程中执行任务
    when (msg.what) {
        1 -> processTask1()
        2 -> processTask2()
    }
    true
}

// 3. 发送消息
handler.sendEmptyMessage(1)
handler.sendMessage(Message.obtain().apply {
    what = 2
    obj = "任务数据"
})

// 4. 发送Runnable
handler.post {
    // 在工作线程中执行
    performBackgroundTask()
}

// 5. 结束时清理
fun cleanup() {
    handler.removeCallbacksAndMessages(null)
    handlerThread.quitSafely()  // 推荐使用quitSafely
}

2. 消息处理流程

// HandlerThread内部的消息处理序列
1. Handler.sendMessage() → MessageQueue.enqueueMessage()
2. Looper.loop() 不断从MessageQueue取消息
3. Message.target.dispatchMessage() 分发消息
4. Handler.handleMessage() 处理消息
5. 处理完成后回收Message到消息池

(三)HandlerThread的优缺点分析

1. 优点总结

(1)简化线程间通信
// 传统方式:需要手动管理Looper
Thread {
    Looper.prepare()
    val looper = Looper.myLooper()
    val handler = Handler(looper)
    // 复杂的同步逻辑...
    Looper.loop()
}.start()

// HandlerThread方式:一行代码
val handlerThread = HandlerThread("worker").apply { start() }
val handler = Handler(handlerThread.looper)
(2)生命周期管理
// HandlerThread提供了标准的退出机制
handlerThread.quit()       // 立即退出,不处理剩余消息
handlerThread.quitSafely() // 安全退出,处理完已有消息
(3)线程安全的消息队列
  • 内置的消息队列保证任务按顺序执行
  • 避免多线程竞争导致的复杂同步问题

2. 缺点总结

(1)串行执行效率问题
// 所有任务都在同一个线程中串行执行
handler.post { Thread.sleep(3000) }  // 任务1:耗时3秒
handler.post { doQuickTask() }       // 任务2:必须等待3秒后才能执行

// 性能问题:长任务会阻塞后续所有任务
(2)内存泄漏风险
class MyService : Service() {
    private lateinit var handlerThread: HandlerThread
    private lateinit var handler: Handler
    
    override fun onCreate() {
        handlerThread = HandlerThread("ServiceThread")
        handlerThread.start()
        handler = Handler(handlerThread.looper)
    }
    
    override fun onDestroy() {
        // 容易忘记清理
        // handlerThread.quit() // 如果忘记调用会导致内存泄漏
    }
}
(3)异常处理困难
handlerThread.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
    // HandlerThread内部异常会导致线程终止
    // 但默认没有崩溃恢复机制
    Log.e("HandlerThread", "线程崩溃", e)
    // 线程终止后,所有后续消息都无法处理
}

(四)现代Android替代方案对比

1. ExecutorService(线程池)

// 创建线程池
val executor = Executors.newFixedThreadPool(4)  // 4个线程

// 执行任务
executor.execute {
    // 并行执行,不会互相阻塞
    performTask1()
}

executor.execute {
    performTask2()  // 可以立即执行,无需等待
}

// 定时任务
val scheduledExecutor = Executors.newScheduledThreadPool(2)
scheduledExecutor.schedule({
    performDelayedTask()
}, 1, TimeUnit.SECONDS)

// 关闭线程池
executor.shutdown()
线程池优势:
  • 并行处理:多个线程同时执行任务
  • 资源复用:避免频繁创建销毁线程
  • 任务队列:支持各种队列策略
  • 灵活控制:支持定时、周期性任务

2. Kotlin协程(Coroutines)

// 使用协程替代HandlerThread
viewModelScope.launch {
    // 在主线程启动
    val result = withContext(Dispatchers.IO) {
        // 在IO线程执行耗时操作
        performNetworkRequest()
    }
    // 自动切换回主线程更新UI
    updateUI(result)
}

// 多个任务并行执行
val deferred1 = async { fetchData1() }
val deferred2 = async { fetchData2() }
val results = awaitAll(deferred1, deferred2)

// 结构化并发,自动取消
class MyViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // 当ViewModel被清除时,所有协程自动取消
            val data = repository.loadData()
            _uiState.value = data
        }
    }
}
协程优势:
  • 简洁的异步代码:用同步方式写异步逻辑
  • 结构化并发:自动管理生命周期
  • 灵活的调度器Dispatchers.IO、Main、Default
  • 异常处理:完善的异常处理机制
  • 取消支持:自动传播取消操作

3. WorkManager(后台任务)

// 处理需要持久化的后台任务
val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresBatteryNotLow(true)
    .build()

val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
    .setConstraints(constraints)
    .setBackoffCriteria(
        BackoffPolicy.LINEAR,
        10,
        TimeUnit.SECONDS
    )
    .build()

WorkManager.getInstance(context).enqueue(workRequest)

// Worker实现
class MyWorker(context: Context, params: WorkerParameters) 
    : CoroutineWorker(context, params) {
    
    override suspend fun doWork(): Result {
        // 在后台线程执行
        val data = performTask()
        return Result.success()
    }
}

(五)性能对比测试

1. 基准测试结果

// 测试代码示例
@RunWith(AndroidJUnit4::class)
class PerformanceTest {
    
    @Test
    fun testPerformance() {
        // HandlerThread串行执行100个任务
        val handlerThreadTime = measureTimeMillis {
            val handlerThread = HandlerThread("test").apply { start() }
            val handler = Handler(handlerThread.looper)
            
            repeat(100) { i ->
                handler.post {
                    Thread.sleep(10) // 模拟10ms任务
                }
            }
            
            // 等待所有任务完成
            Thread.sleep(2000)
            handlerThread.quitSafely()
        }
        
        // 线程池并行执行100个任务
        val executorTime = measureTimeMillis {
            val executor = Executors.newFixedThreadPool(4)
            
            repeat(100) { i ->
                executor.execute {
                    Thread.sleep(10)
                }
            }
            
            executor.shutdown()
            executor.awaitTermination(2, TimeUnit.SECONDS)
        }
        
        println("HandlerThread: ${handlerThreadTime}ms")
        println("Executor: ${executorTime}ms")
        // 结果:Executor明显快于HandlerThread
    }
}

2. 内存使用对比

方案 内存占用 线程创建开销 适用场景
HandlerThread 单个线程 中等 串行任务,简单通信
线程池 多个线程 高(但可复用) 并行任务,CPU密集型
协程 轻量级 很低 I/O密集型,异步编程
WorkManager 系统管理 持久化后台任务

(六)实际应用场景建议

1. 仍可使用HandlerThread的场景

(1)串行数据库操作
// 数据库操作需要串行执行以避免并发问题
object DatabaseManager {
    private val dbThread = HandlerThread("DatabaseThread").apply {
        start()
    }
    
    private val dbHandler = Handler(dbThread.looper)
    
    fun queryAsync(query: String, callback: (Cursor?) -> Unit) {
        dbHandler.post {
            val cursor = database.query(...)
            // 切换到主线程回调
            mainHandler.post { callback(cursor) }
        }
    }
}
(2)简单的后台任务队列
// 需要按顺序执行的后台任务
class TaskQueue {
    private val taskThread = HandlerThread("TaskQueue").apply {
        start()
    }
    
    private val taskHandler = Handler(taskThread.looper)
    
    fun enqueueTask(task: Runnable) {
        taskHandler.post(task)
    }
    
    fun cleanup() {
        taskThread.quitSafely()
    }
}

2. 推荐使用现代方案的场景

(1)网络请求处理
// 使用协程 + Retrofit
interface ApiService {
    @GET("data")
    suspend fun getData(): Response<Data>
}

// ViewModel中调用
viewModelScope.launch {
    try {
        val data = withContext(Dispatchers.IO) {
            apiService.getData()
        }
        _uiState.value = UiState.Success(data)
    } catch (e: Exception) {
        _uiState.value = UiState.Error(e.message)
    }
}
(2)并行图片处理
// 使用线程池并行处理
val executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())

fun processImages(images: List<Bitmap>) {
    images.forEach { bitmap ->
        executor.execute {
            // 并行处理每张图片
            val processed = applyFilter(bitmap)
            
            // 切换到主线程更新
            runOnUiThread {
                imageView.setImageBitmap(processed)
            }
        }
    }
}

(七)迁移指南:从HandlerThread到现代方案

1. 迁移到协程的步骤

// 旧的HandlerThread代码
class OldService {
    private val handlerThread = HandlerThread("Worker")
    private lateinit var handler: Handler
    
    init {
        handlerThread.start()
        handler = Handler(handlerThread.looper)
    }
    
    fun doWork(callback: (Result) -> Unit) {
        handler.post {
            val result = performLongTask()
            mainHandler.post { callback(result) }
        }
    }
}

// 迁移为协程
class ModernService {
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    
    suspend fun doWork(): Result = withContext(scope.coroutineContext) {
        performLongTask() // 直接返回结果
    }
    
    fun cleanup() {
        scope.cancel()
    }
}

2. 迁移到ExecutorService

// HandlerThread → 线程池
class TaskProcessor {
    // 旧方案
    private val handlerThread = HandlerThread("Processor")
    private val handler: Handler
    
    // 新方案
    private val executor = Executors.newSingleThreadExecutor()
    
    fun processTask(task: Runnable) {
        // 旧:handler.post(task)
        // 新:
        executor.execute(task)
    }
    
    fun shutdown() {
        // 旧:handlerThread.quitSafely()
        // 新:
        executor.shutdown()
    }
}

(八)最佳实践总结

1. 选择建议

需求 推荐方案 理由
简单的串行任务 HandlerThread 代码简单,无需复杂配置
并行任务处理 ExecutorService 充分利用多核CPU
异步编程,避免回调地狱 Kotlin协程 代码简洁,可读性好
需要生命周期感知 ViewModel + 协程 自动取消,避免内存泄漏
持久化后台任务 WorkManager 系统调度,保证执行

2. 编码规范

// ✅ 正确的HandlerThread使用
val handlerThread = HandlerThread("Background").apply {
    start()
}
val handler = Handler(handlerThread.looper)

// 使用完毕及时清理
fun onDestroy() {
    handler.removeCallbacksAndMessages(null)
    handlerThread.quitSafely() // 优先使用quitSafely
}

// ❌ 避免的错误做法
// 1. 不要忘记启动线程
// val thread = HandlerThread("Test") // 缺少start()

// 2. 不要在主线程创建HandlerThread的Handler
// val handler = Handler() // 这会在主线程创建Handler

// 3. 不要忘记清理
// handlerThread.quit() // 如果不调用会导致线程泄漏

3. 调试与监控

// 监控HandlerThread状态
fun monitorHandlerThread(handlerThread: HandlerThread) {
    Log.d("ThreadMonitor", 
        "线程状态: ${handlerThread.state}, " +
        "存活: ${handlerThread.isAlive}, " +
        "中断: ${handlerThread.isInterrupted}")
    
    // 获取线程堆栈信息
    val stackTraces = Thread.getAllStackTraces()
    stackTraces[handlerThread]?.forEach { 
        Log.d("ThreadMonitor", "  $it") 
    }
}

(九)面试回答要点

1. 核心原理回答框架

"HandlerThread是Android中一个封装了Looper的Thread子类,它的主要原理是:

  1. 继承Thread,在run()方法中调用Looper.prepare()创建消息队列
  2. 调用Looper.loop()进入消息循环
  3. 对外提供getLooper()方法,供其他线程创建Handler与它通信"

2. 优缺点对比

“优点:简化了子线程中Handler的使用,提供了现成的消息循环机制。
缺点:串行执行效率低,一个耗时任务会阻塞整个队列,且需要手动管理生命周期。”

3. 现代方案介绍

"在现代Android开发中,更推荐使用:

  1. Kotlin协程:结构化并发,代码简洁,自动生命周期管理
  2. ExecutorService线程池:支持并行执行,资源利用率高
  3. WorkManager:系统级后台任务调度,保证任务执行"

4. 适用场景判断

“HandlerThread仍适用于简单的串行任务场景,如顺序执行的数据库操作。
但对于复杂并发场景,应优先考虑协程或线程池。”

最终总结:HandlerThread是一个特定历史时期的解决方案,在现代Android开发中已经不再是首选,但在理解Android消息机制和进行简单任务处理时仍有其价值。新项目建议使用协程等现代并发方案,老项目迁移时可逐步替换。

七十九、IntentService的特点?

(一)IntentService核心概述

1. 基本定义与设计目的

IntentService是Android框架中一个特殊的Service子类,专门用于处理异步请求。它通过工作线程顺序处理Intent请求,并在所有任务完成后自动停止。

2. 设计目标

  • 简化后台任务处理:自动创建工作线程,开发者无需手动管理线程
  • 避免并发问题:串行处理任务,天然线程安全
  • 自动资源管理:任务完成后自动停止服务,减少资源占用
  • 集成Intent机制:与Android的Intent系统无缝集成

(二)核心特点详解

1. 自动创建工作线程

// IntentService内部实现关键代码
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        
        @Override
        public void handleMessage(Message msg) {
            // 在工作线程中调用onHandleIntent
            onHandleIntent((Intent) msg.obj);
            // 自动停止服务(如果没有待处理消息)
            stopSelf(msg.arg1);
        }
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        // 创建HandlerThread作为工作线程
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
}

特点说明

  • 内部使用HandlerThread创建工作线程
  • 开发者只需实现onHandleIntent()方法,无需关心线程创建和管理
  • 自动处理线程的生命周期

2. 串行处理Intent

// IntentService的任务执行机制
@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    
    // 消息被顺序发送到Handler,实现串行执行
    mServiceHandler.sendMessage(msg);
}

// 串行执行示例
val intentService = MyIntentService()
intentService.onStart(intent1, 1)  // 任务1开始执行
intentService.onStart(intent2, 2)  // 任务2等待任务1完成
intentService.onStart(intent3, 3)  // 任务3等待任务2完成

串行处理优势

  • 线程安全:避免多线程并发访问共享资源的问题
  • 顺序执行:任务按照启动顺序依次执行
  • 简化逻辑:无需复杂的同步机制

3. 执行完毕自动停止

@Override
public void handleMessage(Message msg) {
    onHandleIntent((Intent) msg.obj);
    
    // 使用startId判断是否停止服务
    stopSelf(msg.arg1);
}

// stopSelf(startId)的智能停止逻辑
public final void stopSelf(int startId) {
    // 只有当最后启动的服务实例完成时才真正停止
    if (mActivityManager == null) return;
    
    try {
        mActivityManager.stopServiceToken(
            new ComponentName(this, mClassName), 
            mToken, startId
        );
    } catch (RemoteException e) {
        // 处理异常
    }
}

自动停止机制

  • stopSelf():立即尝试停止服务
  • stopSelf(startId):智能停止,确保所有任务完成
  • 避免服务在仍有任务执行时被提前终止

(三)使用方式与示例

1. 基础实现示例

class MyIntentService : IntentService("MyIntentService") {
    
    companion object {
        private const val ACTION_DOWNLOAD = "com.example.action.DOWNLOAD"
        private const val EXTRA_URL = "extra_url"
        
        fun startDownload(context: Context, url: String) {
            val intent = Intent(context, MyIntentService::class.java).apply {
                action = ACTION_DOWNLOAD
                putExtra(EXTRA_URL, url)
            }
            context.startService(intent)
        }
    }
    
    override fun onHandleIntent(intent: Intent?) {
        when (intent?.action) {
            ACTION_DOWNLOAD -> {
                val url = intent.getStringExtra(EXTRA_URL)
                url?.let { downloadFile(it) }
            }
            // 处理其他action...
        }
    }
    
    private fun downloadFile(url: String) {
        // 在工作线程中执行下载任务
        // 这里是长时间运行的操作
        Thread.sleep(5000) // 模拟耗时操作
        Log.d("MyIntentService", "下载完成: $url")
    }
    
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MyIntentService", "服务停止")
    }
}

// 启动服务
MyIntentService.startDownload(context, "https://example.com/file.zip")

2. 带回调功能的扩展实现

class NotificationIntentService : IntentService("NotificationService") {
    
    interface Callback {
        fun onTaskCompleted(result: String)
    }
    
    companion object {
        private var callback: Callback? = null
        
        fun setCallback(cb: Callback) {
            callback = cb
        }
        
        fun startProcess(context: Context, data: String) {
            val intent = Intent(context, NotificationIntentService::class.java).apply {
                putExtra("data", data)
            }
            context.startService(intent)
        }
    }
    
    override fun onHandleIntent(intent: Intent?) {
        val input = intent?.getStringExtra("data") ?: return
        
        // 模拟处理过程
        Thread.sleep(3000)
        val result = "处理结果: $input"
        
        // 通过Handler发送到主线程回调
        Handler(Looper.getMainLooper()).post {
            callback?.onTaskCompleted(result)
        }
        
        // 发送通知
        sendNotification(result)
    }
    
    private fun sendNotification(result: String) {
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) 
                as NotificationManager
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                "service_channel",
                "服务通知",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }
        
        val notification = NotificationCompat.Builder(this, "service_channel")
            .setContentTitle("任务完成")
            .setContentText(result)
            .setSmallIcon(R.drawable.ic_notification)
            .build()
        
        notificationManager.notify(1, notification)
    }
}

(四)局限性分析

1. 串行执行的性能问题

// 性能限制示例
class MyIntentService : IntentService("MyService") {
    override fun onHandleIntent(intent: Intent?) {
        when (intent?.action) {
            "TASK_LONG" -> {
                Thread.sleep(10000) // 10秒长任务
            }
            "TASK_SHORT" -> {
                Thread.sleep(1000)  // 1秒短任务
            }
        }
    }
}

// 问题:短任务必须等待长任务完成
MyIntentService.startTask(context, "TASK_LONG")  // 开始10秒任务
MyIntentService.startTask(context, "TASK_SHORT") // 必须等待10秒后才能执行

2. 生命周期控制有限

  • 无法暂停/恢复任务:一旦开始执行必须完成
  • 无法取消特定任务:只能停止整个服务
  • 无法获取任务进度:没有内置的进度报告机制

3. Android版本限制问题

Android 8.0(API 26)限制
// Android 8.0+ 后台服务限制
// IntentService属于后台服务,有以下限制:
class MyIntentService : IntentService("MyService") {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 在Android 8.0+上,当应用进入后台后:
        // 1. 有几分钟的时间窗口可以启动服务
        // 2. 之后系统会停止服务并抛出异常
        
        // 必须转为前台服务才能长时间运行
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notification = createNotification()
            startForeground(1, notification)
        }
        
        return super.onStartCommand(intent, flags, startId)
    }
}
Android 11(API 30)废弃
// Android 11开始,IntentService被标记为@Deprecated
/**
 * @deprecated This class is deprecated. See ...
 * Use {@link androidx.core.app.JobIntentService} or
 * {@link android.app.job.JobScheduler} or
 * {@link androidx.work.WorkManager} instead.
 */
@Deprecated
public abstract class IntentService extends Service {
    // ...
}

(五)现代替代方案

1. JobIntentService(兼容方案)

// 使用Jetpack的JobIntentService
class MyJobIntentService : JobIntentService() {
    
    companion object {
        private const val JOB_ID = 1000
        
        fun enqueueWork(context: Context, intent: Intent) {
            enqueueWork(context, MyJobIntentService::class.java, JOB_ID, intent)
        }
    }
    
    override fun onHandleWork(intent: Intent) {
        // 处理后台工作
        // 在Android 8.0+上自动使用JobScheduler
        // 在旧版本上使用普通Service
        performTask(intent)
    }
    
    override fun onStopCurrentWork(): Boolean {
        // 返回true表示允许停止当前工作
        // 返回false表示继续执行
        return true
    }
}

JobIntentService优势

  • 自动适配不同Android版本
  • 在Android 8.0+上使用JobScheduler,避免后台限制
  • 保持与IntentService类似的API

2. WorkManager(官方推荐)

// 使用WorkManager处理后台任务
class MyWorker(context: Context, params: WorkerParameters) 
    : CoroutineWorker(context, params) {
    
    override suspend fun doWork(): Result {
        // 在后台线程执行
        return try {
            val inputData = inputData.getString("key")
            performTask(inputData)
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) {
                Result.retry()
            } else {
                Result.failure()
            }
        }
    }
    
    companion object {
        fun scheduleWork(context: Context, data: String) {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .setRequiresBatteryNotLow(true)
                .build()
            
            val inputData = workDataOf("key" to data)
            
            val workRequest = OneTimeWorkRequestBuilder<MyWorker>()
                .setConstraints(constraints)
                .setInputData(inputData)
                .setBackoffCriteria(
                    BackoffPolicy.LINEAR,
                    10,
                    TimeUnit.SECONDS
                )
                .build()
            
            WorkManager.getInstance(context).enqueue(workRequest)
        }
    }
}

WorkManager优势

  • 系统级调度,保证任务执行
  • 支持约束条件(网络、电量、存储空间等)
  • 支持链式任务和周期性任务
  • 自动处理Android版本差异

3. 协程+生命周期感知组件

// 使用ViewModel + 协程
class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
    
    private val _taskState = MutableStateFlow<TaskState>(TaskState.Idle)
    val taskState: StateFlow<TaskState> = _taskState
    
    fun executeTask(data: String) {
        viewModelScope.launch {
            _taskState.value = TaskState.Loading
            try {
                val result = withContext(Dispatchers.IO) {
                    repository.processData(data)
                }
                _taskState.value = TaskState.Success(result)
            } catch (e: Exception) {
                _taskState.value = TaskState.Error(e.message)
            }
        }
    }
}

// 在Activity/Fragment中观察
class TaskFragment : Fragment() {
    private val viewModel: TaskViewModel by viewModels()
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        lifecycleScope.launch {
            viewModel.taskState.collect { state ->
                when (state) {
                    is TaskState.Loading -> showLoading()
                    is TaskState.Success -> showResult(state.result)
                    is TaskState.Error -> showError(state.message)
                    else -> Unit
                }
            }
        }
    }
}

(六)迁移指南

1. 从IntentService迁移到WorkManager

// 原IntentService
class OldIntentService : IntentService("OldService") {
    override fun onHandleIntent(intent: Intent?) {
        val data = intent?.getStringExtra("data")
        // 长时间运行的任务
        processData(data)
        sendBroadcast(Intent("ACTION_COMPLETE"))
    }
}

// 迁移为WorkManager
class NewWorker(context: Context, params: WorkerParameters) 
    : CoroutineWorker(context, params) {
    
    override suspend fun doWork(): Result {
        val data = inputData.getString("data")
        data?.let { processData(it) }
        
        // 发送广播或使用其他通知机制
        withContext(Dispatchers.Main) {
            LocalBroadcastManager.getInstance(context)
                .sendBroadcast(Intent("ACTION_COMPLETE"))
        }
        
        return Result.success()
    }
}

// 启动方式变更
// 旧方式:
context.startService(Intent(context, OldIntentService::class.java).apply {
    putExtra("data", "test")
})

// 新方式:
val workRequest = OneTimeWorkRequestBuilder<NewWorker>()
    .setInputData(workDataOf("data" to "test"))
    .build()
WorkManager.getInstance(context).enqueue(workRequest)

2. 兼容旧代码的包装方案

// 提供向后兼容的接口
object TaskExecutor {
    
    @Deprecated("使用scheduleTask代替")
    fun startIntentService(context: Context, data: String) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
            // 在Android 8.0以下使用IntentService
            val intent = Intent(context, LegacyIntentService::class.java).apply {
                putExtra("data", data)
            }
            context.startService(intent)
        } else {
            // 在Android 8.0+使用WorkManager
            scheduleTask(context, data)
        }
    }
    
    fun scheduleTask(context: Context, data: String) {
        val workRequest = OneTimeWorkRequestBuilder<ModernWorker>()
            .setInputData(workDataOf("data" to data))
            .build()
        WorkManager.getInstance(context).enqueue(workRequest)
    }
}

(七)最佳实践总结

1. 现代Android开发建议

场景 推荐方案 理由
简单后台任务 WorkManager 系统调度,保证执行,自动适配
需要即时执行 前台服务 + 协程 立即执行,用户可见,符合政策
数据库操作 Room + 协程 类型安全,自动线程切换
文件下载 WorkManager + 协程 支持约束条件,断点续传
周期性任务 WorkManager定期任务 系统优化,省电

2. 选择决策流程

graph TD
    A[需要后台任务] --> B{任务特性}
    B --> C[简单/一次性]
    B --> D[复杂/周期性]
    B --> E[需要即时执行]
    
    C --> F[使用WorkManager]
    D --> F
    E --> G[前台服务 + 协程]
    
    F --> H[Android 8.0+ 自动使用JobScheduler]
    F --> I[旧版本使用兼容方案]
    G --> J[显示通知 遵循前台服务规范]

3. 代码质量要点

// ✅ 现代最佳实践
class ModernTaskHandler {
    // 使用依赖注入
    private val workManager: WorkManager
    private val scope: CoroutineScope
    
    // 提供明确的API
    suspend fun executeTask(data: String): Result {
        return withContext(Dispatchers.IO) {
            try {
                performTask(data)
                Result.success()
            } catch (e: Exception) {
                Result.failure()
            }
        }
    }
    
    // 支持取消
    fun cancelAllTasks() {
        scope.cancel()
        workManager.cancelAllWork()
    }
}

// ❌ 应避免的做法
class BadPractice {
    // 1. 不要在新的Android版本上使用IntentService
    // class OldService : IntentService("Deprecated") 
    
    // 2. 不要忘记处理后台限制
    // context.startService(intent) // Android 8.0+ 会崩溃
    
    // 3. 不要在没有通知的情况下使用前台服务
    // startForeground(0, null) // Android 9.0+ 会崩溃
}

(八)面试回答要点

1. 核心特点回答框架

"IntentService是Android中一个特殊的Service,它的核心特点包括:

  1. 自动创建工作线程执行任务,开发者只需实现onHandleIntent方法
  2. 串行处理Intent请求,避免多线程并发问题
  3. 任务执行完成后自动停止服务,减少资源占用
  4. 内部基于HandlerThread和Handler实现"

2. 废弃原因与替代方案

"IntentService在Android 11中被废弃,主要因为:

  1. Android 8.0+的后台执行限制使IntentService不可靠
  2. 串行执行模型在现代多核CPU上效率不高
  3. 缺乏灵活的任务控制(暂停、取消、进度报告)

推荐替代方案:

  1. WorkManager:系统级任务调度,支持约束条件
  2. JobIntentService:兼容方案,自动适配不同版本
  3. 协程+ViewModel:结构化并发,生命周期感知"

3. 实际应用建议

“对于新项目,应直接使用WorkManager或协程方案。
对于维护老项目,可以逐步将IntentService迁移到WorkManager,同时使用兼容层保持API稳定。
需要立即执行的任务应使用前台服务并显示通知。”

总结要点:IntentService曾是Android后台任务的标准解决方案,但随着Android系统的发展,它已被更现代的方案取代。理解其原理有助于学习Android线程模型,但在实际开发中应使用WorkManager等官方推荐方案。

八十、如何将Activity设置为窗口样式?

(一)基本实现方法

1. 通过AndroidManifest配置

<!-- 方式1:使用系统对话框主题 -->
<activity
    android:name=".DialogActivity"
    android:theme="@android:style/Theme.Dialog"
    android:windowSoftInputMode="adjustResize|stateHidden" />

<!-- 方式2:使用透明对话框主题 -->
<activity
    android:name=".TranslucentDialogActivity"
    android:theme="@android:style/Theme.Translucent.NoTitleBar"
    android:windowSoftInputMode="adjustResize" />

<!-- 方式3:使用Material Design对话框主题 -->
<activity
    android:name=".MaterialDialogActivity"
    android:theme="@style/Theme.MaterialComponents.Light.Dialog"
    android:windowSoftInputMode="adjustResize" />

2. 通过代码动态设置

class WindowActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // 必须在setContentView之前调用
        setWindowTheme()
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_window)
        
        // 进一步配置窗口属性
        configureWindowProperties()
    }
    
    private fun setWindowTheme() {
        // 方法1:设置对话框主题
        setTheme(android.R.style.Theme_Dialog)
        
        // 方法2:设置透明主题
        // setTheme(android.R.style.Theme_Translucent_NoTitleBar)
        
        // 方法3:使用自定义主题
        // setTheme(R.style.CustomDialogTheme)
    }
    
    private fun configureWindowProperties() {
        val window = window
        
        // 设置窗口大小
        window.setLayout(
            WindowManager.LayoutParams.MATCH_PARENT, // 宽度
            WindowManager.LayoutParams.WRAP_CONTENT   // 高度
        )
        
        // 设置窗口位置
        window.setGravity(Gravity.CENTER) // 居中显示
        
        // 设置窗口动画
        window.setWindowAnimations(R.style.DialogAnimation)
        
        // 设置窗口背景(透明或半透明)
        window.setBackgroundDrawableResource(android.R.color.transparent)
        
        // 设置窗口属性
        val attributes = window.attributes
        attributes.dimAmount = 0.5f  // 背景变暗程度(0-1)
        attributes.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND
        window.attributes = attributes
    }
}

(二)自定义主题详细配置

1. 自定义对话框主题(styles.xml)

<!-- values/styles.xml -->
<style name="CustomDialogTheme" parent="Theme.MaterialComponents.Light.Dialog">
    <!-- 窗口背景 -->
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:backgroundDimEnabled">true</item>
    <item name="android:backgroundDimAmount">0.6</item>
    
    <!-- 窗口尺寸 -->
    <item name="android:windowMinWidthMajor">@dimen/dialog_min_width</item>
    <item name="android:windowMinWidthMinor">@dimen/dialog_min_width</item>
    <item name="android:windowMaxWidth">@dimen/dialog_max_width</item>
    
    <!-- 窗口动画 -->
    <item name="android:windowAnimationStyle">@style/DialogAnimation</item>
    
    <!-- 沉浸式适配 -->
    <item name="android:windowFullscreen">false</item>
    <item name="android:windowContentOverlay">@null</item>
    
    <!-- 状态栏和导航栏 -->
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">false</item>
    <item name="android:windowDrawsSystemBarBackgrounds">true</item>
    
    <!-- 圆角背景 -->
    <item name="android:windowCornerRadius">@dimen/dialog_corner_radius</item>
</style>

<!-- 窗口动画 -->
<style name="DialogAnimation">
    <item name="android:windowEnterAnimation">@anim/dialog_enter</item>
    <item name="android:windowExitAnimation">@anim/dialog_exit</item>
</style>

2. 圆角对话框实现

<!-- drawable/dialog_background.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@android:color/white" />
    <corners
        android:radius="16dp"
        android:topLeftRadius="16dp"
        android:topRightRadius="16dp" />
    <padding
        android:left="24dp"
        android:top="24dp"
        android:right="24dp"
        android:bottom="24dp" />
</shape>

<!-- values/themes.xml -->
<style name="RoundedDialogTheme" parent="Theme.MaterialComponents.Light.Dialog">
    <item name="android:windowBackground">@drawable/dialog_background</item>
    <item name="android:background">@android:color/transparent</item>
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowContentOverlay">@null</item>
</style>

(三)窗口属性详细控制

1. WindowManager.LayoutParams配置

class FloatingActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_floating)
        
        configureWindowParams()
    }
    
    private fun configureWindowParams() {
        val window = window
        val params = WindowManager.LayoutParams().apply {
            // 基本属性
            width = WindowManager.LayoutParams.MATCH_PARENT
            height = WindowManager.LayoutParams.WRAP_CONTENT
            gravity = Gravity.CENTER
            
            // 窗口类型(重要!)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                type = WindowManager.LayoutParams.TYPE_PHONE
            }
            
            // 窗口标志
            flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
            
            // 透明度
            alpha = 0.9f
            
            // 背景变暗
            dimAmount = 0.5f
            flags = flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND
            
            // 软键盘处理
            softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
            
            // 动画
            windowAnimations = R.style.DialogAnimation
        }
        
        window.attributes = params
        
        // 设置窗口背景
        window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
    }
    
    /**
     * 动态调整窗口大小和位置
     */
    fun updateWindowLayout(width: Int, height: Int, gravity: Int) {
        val window = window
        val params = window.attributes.apply {
            this.width = width
            this.height = height
            this.gravity = gravity
        }
        window.attributes = params
    }
}

2. 全屏对话框(边缘到边缘)

class FullScreenDialogActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        // 设置全屏对话框主题
        setTheme(R.style.FullScreenDialogTheme)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_fullscreen_dialog)
        
        // 边缘到边缘显示
        enableEdgeToEdge()
    }
    
    private fun enableEdgeToEdge() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            window.setDecorFitsSystemWindows(false)
            WindowCompat.setDecorFitsSystemWindows(window, false)
            WindowInsetsControllerCompat(window, window.decorView).let { controller ->
                controller.hide(WindowInsetsCompat.Type.systemBars())
                controller.systemBarsBehavior = 
                    WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
            }
        } else {
            @Suppress("DEPRECATION")
            window.decorView.systemUiVisibility = (
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            )
        }
    }
    
    override fun onBackPressed() {
        // 添加退出动画
        supportFinishAfterTransition()
    }
}

(四)交互处理与事件控制

1. 外部点击关闭处理

class DismissibleDialogActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dialog)
        
        // 设置外部可点击关闭
        setFinishOnTouchOutside(true)
        
        // 或者自定义点击外部逻辑
        setupExternalClickHandler()
    }
    
    private fun setupExternalClickHandler() {
        // 方法1:监听根布局点击
        findViewById<View>(android.R.id.content).setOnClickListener {
            if (isOutsideClick(it)) {
                finishWithAnimation()
            }
        }
        
        // 方法2:在onTouchEvent中处理
        // 已在setFinishOnTouchOutside中实现
    }
    
    private fun isOutsideClick(view: View): Boolean {
        // 检查点击是否在对话框内容区域外
        val content = findViewById<ViewGroup>(R.id.dialog_content)
        val location = IntArray(2)
        content.getLocationOnScreen(location)
        
        val x = location[0]
        val y = location[1]
        val width = content.width
        val height = content.height
        
        val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 
            view.x, view.y, 0)
        
        return !(event.x >= x && event.x <= x + width &&
                event.y >= y && event.y <= y + height)
    }
    
    private fun finishWithAnimation() {
        supportFinishAfterTransition()
    }
    
    override fun onTouchEvent(event: MotionEvent): Boolean {
        // 方法3:重写onTouchEvent
        if (event.action == MotionEvent.ACTION_DOWN && isOutsideClick(window.decorView)) {
            finish()
            return true
        }
        return super.onTouchEvent(event)
    }
}

2. 拖动功能实现

class DraggableDialogActivity : AppCompatActivity() {
    
    private var initialX = 0
    private var initialY = 0
    private var initialTouchX = 0f
    private var initialTouchY = 0f
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_draggable)
        
        // 设置可拖动区域
        val dragHandle = findViewById<View>(R.id.drag_handle)
        dragHandle.setOnTouchListener { v, event ->
            handleDrag(event)
            true
        }
        
        // 设置窗口为可拖动模式
        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
        window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
    }
    
    private fun handleDrag(event: MotionEvent): Boolean {
        val window = window
        val params = window.attributes
        
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                initialX = params.x
                initialY = params.y
                initialTouchX = event.rawX
                initialTouchY = event.rawY
                return true
            }
            
            MotionEvent.ACTION_MOVE -> {
                val deltaX = (event.rawX - initialTouchX).toInt()
                val deltaY = (event.rawY - initialTouchY).toInt()
                
                params.x = initialX + deltaX
                params.y = initialY + deltaY
                window.attributes = params
                return true
            }
            
            MotionEvent.ACTION_UP -> {
                // 拖动结束,可以添加吸附效果
                snapToEdge(params)
                window.attributes = params
                return true
            }
        }
        return false
    }
    
    private fun snapToEdge(params: WindowManager.LayoutParams) {
        val display = windowManager.defaultDisplay
        val displayMetrics = DisplayMetrics()
        display.getMetrics(displayMetrics)
        
        val screenWidth = displayMetrics.widthPixels
        val screenHeight = displayMetrics.heightPixels
        
        // 吸附到左边或右边
        if (params.x < screenWidth / 2) {
            params.x = 0
        } else {
            params.x = screenWidth - window.decorView.width
        }
    }
}

(五)现代替代方案

1. DialogFragment(推荐方案)

class CustomDialogFragment : DialogFragment() {
    
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        // 使用Material Design对话框
        return MaterialAlertDialogBuilder(requireContext())
            .setTitle("标题")
            .setMessage("消息内容")
            .setPositiveButton("确定") { dialog, which ->
                // 处理点击
            }
            .setNegativeButton("取消") { dialog, which ->
                dismiss()
            }
            .create()
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 自定义布局
        return inflater.inflate(R.layout.fragment_custom_dialog, container, false)
    }
    
    override fun onStart() {
        super.onStart()
        
        // 配置对话框窗口
        dialog?.window?.apply {
            // 设置大小
            setLayout(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.WRAP_CONTENT
            )
            
            // 设置动画
            setWindowAnimations(R.style.DialogAnimation)
            
            // 设置背景
            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        }
    }
    
    // 在Activity中显示
    fun showDialog() {
        val dialog = CustomDialogFragment()
        dialog.show(supportFragmentManager, "CustomDialog")
    }
}

2. BottomSheetDialogFragment

class CustomBottomSheetFragment : BottomSheetDialogFragment() {
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_bottom_sheet, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 配置BottomSheet行为
        dialog?.setOnShowListener { dialog ->
            val bottomSheetDialog = dialog as BottomSheetDialog
            val bottomSheet = bottomSheetDialog.findViewById<View>(
                com.google.android.material.R.id.design_bottom_sheet
            )
            bottomSheet?.let {
                val behavior = BottomSheetBehavior.from(it)
                behavior.state = BottomSheetBehavior.STATE_EXPANDED
                behavior.peekHeight = 0
                behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                    override fun onStateChanged(bottomSheet: View, newState: Int) {
                        if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                            behavior.state = BottomSheetBehavior.STATE_EXPANDED
                        }
                    }
                    
                    override fun onSlide(bottomSheet: View, slideOffset: Float) {
                        // 滑动回调
                    }
                })
            }
        }
    }
}

3. Compose对话框

@Composable
fun CustomDialog(
    onDismiss: () -> Unit
) {
    AlertDialog(
        onDismissRequest = onDismiss,
        properties = DialogProperties(
            dismissOnBackPress = true,
            dismissOnClickOutside = true,
            usePlatformDefaultWidth = false
        ),
        modifier = Modifier
            .fillMaxWidth(0.9f)
            .wrapContentHeight()
            .clip(RoundedCornerShape(16.dp)),
        title = {
            Text(text = "对话框标题")
        },
        text = {
            Text("对话框内容")
        },
        confirmButton = {
            TextButton(onClick = onDismiss) {
                Text("确定")
            }
        },
        dismissButton = {
            TextButton(onClick = onDismiss) {
                Text("取消")
            }
        }
    )
}

// 在Activity中使用
class ComposeDialogActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setContent {
            var showDialog by remember { mutableStateOf(false) }
            
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.Center
            ) {
                Button(onClick = { showDialog = true }) {
                    Text("显示对话框")
                }
                
                if (showDialog) {
                    CustomDialog(
                        onDismiss = { showDialog = false }
                    )
                }
            }
        }
    }
}

(六)注意事项与最佳实践

1. 内存泄漏预防

class SafeDialogActivity : AppCompatActivity() {
    
    private val uiScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 设置对话框主题
        setTheme(R.style.DialogTheme)
        setContentView(R.layout.activity_dialog)
        
        // 监听生命周期
        lifecycle.addObserver(object : LifecycleEventObserver {
            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                when (event) {
                    Lifecycle.Event.ON_DESTROY -> {
                        // 清理资源
                        cleanup()
                    }
                    else -> {}
                }
            }
        })
    }
    
    private fun cleanup() {
        // 取消协程
        uiScope.cancel()
        
        // 移除回调
        window.decorView.viewTreeObserver.removeOnGlobalLayoutListener(layoutListener)
        
        // 清空Handler消息
        handler.removeCallbacksAndMessages(null)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // 确保被销毁
        if (!isFinishing) {
            finish()
        }
    }
}

2. 多窗口模式适配

class MultiWindowDialogActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 检测多窗口模式
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            if (isInMultiWindowMode) {
                // 调整对话框大小和位置
                adjustForMultiWindow()
            }
        }
        
        // 监听多窗口模式变化
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            registerComponentCallbacks(object : ComponentCallbacks2 {
                override fun onConfigurationChanged(newConfig: Configuration) {
                    // 配置变化
                }
                
                override fun onLowMemory() {
                    // 低内存
                }
                
                override fun onTrimMemory(level: Int) {
                    // 内存调整
                }
            })
        }
    }
    
    private fun adjustForMultiWindow() {
        window.attributes.apply {
            // 在多窗口模式下调整大小
            width = (resources.displayMetrics.widthPixels * 0.7).toInt()
            height = WindowManager.LayoutParams.WRAP_CONTENT
            gravity = Gravity.CENTER
        }
    }
    
    override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) {
        super.onMultiWindowModeChanged(isInMultiWindowMode)
        if (isInMultiWindowMode) {
            adjustForMultiWindow()
        } else {
            // 恢复全屏模式设置
            window.attributes.apply {
                width = WindowManager.LayoutParams.MATCH_PARENT
                gravity = Gravity.CENTER
            }
        }
    }
}

3. 权限处理

// Android 10+ 需要悬浮窗权限
class FloatingWindowActivity : AppCompatActivity() {
    
    companion object {
        private const val REQUEST_OVERLAY_PERMISSION = 100
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 检查悬浮窗权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(this)) {
                requestOverlayPermission()
                return
            }
        }
        
        setupFloatingWindow()
    }
    
    @RequiresApi(Build.VERSION_CODES.M)
    private fun requestOverlayPermission() {
        val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {
            data = Uri.parse("package:$packageName")
        }
        startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION)
    }
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_OVERLAY_PERMISSION) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
                Settings.canDrawOverlays(this)) {
                setupFloatingWindow()
            } else {
                Toast.makeText(this, "需要悬浮窗权限", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }
    
    private fun setupFloatingWindow() {
        // 设置悬浮窗属性
        window.attributes.apply {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
            } else {
                @Suppress("DEPRECATION")
                type = WindowManager.LayoutParams.TYPE_PHONE
            }
        }
    }
}

(七)性能优化建议

1. 窗口重用

object DialogWindowManager {
    
    private val dialogCache = mutableMapOf<String, WeakReference<Dialog>>()
    
    fun showCachedDialog(
        context: Context,
        dialogKey: String,
        createDialog: () -> Dialog
    ) {
        val cachedDialog = dialogCache[dialogKey]?.get()
        
        if (cachedDialog?.isShowing == true) {
            return
        }
        
        val dialog = cachedDialog ?: createDialog()
        dialog.show()
        
        dialogCache[dialogKey] = WeakReference(dialog)
    }
    
    fun dismissDialog(dialogKey: String) {
        dialogCache[dialogKey]?.get()?.dismiss()
    }
    
    fun clearCache() {
        dialogCache.values.forEach { it.get()?.dismiss() }
        dialogCache.clear()
    }
}

2. 内存优化

class OptimizedDialogActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 使用轻量级主题
        setTheme(R.style.LightweightDialogTheme)
        
        // 避免过度绘制
        window.setBackgroundDrawableResource(android.R.color.transparent)
        
        // 使用硬件加速
        window.setFlags(
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
        )
    }
    
    override fun onStop() {
        super.onStop()
        // 释放资源
        releaseUnusedResources()
    }
    
    private fun releaseUnusedResources() {
        // 释放图片资源等
        findViewById<ImageView>(R.id.large_image)?.setImageDrawable(null)
        
        // 停止动画
        findViewById<View>(R.id.animated_view)?.clearAnimation()
    }
}

(八)总结与最佳实践

1. 实现方案对比

方案 优点 缺点 适用场景
对话框主题Activity 简单易用,生命周期完整 性能开销大,启动慢 需要完整Activity功能的对话框
DialogFragment 生命周期管理好,复用性强 需要FragmentManager 大多数对话框场景
BottomSheet 符合Material Design,体验好 限制底部显示 底部弹出的对话框
Compose对话框 声明式UI,代码简洁 需要Compose环境 使用Compose的项目
系统悬浮窗 可跨应用显示,灵活 权限复杂,兼容性问题 需要常驻悬浮窗

2. 关键配置要点

  1. 主题设置:在AndroidManifest或代码中设置对话框主题
  2. 窗口属性:通过WindowManager.LayoutParams控制大小、位置、动画
  3. 交互处理:正确处理外部点击、返回键、拖动等
  4. 权限管理:Android 10+需要悬浮窗权限
  5. 生命周期:妥善管理资源,防止内存泄漏

3. 现代开发建议

  • 优先使用DialogFragment:Google推荐,生命周期管理完善
  • 使用Material Design组件:提供一致的用户体验
  • 考虑使用Compose:声明式UI简化对话框开发
  • 避免滥用Activity对话框:性能开销大,启动慢
  • 做好兼容性适配:考虑不同Android版本和厂商定制

4. 常见问题解决

  • 窗口大小不适配:使用MATCH_PARENT或固定尺寸,考虑屏幕方向
  • 软键盘遮挡:设置windowSoftInputModeadjustResize
  • 内存泄漏:使用WeakReference,及时清理资源
  • 动画卡顿:使用硬件加速,优化动画资源
  • 厂商兼容性:测试不同厂商设备,使用系统通用方案

最终建议:在新项目中,除非有特殊需求,否则应优先使用DialogFragment或Compose对话框。Activity对话框方案适用于需要完整Activity生命周期和复杂业务逻辑的场景,但需要注意性能优化和内存管理。

参考文献