您的位置:首页 > 移动开发

Android - 《Keeping Your App Responsive》

2016-09-21 22:21 218 查看
翻译自Android官方文档。保持你的App有响应在这个世界上,写出能跑赢所有性能测试的代码是可能,但是在重大的周期里,仍然让人感觉缓慢、卡顿或者卡住,或者花太多时间来处理输入。这种能发生在你的App的响应能力的最坏的事情是一个“ANR”Dialog。在Android系统里,系统守卫依靠在一个周期里确认App没有响应能力,从而显示一个对话框来告诉你,你的App已经停止响应,就像图1显示的那个Dialog那样。图1:一个显示给用户的ANR窗口在这时,你的App已经在一个足够的时间周期里不响应,所以系统提供给用户一个退出App的选项。设计响应性好的App,保证系统不现实ANR窗口给用户,这是非常关键的。这篇文档描述Android如果判断一个App是否是无响应,并且提供一个指导,用来帮助你保证你的App一直有响应。什么会触发ANR?通常,如果一个App不响应用户输入,那么Android系统就会显示一个ANR窗口。例如,一个App的UI线程阻塞在IO操作上,那么系统就不能处理到来的输入事件。又或许App在UI线程里花费了太多时间来构建一个精细的内存结构,或者在游戏中计算下一步的移动。确认这些计算是有效率的总是很重要的,但即使是最有效率的代码,仍然要花时间来运行。在一些情景下,在你执行一个尽可能长时间的操作时,你不应该在UI线程做这个工作,而是用创建一个工作线程来代替,这个工作线程能做更多的事情。这能保证UI线程(驱动UI时间循环)能一直运行,并且能阻止系统认定你的代码已经卡死。因为这些线程通常是一个完整的类,所以你能将响应性想成一个类问题(比较这个代码和基础代码的效率,是方法层级关心的事情)。在Android系统里,app的响应性是由ActivityManager和WindowManager来监测的。Android将会为检测到以下条件中的一个的特定App显示一个ANR窗口:·不响应输入事件5秒以内。·一个广播接收器不能完成处理10秒以内。怎样避免ANR?AndroidApp通常完全运行在单个线程上,默认是UI线程或者主线程。这意味着你的App在UI线程内做的需要花很长时间完成的任何事情都会触发ANR窗口,因为你的App不会给自己一个机会来处理输入事件或广播Intent。因此,任何运行在UI线程上的方法应该尽量的在线程上做一些微小的工作。特别是,Activity应该尽可能小的在关键函数:OnCreate、OnResume里做设置。可能长时间的操作,比如网络连接,数据库操作,或者计算成本昂贵的计算,比如改变位图大小应该放在工作者线程内(或者在操作数据库的情况下,通过一个异步请求来操作)。为长时间操作创建工作者线程的最有效的方法是使用AsyncTask类。简单的扩展自AsyncTask类,并且实现doInBackground()
方法来执行工作。为了发送进度变化给用户,你能调用
publishProgress()方法,系统调用onProgressUpdate()
回调函数。在你的
onProgressUpdate()
方法(这个方法运行在UI线程中)实现,你能通知用户。例如:
privateclassDownloadFilesTaskextendsAsyncTask<URL,Integer,Long>{//Dothelong-runningworkinhereprotectedLongdoInBackground(URL...urls){intcount=urls.length;longtotalSize=0;for(inti=0;i<count;i++){totalSize+=Downloader.downloadFile(urls[i]);publishProgress((int)((i/(float)count)*100));//Escapeearlyifcancel()iscalledif(isCancelled())break;}returntotalSize;}//ThisiscalledeachtimeyoucallpublishProgress()protectedvoidonProgressUpdate(Integer...progress){setProgressPercent(progress[0]);}//ThisiscalledwhendoInBackground()isfinishedprotectedvoidonPostExecute(Longresult){showNotification("Downloaded"+result+"bytes");}}
为了执行这个工作线程,创建一个实例,并且调用execute()方法:
newDownloadFilesTask().execute(url1,url2,url3);
你应该会想要创建自己的Thread或者HandlerThread类,虽然它们比AsyncTask更复杂。如果你那么做了,你应该设置那个线程的优先级为“background”,通过调用Process.setThreadPriority()[code]方法和传递
THREAD_PRIORITY_BACKGROUND标识。如果你不想要走设置低线程优先级这条路,那么你的线程就会一直减缓你的App的运行速度,因为默认你的线程和UI线程在同一优先级。[/code]
如果你实现了Thread和HandlerThread,请确认,当UI线程等待工作线程完成时,不会被阻塞,不要在UI线程里调用Thread.wait(),Thread.sleep()。替代UI线程阻塞等待工作者线程完成的方法是,你的主线程应该提供一个Handler给其他线程,其他线程可以用Handler来回传工作者线程完成消息。用这种方法来设计你的App,将会允许你的UI线程保留对输入事件的响应性,并且因此避免因输入事件5秒超时引起的ANR对话框。
广播接收器的特殊规则着重于广播接收器会这么做:小,分散大量的后台工作,比如保存一个设置,或者注册一个Notification。所以,就像调用UI线程的其他方法那样,App应该避免在一个广播接收器进行潜在的长时间操作或计算。而是通过工作线程来替代这样的任务。如果一个潜在的长时间的Action需要被用来响应一个Intent广播,你的应用应该开启一个IntentService。
小技巧:你能通过
StrictMode
模式来帮助你找到潜在的长时间操作,比如网络连接、数据库操作这种你可能很意外的在你的主线程里做的动作。
强化响应能力
通常,在一个App上,100到200毫秒是一个极限,超过这个极限用户会感受到缓慢。同样地,这里有一些额外的小技巧,让你应该做什么来避免ANR,并且让你的App看起来像是响应用户了。
·如果你的App正在后台做一些工作,并且还响应用户输入,显示一个进度条是有用的(比如在你的UI里添加一个进度条控件)。
·特别对于游戏,将计算移动放到工作线程里。
·如果你的App有一个很耗时的初始化设置阶段,考虑显示一个启动画页,或者尽可能快的渲染一个主View,表明加载正在进行并且正在异步填充信息。在其他的Case下,你也总得指示出正在进行中的样子,以免让用户感觉App已经卡死。
StrictMode说明StrictMode是一个开发工具,用来检测一些你可能意外做的事情,并且将你的注意力带到它们身上,那样你就可以修理它们了。StrictMode最常用来在你的App的主线程里捕获意外的磁盘或网络访问动作,这个主线程就是UI动作被接收的地方,动画播放的地方。保持磁盘和网络操作远离主线程,将使App更顺畅,更有响应性。通过保持你的App的主线程的响应,你就能阻止ANR窗口显示给用户。、注意:虽然Android设备的磁盘经常在闪存上,很多设备运行一个并发性有限的文件系统在内存上。下面这个是个经常的情况,几乎所有磁盘访问都很快,但当某一个IO在后台的其他进程里发生意外,就会出奇的慢。如果可能,最好的假设就是这样的事情并不快。举例代码,从早期启用这样的功能,在你的Application、Activity,或其他App的组件的OnCreate函数:
publicvoidonCreate(){if(DEVELOPER_MODE){StrictMode.setThreadPolicy(new[code]StrictMode.ThreadPolicy.Builder
().detectDiskReads().detectDiskWrites().detectNetwork()//or.detectAll()foralldetectableproblems.penaltyLog().build());StrictMode.setVmPolicy(new
StrictMode.VmPolicy.Builder
().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().penaltyLog().penaltyDeath().build());}super.onCreate();}[/code]你能决定,当一个违规被检测到能发生什么。例如,使用
penaltyLog()函数,你能观察到adblogcat输出,在你用你的App来看发生的违规时。
如果你发现你感觉有问题的违规,这里有各种各样的工具来帮你解析它们:threads,
Handler
,
AsyncTask
,
IntentService
,等等。但是不要感觉强迫要修改StrictMode找到的每个事物。尤其是,很多磁盘访问的Case经常是必要的,在正常的Activity生命周期里。用StrictMode来找你意外做的事情。把网络请求放在UI线程几乎总是有问题的。注意:StrictMode不是一个安全的机制,并且不保证找到所有磁盘和网络访问。在做binder调用时,在它传递它的状态通过进程边界时,基本上它一直就是最好的机制。尤其是,从JNI开始的磁盘和网络调用不是必定会触发StrictMode模式。未来的Android版本有可能会捕获更多的操作,所以你就不再离开StrictMode模式,它会被启动在分布在Googleplay的App里。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  UI Thread Responsive ANR