有时,Android系统调用(例如ContentResolver.query(...))可能比正常时间长1000倍,例如,对于通常在~10毫秒内完成的单个调用,大约需要10秒。我的问题是如何识别这样的案例,理解它为什么会发生并解决问题。
以下示例是检索Android系统上的“用户个人资料”。在我的Nexus 6上,通常需要大约10毫秒才能完成。较旧的设备也同样快。偶尔虽然可以20秒以上。使用traceview,它显示几乎所有时间都花在“上下文切换”中:
我的设备异常拥有约7,000个联系人。对于我正在请求的“用户配置文件”,结果只有一行。它是使用Uri.withAppendedPath(Profile.CONTENT_URI, Data.CONTENT_DIRECTORY)
请求的 - 我认为这是一个常见的优化案例。目前,查询是在主线程上完成的,这可能使事情变得复杂...... Android文档提到“你应该在一个单独的线程上异步进行查询”。我将在未来迁移到那个,但我怀疑这是因为它之前工作得很好。
另一个令人惊讶的因素是奇怪的一致性。我在应用程序中有多个查询。对于一系列运行,“用户配置文件”将始终缓慢,然后再次开始快速,我无法重现该问题。同样,不是用户配置文件的其他联系人查询将很快,然后变慢。在过去六个月中,通过Nexus 5和Nexus 6设备快速查询了所有问题 - 这只是在上周出现的。
查询:
Uri uri = Uri.withAppendedPath(Profile.CONTENT_URI, Data.CONTENT_DIRECTORY),
Log.d(TAG, "getOwner: " + uri.toString());
Cursor cur = mResolver.query(
uri,
PROJECTION, // 6 columns
null,
null,
Data.IS_PRIMARY + " DESC");
Log.d(TAG, "getOwner: query count - " + cur.getCount());
日志输出:
// NOTE 18 second gap between log entries
19:20:33.134 D/ConnectActivity﹕ getOwner: URI: content://com.android.contacts/profile/data
19:20:51.779 D/ConnectActivity﹕ getOwner: query count - 1
我仍然觉得这种行为令人惊讶,并且不明白为什么会这样。
答案 0 :(得分:3)
调试和改进的级别(后两个专门针对慢速"上下文切换"):
在大多数情况下,查询正确的Uri,减少投影中的列数和缓存列索引是主要的改进:Getting name and email from contact list is very slow
此示例用于从用户配置文件中获取名称:
Cursor cur = mResolver.query(
// Uri will only return user profile rather than everything in contacts
Uri.withAppendedPath(Profile.CONTENT_URI, Data.CONTENT_DIRECTORY),
// Projection only has 2 columns to reduce the data returned
new String[] {
Data.MIMETYPE,
ContactsContract.Data.DISPLAY_NAME};
// Filter matches on specific mimetype to reduce result size
Data.MIMETYPE + "=?",
// Filter matches on specific mimetype to reduce result size
new String[]{StructuredName.CONTENT_ITEM_TYPE},
// No sorting may improve performance
null);
Android Documentation建议使用CursorLoader:
为了清楚起见,本节中的代码片段在" UI线程""上调用ContentResolver.query()。但是,在实际代码中,您应该在单独的线程上异步执行查询。一种方法是使用CursorLoader类,这在“加载器”指南中有更详细的描述。此外,代码行只是片段;他们没有显示完整的申请。
你应该仔细观察减速,以量化它,看看它是否只有原因的线索。 ContentResolver.query(...)调用通常需要10-100毫秒,并且该范围内的任何内容都可以如上所述进行改进,但是相当期待。在1-10秒的范围内,这可能是由上下文切换到其他进程引起的。当发生上下文切换时,ART(Android 5.0中的默认运行时)将输出一条日志行,指示上下文切换及其所需的时间:
Android' traceview可用于确定花费的时间。基本用法是包装您想要分析的方法,如下所示:
Debug.startMethodTracing("trace-name");
// Code to profile
Debug.stopMethodTracing();
接下来从设备中提取日志(我相信这是默认路径):
adb pull /storage/emulated/legacy/dmtrace.trace
Android Device Monitor可用于打开文件和查看。在Android Studio中,选择工具 - > Android - > Android设备监视器,然后打开该文件。通常,将实时和包含Cpu时间列重新排列到最左侧是有帮助的。这些显示了一个方法(以及它的子方法)所花费的时间作为cpu时间和实时(其中还包括阻止在app应用程序中用cpu时间测量的IO调用 - {{3 }})。最可疑的条目是 Incl Real Time 远远超过 Incl Cpu Time 。这意味着这意味着该方法需要很长时间,但在代码中没有做太多,所以它必须被应用程序进程之外的东西阻止。
你可以看到列表方法和每个的时间。从" 0(顶级)开始"点击三角形以查看"孩子"打开那个。每个孩子都会列出一个方法编号,您可以使用该编号在traceview中的其他位置查找该方法,并查看他们的子级,通过层次结构下降。寻找实时>>的案例包含Cpu时间,您经常会发现它以"(上下文切换)"结束。这是一个阻碍你的过程的长期阻塞事件。
维基百科more details:
在计算中,上下文切换是存储和恢复进程或线程的状态(上下文)的过程,以便可以在以后从同一点恢复执行。这使得多个进程可以共享单个CPU,并且是多任务操作系统的基本特性。构成上下文的内容由处理器和操作系统决定。
对于Android,这表示操作系统已选择保留应用程序的状态,将其从CPU中删除,以便它可以执行另一个进程。当上下文切换结束时,可以在CPU上恢复该过程并继续执行。