Android面试题(八)
记录 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支付、小程序支付的不同处理
(八)总结要点
- 核心流程:商户注册→密钥配置→服务端下单→客户端调起支付→异步通知处理
- 安全核心:私钥服务端保存、双重签名验证、金额校验、防重处理
- 最新规范:使用RSA2签名、适配Android新权限系统、隐私合规
- 容错机制:支付状态查询、异步通知补单、异常重试
- 监控运维:支付成功率监控、失败原因分析、定期对账
重要提醒:实际开发中请务必参考支付宝官方文档,支付接口和规范可能更新,本文基于当前最新实践整理。
七十二、如何实现线程安全的单例?
(一)基本概念与要求
单例模式(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");
}
}
(八)面试回答要点
- 理解各种实现的原理:知道每种方式如何保证线程安全
- 掌握优缺点:能对比不同方案的性能、安全性、复杂度
- 结合实际场景:能根据具体需求选择合适方案
- 了解最新发展:知道Kotlin、Java新特性对单例的影响
- 注意陷阱:反射、序列化、克隆等潜在问题
核心总结:
- 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. 性能问题
- 大数据集分页:使用
limit和offset参数 - 频繁更新优化:批量操作,减少通知频率
- 索引优化:数据库表添加合适索引
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. 核心要点记忆
- ContentProvider是基础:提供标准数据接口,支持跨进程
- ContentResolver是桥梁:所有数据访问都通过它
- ContentObserver是监听器:实现数据驱动的UI更新
- 权限控制是关键:通过URI权限机制保证安全
- 生命周期管理重要:及时注册和注销观察者
演进方向:在新应用中,优先考虑使用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. 核心要点总结
- 文件准备:确保数据库文件包含
android_metadata表 - 复制时机:应用首次启动或数据库更新时复制
- 版本管理:使用SharedPreferences或数据库内部版本表跟踪版本
- 性能优化:大文件分块处理,使用事务批量操作
- 错误处理:网络异常、文件损坏等情况的重试机制
- 安全考虑:防止SQL注入,敏感数据加密
3. 现代Android开发建议
- 优先使用Room:提供更好的类型安全和编译时检查
- 响应式编程:结合Flow或LiveData实现数据观察
- 后台处理:使用WorkManager处理大文件导入
- 测试覆盖:确保数据库迁移和导入逻辑的可靠性
最终建议:对于新项目,强烈推荐使用Room的createFromAsset()方法导入预打包数据库,这是目前最简单、最安全的方案。对于现有项目,可以根据具体情况选择最合适的迁移策略。
七十六、LinearLayout、RelativeLayout、FrameLayout性能对比?
(一)布局测量的基本原理
在对比性能前,需要了解Android布局渲染的核心流程:
- 测量(Measure):计算View的大小
- 布局(Layout):确定View的位置
- 绘制(Draw):将View绘制到屏幕上
性能关键:测量阶段的复杂度直接影响布局性能,测量次数越多、嵌套越深,性能消耗越大。
(二)各布局性能详细分析
1. RelativeLayout(相对布局)
(1)测量机制
// RelativeLayout的测量特点
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 第一轮测量:测量所有子View
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 根据相对关系调整后,进行第二轮测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 特殊情况可能需要更多轮测量
}
核心问题:由于子View之间存在复杂的相对依赖关系(如layout_toRightOf、layout_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+的布局预编译优化
(九)面试回答要点总结
- 理解测量原理:解释为什么不同布局测量次数不同
- 掌握性能排序:FrameLayout > LinearLayout(无weight) > ConstraintLayout > RelativeLayout > LinearLayout(有weight)
- 知道优化手段:减少嵌套、使用ViewStub、merge标签等
- 了解现代方案:ConstraintLayout的优势和适用场景
- 平衡开发效率:根据实际场景选择,不盲目追求性能
核心回答框架:
- 从布局测量原理入手
- 对比各布局的测量复杂度和性能特点
- 强调ConstraintLayout的现代优势
- 给出具体的优化建议
- 提及未来趋势(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:协议名称(必填),如:
myapp、weixin、alipay - host:主机地址,如:
www.example.com、page - 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域名,服务器配置验证文件
- 场景:适用于拥有自己域名的成熟应用
2. Firebase Dynamic Links
// Google提供的智能深度链接服务
Firebase.dynamicLinks
.getDynamicLink(intent)
.addOnSuccessListener { pendingDynamicLinkData ->
val deepLink = pendingDynamicLinkData?.link
deepLink?.let { link ->
// 处理深度链接
handleDeepLink(link)
}
}
优势:
- 跨平台(iOS/Android/Web)
- 智能降级(未安装App跳转网页版)
- 数据分析(跟踪链接效果)
3. Jetpack Navigation Deep Links
// 使用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. 安全最佳实践
- 验证来源:检查调用者包名或签名
- 参数签名:重要参数添加签名防止篡改
- 频率限制:防止恶意频繁调用
- 权限控制:敏感操作需要用户确认
- 日志记录:记录所有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子类,它的主要原理是:
- 继承Thread,在run()方法中调用Looper.prepare()创建消息队列
- 调用Looper.loop()进入消息循环
- 对外提供getLooper()方法,供其他线程创建Handler与它通信"
2. 优缺点对比
“优点:简化了子线程中Handler的使用,提供了现成的消息循环机制。
缺点:串行执行效率低,一个耗时任务会阻塞整个队列,且需要手动管理生命周期。”
3. 现代方案介绍
"在现代Android开发中,更推荐使用:
- Kotlin协程:结构化并发,代码简洁,自动生命周期管理
- ExecutorService线程池:支持并行执行,资源利用率高
- 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,它的核心特点包括:
- 自动创建工作线程执行任务,开发者只需实现onHandleIntent方法
- 串行处理Intent请求,避免多线程并发问题
- 任务执行完成后自动停止服务,减少资源占用
- 内部基于HandlerThread和Handler实现"
2. 废弃原因与替代方案
"IntentService在Android 11中被废弃,主要因为:
- Android 8.0+的后台执行限制使IntentService不可靠
- 串行执行模型在现代多核CPU上效率不高
- 缺乏灵活的任务控制(暂停、取消、进度报告)
推荐替代方案:
- WorkManager:系统级任务调度,支持约束条件
- JobIntentService:兼容方案,自动适配不同版本
- 协程+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. 关键配置要点
- 主题设置:在AndroidManifest或代码中设置对话框主题
- 窗口属性:通过WindowManager.LayoutParams控制大小、位置、动画
- 交互处理:正确处理外部点击、返回键、拖动等
- 权限管理:Android 10+需要悬浮窗权限
- 生命周期:妥善管理资源,防止内存泄漏
3. 现代开发建议
- 优先使用DialogFragment:Google推荐,生命周期管理完善
- 使用Material Design组件:提供一致的用户体验
- 考虑使用Compose:声明式UI简化对话框开发
- 避免滥用Activity对话框:性能开销大,启动慢
- 做好兼容性适配:考虑不同Android版本和厂商定制
4. 常见问题解决
- 窗口大小不适配:使用
MATCH_PARENT或固定尺寸,考虑屏幕方向 - 软键盘遮挡:设置
windowSoftInputMode为adjustResize - 内存泄漏:使用WeakReference,及时清理资源
- 动画卡顿:使用硬件加速,优化动画资源
- 厂商兼容性:测试不同厂商设备,使用系统通用方案
最终建议:在新项目中,除非有特殊需求,否则应优先使用DialogFragment或Compose对话框。Activity对话框方案适用于需要完整Activity生命周期和复杂业务逻辑的场景,但需要注意性能优化和内存管理。
参考文献
Android面试题(八)
https://blog.uso6.com/archives/androidmian-shi-ti-ba
评论