Android APN数据库查询对比分析(APN案例)
目录
- 功能背景
- 技术演进补充
- 分析
- 排查建议
- 疑问
- 对疑问的代码执行流程验证
- 代码实现
功能背景
APN 数据通常存储在数据库中,由TelephonyProvider提供。当用户进入APN设置界面时,Activity会启动,AOSP源码通过ContentResolver查询APN数据。关键分析点在于这个查询操作是否在主线程执行,因为主线程上的耗时操作会导致ANR。
技术演进补充
自android 9(API 28)起,系统对Telephony.Carriers
表的访问增加了权限限制:
- 需要
Carrier Privileges
或WRITE_APN_SETTINGS
权限 - 查询结果可能返回空Cursor,需配合
SubscriptionManager
处理多SIM卡场景
分析
通常,Android的ContentProvider处理数据请求,而TelepandroidhonyProvider继承自ContentProvider。查询操作可能会在后台线程执行,但需要确认。例如,CursorLoader会在后台线程处理查询,但如果是直接使用ContentResolver.query,可能就在主线程了。
Android源码中
- 使用LoaderManager来异步加载数据,这样查询会在后台进行。
- 如果用户使用的是直接查询而没有异步处理,那么主线程可能被阻塞。
排查建议
检查ApnSettingsActivity的onCreate或onResume方法,看是否触发数据加载。
- 使用LoaderManager.initLoader来启动CursorLoader,这样查询会在后台线程执行。
- 否则,如果直接在主线程调用getContentResolver().query,就会在主线程执行数据库操作。
疑问
Q:getContentResolver().query都是在主线程执行的吗?
A:直接调用getContentResolver().query取决于调用线程。
根据Android文档,ContentResolver的query方法默认是在调用线程执行的。如果直接在主线程调用getContentResolver().query,那么查询会在主线程执行,可能导致UI卡顿甚至ANR。然而,当使用CursorLoader时,内部会使用AsyncTask或者类似的机制,在后台线程执行查询,从而避免主线程阻塞。
CursorLoader内部封装了异步查询,所以即使调用了ContentResolver的query方法,也android是在Loader管理的后台线程中执行的,而不是主线程。
正确使用Loader或现代的替代方案(如Room的LiveData + ViewModel)来确保数据库操作不在主线程进行。
关键结论
- 直接调用:若在Activity/Fragment主线程中直接调用
getContentResolver().query()
,确实会在主线程执行数据库操作,可能导致ANR - 通过Loader机制:当使用
CursorLoader
时,系统自动在后台线程执行查询,通过Handler
将结果回调到主线程
线程行为对比表
调用方式 | 执行线程 | 是否阻塞UI | 推荐场景 |
---|---|---|---|
直接调用query() | 调用线程 | 可能阻塞 | 小型数据集/非UI线程调用 |
CursorLoader自动执行query | AsyncTask线程池 | 无阻塞 | 列表数据加载等标准场景 |
对疑问的代码执行流程验证
原生CursorLoader实现原理(简化版):
// Framework简化的Loader线程切换逻辑 public class CursorLoader extends AsyncTaskLoader<Cursor> { @Override public Cursor loadInBackground() { // 在AsyncTask线程执行 return getContext().getContentResolver().query(uri, ...); } @Override protected void onStartLoading() { forceLoad(); // 触发异步加载 } }
主线程验证代码:
// 验证调用线程的测试代码 new Handler(Looper.getMainLooper()).post(() -> { // 在主线程执行查询 Cursor cursor = getContentResolver().query(Carriers.CONTENT_URI, ...); Log.d("ThreadTest", "MainThread query: " + (Looper.myLooper() == Looper.getMainLooper())); }); // 输出结果:ThreadTest: MainThread query: true
代码实现
优化设想
用户打开界面,Activity初始化Loader,LoaderManager启动CursorLoader,CursorLoader在后台线程执行查询,通过ContentResolver调用TelephonyProvider的query方法,最终获取APN数据并返回给主线程php更新UI。
%% APN Settings界面数据加载时序图 sequenceDiagram participant User participant ApnSettingsActivity participant LoaderManager participant CursorLoader participant TelephonyProvider participant Database User->>ApnSettingsActivity: 启动APN设置界面 activate ApnSettingsActivity ApnSettingsActivity->>LoaderManager: initLoader(APN_LOADER_ID) LoaderManager->>CursorLoader: 创建新Loader实例 activate CursorLoader CursorLoader->>TelephonyProvider: 异步执行query() activate TelephonyProvider TelephonyProvider->>Database: 执行SQL查询 php activate Database Database-->>TelephonyProvider: 返回APN数据Cursor deactivate Database TelephonyProvider-->>CursorLoader: 返回查询结果 deactivate TelephonyProvider CursorLoader-->>LoaderManager: 交付结果 deactivate CursorLoader LoaderManager->>ApnSettingsActivity: onLoadFinished() ApnSettingsActivity->>ApnSettingsActivity: 更新UI列表 deactivate ApnSettingsActivity Note right of CursorLoader: 关键路径说明<br/>1. CursorLoader自动处理后台线程<br/>2. 数据库查询在AsyncTask线程池执行<br/>3. 结果通过Handler返回主线程
如下是优化方案的案例,但是原生逻辑并不是直接一个Activity
package com.android.settings.network.apn; // APN数据库查询不会阻塞主线程,通过CursorLoader机制实现 // 实际查询发生在AsyncTask线程(AsyncTask.THREAD_POOL_EXECUTOR) // 结果回调通过Handler机制返回主线程 // ApnSettings.Java 核心逻辑 public class ApnSettings extends PreferenceActivity implements LoaderManager.LoaderCallbacks<Cursor> { @Override protected void onCreate(Bundle savedInstanceState) { getLoaderManager().initLoader(APN_LOADER_ID, null, this); // 启动异步加载 } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new CursorLoader(this, Telephony.Carriers.CONTENT_URI, PROJECTION, null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { mAdapter.swapCursor(data); // 主线程更新UI } }
以上符合Android的最佳实践,即避免在主线程进行IO操作。
- ApnSettingsActivity使用了LoaderManager来初始化CursorLoader。
- 在onCreateLoader方法中创建了CursorLoader实例,参数包括ContentProvider的URI和查询参数。
- 当LoaderManager启动加载时,CursorLoader会在后台线程执行查询,完成后再通过onLoadFinished回调主线程更新UI。
AOSP
packages/apps/Settings/src/com/android/settings/network/apn/ApnSettings.java
/** Handle each different apn setting. */ public class ApnSettings extends Restricte编程dSettingsFragment implements Preference.OnPreferenceChangeListener { static final String TAG = "ApnSettings";
到此这篇关于Android 数据库查询对比(APN案例)的文章就介绍到这了,更多相关Android APN数据库查询内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论