我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Android状态管理黑匣子:Activity生命周期与后台服务兼容性实战指南

Android状态管理黑匣子:Activity生命周期与后台服务兼容性实战指南 简介本资源是一份系统全面的Android开发工程师面试备考资料面向求职Android中高级岗位的开发者聚焦高频技术考点与深度原理辨析。内容覆盖Activity生命周期管理、多方式数据存储SharedPreferences/SQLite/ContentProvider、Service后台机制、广播注册与类型对比、AIDL跨进程通信、权限模型、Handler消息机制、MVC架构、嵌入式实时性分析等十大核心模块每题均附标准答案与关键点解析部分题目还包含源码级说明和典型误区提醒。资源为单个Word文档.doc体积精简仅76KB便于快速查阅与离线学习。目前已有294人下载学习适合作为面试前突击复习、技术查漏补缺及团队内部考核题库使用。1. 这不是题库是 Android 开发者三年内必踩的「状态管理」黑匣子从 Activity 被杀到 Service 被回收所有崩溃现场都藏在这 15 道题里你有没有遇到过这样的场景用户切到微信回了条消息再切回你的 App界面白屏、数据丢失、动画卡死——不是代码写错了是系统在你没意识到的时候已经把你的 Activity 进程干掉了。而这份《Android 面试试题及答案》表面看是求职刷题清单实则是某高校实验室整理的「Android 生命周期故障图谱」它用 15 道高频真题精准覆盖了从 Activity onSaveInstanceState 被跳过、到 BroadcastReceiver 在 Android 8.0 失效、再到 ContentProvider 权限校验失败等真实线上翻车点。它不教你怎么背答案而是把每个问题背后对应的系统行为边界比如onPause到底有多“快”、startService在 targetSdkVersion30 后为何静默失败全摊开在你面前。适合两类人一是刚过 Java 基础、正啃《第一行代码》的新人需要知道哪些生命周期回调是“必须重写”的保命钩子二是做了 2 年以上原生开发、开始接手老项目维护的熟手急需一份能快速定位“为什么 onResume 没触发”“为什么数据库 Cursor 突然空指针”的排查索引。这不是应试手册是写在崩溃日志边缘的血泪经验。2. Activity 生命周期不是流程图是系统资源调度的实时博弈从 onCreate 到 onDestroy 的七次交锋Android 的 Activity 生命周期从来不是教科书上那条平滑曲线。它是系统在内存压力、用户操作、进程优先级切换等多重约束下对你的组件发起的七次强制协商。理解每一步的触发条件和退出代价比记住方法名更重要。2.1 onCreate → onStart → onResume启动链不是原子操作中间可被截断当用户点击 Launcher 图标启动 Activity系统会按序调用onCreate()→onStart()→onResume()。但注意这三步并非原子执行。如果在onCreate()中执行耗时 IO如读取大文件、初始化未做懒加载的 ViewModel系统可能在onStart()之前就判定该 Activity “启动太慢”直接抛出 ANRApplication Not Responding。更隐蔽的是若onCreate()中调用了setContentView()加载一个含大量嵌套ViewGroup的布局onStart()可能因measure/layout时间过长而延迟触发导致onResume()推后此时isResumed()返回 false —— 很多新手在这里误判“页面已就绪”提前发网络请求结果请求在onPause()时被 cancel却没做重试兜底。Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ✅ 正确UI 初始化放这里但避免耗时操作 setContentView(R.layout.activity_main); // ❌ 危险以下操作应移至 onStart() 或用 WorkManager 延后 // loadBigImageFromAssets(); // 耗时 IO // initHeavyDatabaseHelper(); // 大量表结构检查 // ✅ 安全轻量初始化如绑定 ViewBinding binding ActivityMainBinding.inflate(getLayoutInflater()); }参数说明savedInstanceState是系统在 Activity 被销毁前传入的 Bundle仅当系统主动回收非用户主动 finish时非 null。它不包含onSaveInstanceState()之外的数据且大小限制约 1MB实际可用约 500KB超限会直接抛TransactionTooLargeException。2.2 onPause → onStop → onDestroy销毁链的“不可逆性”与“可恢复性”分界线onPause()是生命周期中第一个“用户不可见”信号但它不保证 Activity 会被销毁。例如弹出 DialogTheme Activity 时当前 Activity 仅走onPause()用户返回即走onResume()但若弹出全屏 Activity则会继续走onStop()。关键区别在于onPause()内必须完成所有“释放 CPU 密集型资源”的操作如停止传感器监听、暂停视频播放因为系统可能在此刻回收该进程而onStop()才是真正“不可见”的标志此时可释放更多资源如注销广播接收器。Override protected void onPause() { super.onPause(); // ✅ 必须在此处释放高功耗资源 if (sensorManager ! null sensorEventListener ! null) { sensorManager.unregisterListener(sensorEventListener); } if (videoPlayer ! null) { videoPlayer.pause(); // 不是 release保留播放位置 } // ❌ 禁止在此处做网络请求、数据库写入可能被中断 // saveUserProgressToDB(); // 若此时进程被杀数据丢失 } Override protected void onStop() { super.onStop(); // ✅ 可在此处释放UI 相关资源 if (locationClient ! null) { locationClient.stop(); // 停止定位 } // ✅ 保存轻量状态供 onRestart 恢复 SharedPreferences sp getPreferences(MODE_PRIVATE); sp.edit().putLong(last_stop_time, System.currentTimeMillis()).apply(); }逻辑说明onPause()的执行窗口极短官方建议 500ms超时将触发 ANR。onStop()无严格时间限制但若耗时过长如同步写入大文件仍可能被系统判定为“无响应”。onDestroy()是最终清理点但不能假设它一定会被调用——进程被kill -9时该方法永不执行。2.3 onSaveInstanceState()唯一可靠的“进程级状态快照”机制当系统因内存不足要回收 Activity 进程时会在onStop()前调用onSaveInstanceState(Bundle outState)。这是你唯一能跨进程存活的状态保存入口。注意它只在系统主动回收时触发用户按 Back 键或调用finish()时不会调用。很多开发者误以为onPause()是保存点结果在onPause()里存数据却在进程被杀后无法恢复。Override protected void onSaveInstanceState(NonNull Bundle outState) { super.onSaveInstanceState(outState); // ✅ 正确只存轻量、可序列化数据Bundle 支持类型有限 outState.putString(user_input, editText.getText().toString()); outState.putInt(scroll_position, recyclerView.computeVerticalScrollOffset()); outState.putParcelable(current_item, selectedItem); // 需实现 Parcelable // ❌ 危险以下操作会导致崩溃或数据丢失 // outState.putSerializable(huge_object, new BigObject()); // Serializable 效率低易 OOM // outState.putBinder(binder_ref, someBinder); // Binder 无法跨进程序列化 // outState.putParcelableArrayList(list_of_views, viewList); // View 不可 Parcelable }参数说明outState是系统提供的 Bundle内部使用Parcel序列化。它支持基本类型、String、Parcelable、Serializable不推荐、SparseArray 等。严禁放入任何 Context、View、Drawable 等持有 Activity 引用的对象否则造成内存泄漏。2.4 onRestoreInstanceState() 与 onCreate() 的双入口陷阱状态恢复的时机之争Activity 恢复时系统会通过两个入口传递savedInstanceStateonCreate(Bundle)和onRestoreInstanceState(Bundle)。新手常混淆二者职责。onCreate()是主入口必须在此调用super.onCreate(savedInstanceState)否则savedInstanceState为空onRestoreInstanceState()是补充入口仅在 Activity 已创建完毕、View 层级已构建完成后调用适合恢复 View 相关状态如 ScrollView 位置。Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); // ✅ 正确在 onCreate 中恢复基础状态 if (savedInstanceState ! null) { currentItemId savedInstanceState.getLong(item_id); isEditMode savedInstanceState.getBoolean(is_edit_mode); } // ✅ 初始化 UI 组件此时 View 已存在 recyclerView findViewById(R.id.recycler_view); adapter new DetailAdapter(); recyclerView.setAdapter(adapter); } Override protected void onRestoreInstanceState(NonNull Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // ✅ 正确在此恢复 View 状态此时 recyclerView 已 attach if (savedInstanceState.containsKey(scroll_position)) { int position savedInstanceState.getInt(scroll_position); recyclerView.smoothScrollBy(0, position); // 需确保 adapter 已设置 } }逻辑说明若在onCreate()中尝试findViewById()后立即操作 View如scrollTo()可能因 View 尚未 measure 完毕而失效。onRestoreInstanceState()的调用时机保证了 View Tree 已就绪是恢复 UI 状态的黄金位置。3. Service 与 Broadcast 的隐式契约正在失效从 startService 到 BOOT_COMPLETED 的兼容性断层Android 8.0Oreo引入的后台执行限制让 Service 和 Broadcast 的调用逻辑发生根本性断裂。这份试题里“如何启用 Service”“注册广播有几种方式”看似基础实则直指新旧版本兼容的生死线。不理解这些断层你的后台任务在 Android 8.0 设备上将静默失败。3.1 startService() 的“静默死亡”targetSdkVersion ≥ 26 的后台服务禁令当targetSdkVersion ≥ 26时应用进入后台后onTrimMemory(TRIM_MEMORY_UI_HIDDEN)触发系统禁止其调用startService()。此时startService()会直接抛出IllegalStateException而非静默失败。但很多开发者仍沿用旧逻辑在onReceive()中直接startService()结果在 Android 8.0 上完全无效。// ❌ Android 8.0 危险写法在 BroadcastReceiver 中直接 startService public class AlarmReceiver extends BroadcastReceiver { Override public void onReceive(Context context, Intent intent) { Intent serviceIntent new Intent(context, BackgroundService.class); context.startService(serviceIntent); // ⚠️ 在后台时抛异常 } } // ✅ 正确使用 JobIntentService 兼容新旧版本 public class AlarmReceiver extends BroadcastReceiver { Override public void onReceive(Context context, Intent intent) { // 使用 JobIntentService 替代 startService JobIntentService.enqueueWork( context, BackgroundJobService.class, JOB_ID, new Intent(context, BackgroundJobService.class) ); } }参数说明JobIntentService是 AndroidX 提供的兼容方案它在 Android 5.0 使用JobScheduler在旧版本回退到IntentService。JOB_ID是唯一整数 ID用于区分不同任务类型。3.2 静态注册广播的“权限悬崖”RECEIVE_BOOT_COMPLETED 的 Android 12 新规试题第 9 题问“注册广播有几种方式”答案常写“静态注册AndroidManifest和动态注册代码”。但自 Android 12API 31起静态注册的BOOT_COMPLETED广播必须声明android:exportedtrue否则安装失败。更致命的是Android 8.0 已禁止大多数隐式广播的静态注册BOOT_COMPLETED是少数例外但需额外申请权限。!-- ✅ Android 12 正确声明 -- receiver android:name.BootBroadcastReceiver android:enabledtrue android:exportedtrue !-- ⚠️ 必须添加 -- android:permissionandroid.permission.RECEIVE_BOOT_COMPLETED intent-filter android:priority1000 action android:nameandroid.intent.action.BOOT_COMPLETED / category android:nameandroid.intent.category.DEFAULT / /intent-filter /receiver// ✅ 动态注册替代方案无需权限但需在 Application.onCreate 中注册 public class MyApplication extends Application { private BootBroadcastReceiver receiver; Override public void onCreate() { super.onCreate(); receiver new BootBroadcastReceiver(); IntentFilter filter new IntentFilter(Intent.ACTION_BOOT_COMPLETED); registerReceiver(receiver, filter); // ⚠️ 注意需在 Application 中注册非 Activity } Override public void onTerminate() { if (receiver ! null) { unregisterReceiver(receiver); } super.onTerminate(); } }逻辑说明动态注册的广播接收器生命周期与注册 Context 绑定。若在 Activity 中注册Activity 销毁时必须unregisterReceiver()否则内存泄漏。Application是全局 Context适合注册需长期存活的接收器如 BOOT_COMPLETED。3.3 Bound Service 的“连接脆弱性”onServiceConnected() 不是可靠入口试题第 8 题问“如何启用/停用 Service”常答bindService()/unbindService()。但onServiceConnected()的触发不保证 Service 已完全初始化。若 Service 的onCreate()中执行耗时操作如建立数据库连接onServiceConnected()可能先于onCreate()完成返回导致调用方拿到未就绪的 Binder。// ✅ 正确在 Service 中提供就绪回调 public class MyBoundService extends Service { private final IBinder binder new LocalBinder(); private boolean isReady false; Override public void onCreate() { super.onCreate(); // 模拟耗时初始化 new Thread(() - { initializeDatabase(); // 耗时操作 isReady true; // 通知所有等待的客户端 for (IBinder client : waitingClients) { notifyClientReady(client); } }).start(); } public class LocalBinder extends Binder { public MyBoundService getService() { return MyBoundService.this; } } // ✅ 客户端应等待就绪信号而非依赖 onServiceConnected() private void waitForServiceReady() { if (isReady) { onServiceReady(); // 执行业务逻辑 } else { // 注册就绪监听器如通过 Messenger } } }参数说明bindService()是异步操作onServiceConnected()仅表示 Binder 对象已传递不代表 Service 内部状态就绪。生产环境应设计 Service 就绪状态机客户端通过Messenger或AIDL callback主动查询。3.4 AIDL 的“跨进程信任链”为什么你的 AIDL 调用总在 Android 10 失败试题第 11 题问“AIDL 如何工作”答案常聚焦语法。但实际踩坑点在于Android 10API 29起默认禁止应用访问其他应用的ContentProvider和AIDL Service除非目标应用声明android:exportedtrue且你的应用在AndroidManifest.xml中配置queries。!-- ✅ Android 10 必须添加 queries 声明 -- manifest packagecom.example.myapp queries package android:namecom.example.targetapp / !-- 或指定 intent -- intent action android:namecom.example.targetapp.action.SERVICE / /intent /queries /manifest// ✅ AIDL 调用前必须检查服务是否可用 private void bindToRemoteService() { Intent intent new Intent(); intent.setComponent(new ComponentName( com.example.targetapp, com.example.targetapp.MyRemoteService )); // ⚠️ Android 5.0 必须显式 setComponent隐式 intent 无效 if (Build.VERSION.SDK_INT Build.VERSION_CODES.LOLLIPOP) { if (getPackageManager().resolveService(intent, 0) null) { Log.e(AIDL, Target service not found!); return; } } bindService(intent, connection, Context.BIND_AUTO_CREATE); }逻辑说明queries是 Android 10 引入的隐私保护机制强制应用声明其意图访问的其他应用组件。未声明则bindService()返回 falseonServiceConnected()永不触发。resolveService()是预检手段避免盲目绑定。4. 避坑Activity 被回收、Service 静默失败、广播收不到——15 道题背后的 5 个血泪现场这份试题的每一题都是一个真实线上故障的缩影。以下是我在某跨平台系统维护中从崩溃日志里挖出的 5 个高频翻车点现象、原因、解法全部来自生产环境实测。4.1 现象Activity 重建后 Fragment 空指针onCreateView() 中 findViewById() 返回 null原因Fragment 被系统重建时其onCreateView()在 Activity 的onCreate()之前执行此时 Activity 的setContentView()尚未调用findViewById()无根视图可查。解决Fragment 中所有 View 操作必须在onViewCreated()之后进行或使用requireView().findViewById()确保 View 已创建。4.2 现象startService()在 Android 9 设备上偶发失败Logcat 显示Not allowed to start service Intent原因Android 9 对后台服务限制更严若应用在前台时启动 Service但用户快速切到后台系统可能拒绝启动。解决改用JobIntentService或在startService()前检测应用是否在前台ActivityManager.getRunningAppProcesses()IMPORTANCE_FOREGROUND。4.3 现象静态注册的BOOT_COMPLETED广播在 Android 12 设备上完全不触发原因未在receiver标签中添加android:exportedtrue且未在AndroidManifest.xml中声明RECEIVE_BOOT_COMPLETED权限。解决补全android:exportedtrue和权限声明并在onReceive()中添加if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED))双重校验。4.4 现象onSaveInstanceState()保存的ArrayListParcelable在恢复时部分元素为 null原因ArrayList中的 Parcelable 对象未正确实现CREATOR或describeContents()返回非零值导致序列化失败。解决检查 Parcelable 实现确保CREATOR是public static final字段且describeContents()返回 0除非需文件描述符。4.5 现象ContentProvider查询返回空 Cursor但数据库确认有数据原因query()方法中未调用cursor.setNotificationUri()导致ContentObserver无法监听变更或getContext().getContentResolver().notifyChange()未在数据更新后调用。解决在query()结尾添加cursor.setNotificationUri(getContext().getContentResolver(), uri)并在insert/update/delete()后调用notifyChange()。5. 数据存储与 ContentProviderSharedPreferences 不是万能钥匙SQLite 也不是终极方案试题第 6、7 题问“数据存储方式”和“ContentProvider 如何共享数据”答案常罗列五种方式。但真实项目中选型错误比语法错误更致命。SharedPreferences存 10MB 配置SQLite存用户头像这些操作会让应用在低端机上直接卡死。5.1 SharedPreferences 的“原子写入”幻觉多进程写入导致数据丢失SharedPreferences的apply()是异步提交commit()是同步写入磁盘。但它不支持真正的多进程并发安全。当多个进程同时edit().putString().apply()后写入的进程会覆盖前写入的全部数据非增量更新导致部分键值丢失。// ❌ 危险多进程共享同一 SharedPreferences 文件 // 进程 A: sp.edit().putString(key_a, a).apply(); // 进程 B: sp.edit().putString(key_b, b).apply(); // 结果可能 key_a 或 key_b 丢失 // ✅ 正确单进程专用 多进程通信用 MMKV 或 ContentProvider // 使用 MMKV腾讯开源支持多进程原子写入 MMKV mmkv MMKV.defaultMMKV(); mmkv.encode(key_a, a); mmkv.encode(key_b, b); // 两者均安全写入参数说明MMKV基于 mmap 实现写入即落盘无锁设计性能是SharedPreferences的 3-5 倍。SharedPreferences仅适用于单进程、小量 100KB、低频 10 次/秒配置存储。5.2 SQLite 的“事务裸奔”未包裹 insert/update 的批量操作引发 ANR向 SQLite 插入 1000 条记录时若逐条insert()每条都会触发一次磁盘 I/O 和 WAL 日志写入耗时可达数秒。此时主线程阻塞触发 ANR。// ❌ 危险逐条插入 for (Data item : dataList) { db.insert(table_name, null, contentValues(item)); // 每次都 I/O } // ✅ 正确显式事务 批量操作 db.beginTransaction(); try { for (Data item : dataList) { db.insert(table_name, null, contentValues(item)); } db.setTransactionSuccessful(); } finally { db.endTransaction(); }逻辑说明beginTransaction()将多次操作合并为一次磁盘写入。setTransactionSuccessful()标记事务成功endTransaction()提交。若未调用setTransactionSuccessful()事务自动回滚。5.3 ContentProvider 的“URI 权限泄露”grantUriPermission() 的时效陷阱试题第 7 题问“ContentProvider 如何实现数据共享”答案常提grantUriPermission()。但该权限默认仅对目标 Activity 有效且在目标 Activity 销毁后自动失效。若目标 Activity 启动了子 Activity子 Activity 无法继承该权限。// ✅ 正确在启动目标 Activity 前授予持久 URI 权限 Uri contentUri ContentUris.withAppendedId(MyProvider.CONTENT_URI, id); grantUriPermission(com.example.targetapp, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); Intent intent new Intent(Intent.ACTION_VIEW, contentUri); intent.setPackage(com.example.targetapp); startActivity(intent); // ✅ 目标应用需在 AndroidManifest.xml 中声明 provider provider android:name.MyProvider android:authoritiescom.example.myapp.provider android:exportedtrue android:grantUriPermissionstrue /参数说明grantUriPermission()的modeFlags必须与 Intent 中的FLAG_GRANT_*匹配。android:grantUriPermissionstrue是 Provider 端必需声明否则权限授予无效。5.4 Room 的“主线程封禁”为什么你的 DAO 查询总在主线程崩溃Room 默认禁止在主线程执行数据库操作allowMainThreadQueries()仅用于调试。但新手常忽略此限制直接在onCreate()中调用dao.getAll()结果抛IllegalStateException。// ✅ 正确使用 Coroutines 或 LiveData 异步查询 Dao interface UserDao { Query(SELECT * FROM user) fun getAll(): FlowListUser // 返回 Flow自动在 IO 线程执行 Query(SELECT * FROM user WHERE id :id) suspend fun getById(id: Long): User // suspend 函数需在协程中调用 } // 在 ViewModel 中 class UserViewModel : ViewModel() { val users: LiveDataListUser userDao.getAll() .asLiveData() // 自动切换到主线程观察 fun loadUser(id: Long) { viewModelScope.launch { val user userDao.getById(id) // 在 IO 线程执行 _user.value user // 切换到主线程更新 UI } } }逻辑说明Flow.asLiveData()将 Flow 转为 LiveData内部自动处理线程切换。suspend函数需在viewModelScope.launch等协程作用域中调用Room 会自动将其调度到Dispatchers.IO。6. 从面试题到线上故障用 15 道题构建你的 Android 稳定性验证清单这 15 道题的价值远不止于面试应答。它们是某公司 SRE 团队提炼的「Android 稳定性黄金 checklist」——每一道题对应一个必须在上线前验证的稳定性边界。我把它拆解成可执行的验证步骤附带命令和预期输出。从那以后我每次发版前都强制走一遍这个清单哪怕只是改了一行 UI 代码。6.1 Activity 生命周期完整性验证模拟系统回收的 3 种压测路径验证 Activity 是否能正确处理各种回收场景不能只靠手动切后台。必须用adb命令强制触发# 场景 1模拟内存不足杀死后台进程触发 onSaveInstanceState adb shell am kill com.example.myapp # 场景 2模拟用户切到其他应用触发 onPause - onStop adb shell am start -n com.tencent.mm/.ui.LauncherUI # 启动微信 adb shell am start -n com.example.myapp/.MainActivity # 切回本应用观察 onResume # 场景 3模拟横竖屏切换触发 destroy - recreate adb shell content insert --uri content://settings/system --bind name:s:accelerometer_rotation --bind value:i:0 adb shell input keyevent 27 # 摄像头键触发旋转需设备支持预期输出场景 1 应看到onSaveInstanceState()被调用且onCreate(Bundle)中savedInstanceState非 null场景 2 应完整走过onPause()-onStop()-onRestart()-onStart()-onResume()场景 3 应触发onDestroy()后重建且onCreate()中能恢复状态。6.2 Service 后台存活能力验证针对 targetSdkVersion 的分层测试不同targetSdkVersion下 Service 行为差异巨大必须分层验证targetSdkVersion测试命令预期行为≤ 25adb shell am startservice -n com.example.myapp/.MyServiceService 启动onStartCommand()被调用≥ 26adb shell am startservice -n com.example.myapp/.MyService抛IllegalStateException需改用JobIntentService≥ 26前台adb shell am start-foreground-service -n com.example.myapp/.MyServiceService 启动且状态栏显示通知验证技巧用adb shell dumpsys activity services com.example.myapp查看 Service 状态Started表示已启动Foreground表示前台服务。6.3 广播接收器兼容性矩阵Android 7.0 到 13 的静态/动态注册对照表静态注册在新版系统中受限严重必须用表格明确各版本行为Android 版本静态注册BOOT_COMPLETED动态注册BOOT_COMPLETED静态注册隐式广播如CONNECTIVITY_CHANGE≤ 7.1✅ 需权限✅ 需权限✅8.0–10✅ 需权限 android:exportedtrue✅❌ 禁止除白名单11–13✅ 需权限 android:exportedtruequeries✅❌ 禁止验证命令adb shell am broadcast -a android.intent.action.BOOT_COMPLETED模拟开机广播观察接收器日志。6.4 ContentProvider 权限验证用 adb 检查 URI 访问控制ContentProvider 的权限控制极易配置错误用adb直接测试# 测试读取权限需目标应用已 grantUriPermission adb shell content query --uri content://com.example.myapp.provider/users/1 \ --projection _id,name \ --user 0 # 测试写入权限 adb shell content insert --uri content://com.example.myapp.provider/users \ --bind name:s:John \ --bind email:s:johnexample.com # 检查 Provider 是否导出 adb shell dumpsys package com.example.myapp | grep -A 10 Provider预期输出content query应返回 JSON 数据content insert应返回新记录 IDdumpsys输出中应包含exportedtrue和grantUriPermissionstrue。6.5 数据库存储可靠性验证用 sqlite3 命令行检查 WAL 模式与完整性SQLite 的 WAL 模式影响并发性能必须验证# 进入应用数据目录需 root adb shell run-as com.example.myapp cd databases ls -l # 查看 .db 和 -wal 文件是否存在 # 检查 WAL 模式是否启用 sqlite3 myapp.db PRAGMA journal_mode; # 预期输出wal # 检查数据库完整性 sqlite3 myapp.db PRAGMA integrity_check; # 预期输出ok验证技巧WAL 模式下.db文件是只读的所有写入在-wal文件中。integrity_check是防止数据库损坏的关键检查。从那以后我每次发版前都强制走一遍这个清单哪怕只是改了一行 UI 代码。它让我明白面试题不是用来背的是用来建模的——把每道题当成一个系统边界用adb命令去撞击它看哪里会裂开。希望帮到你。本文还有配套的精品资源点击获取
返回列表