5.UI线程和非UI线程的交互方式

来源:互联网 发布:企业级软件 编辑:程序博客网 时间:2024/05/09 08:12
转载请标明出处: 

http://blog.csdn.net/yujun411522/article/details/46041637
本文出自:【yujun411522的博客】


 这里说的交互方式应该指的是如何在非UI线程中修改UI线程中的组件。
     一般来说有三种方式:
     1.Activity.unOnUiThread(Runnable)
     
     如果当前线程是UI Thread,立马执行action.run方法;否则将Runnable发送到UI Thread的event 队列中。
    2. view.post(Runnable)
     
     将action加入到UI thread 的message queue。
     3.view.postDelayed(Runnable,long)同2一样,经过long时间之后将runnable发送给message queue。

     5.1UI线程
     当一个app启动时,系统为该app创建一个线程,成为主线程。这个主线程管理ui中的组件,包括分发事件重绘view等。因此也叫做UI Thread。我们还知道当默认情况下,系统并不为四大组件创建新的线程,都是运行在这个UI Thread之中,这种方式也称为单线程模型。所以一旦app中出现了耗时的工作(访问网络,写文件,读写数据库等等)有可能导致thread阻塞,如果这时系统无法及时分发事件,对于用户来说最直观的感受就是卡住了,而这时用户又不停的点击屏幕很有可能出现ANR。在设计app时要尽力避免出现ANR的出现,关于ANR请看5.2
     而且UI toolkit不是线程安全的,所以一定不能从子线程中操作ui,只能在ui thread中操作ui。对于android中的这种单线程模型有两条规则
     1.不要阻塞UI thread     
     2.不要在非UI Thread中操作UI
     如果在子线程中访问ui线程中的view,会报错:android.view.ViewRootImpl$CalledFromWrongThreadException:Onlythe original thread that created a view hierarchy can touch its views.
     但是现实中需要进行耗时操作的场景又非常多,如果需要耗时操作,然后更新UI怎么做呢(下载图片然后更新imageview)?我们先来按照它的规则来指定自己的方案:
     先按照规则1:不在UI Thread中做耗时操作,那么我们开启一个子线程来处理耗时操作,然后再更新view
     newThread(new Runnable(){
               public void run(){
                          //模拟耗时操作,比如上网下载图片
                         Bitmap b = loadBitmapFormWeb();      
                         //不能直接访问UI Thread        
                        
imageview.setBitmap(b);//在子线程中如果直接访问UI线程,会报错,不允许这么做。
//这里可以操作runOnUiThread,imageView.post(Runnable),imageView.postDelay();
                         imageview.post(new Runnable(){
                               imageview.setImageBitmap(b);
                         });
               }          
     }).start();
     但是这又不符合规则2,不能在非UI Thread中操作UI。这就很矛盾了,android系统在设计时已经考虑过这个问题,所以在android系统提供了几种方式在非UI Thread中访问UI线程:                
     Activity.runOnUiThread,View.post(),View.postDelayed()。
     这样的话,耗时操作在子线程中执行,UI 更新在UI Thread中执行,两者互不干扰。 
    如果子线程过多时,代码就会显得很臃肿、可读性不好。所以android 系统中集成了一个AsyncTask工具类用来包装Thread+Handler,这个类的主要在子线程运行耗时操作,然后在UI线程更新UI。同时使得代码的可读性好多了。关于AsyncTask有专门介绍。
     
     5.2 ANR
     在使用android系统的时候或多或少可能遇到过ANR(Application Not Responding), 类似的这种ANR对话框应该都见过。
         
     为什么会出现:android中的所有组件默认都是在UI线程中完成的,其中有ActivityManagerService(AMS)和WindowsManagerService(WMS)会监控一个动作的响应时间,如果在一定范围内对用户交互没有处理完毕就会出现ANR。比如说分派事件,假如某一段时间内UI 线程做了耗时操作,线程阻塞了,那么用户的一个触摸事件就可能来不得及处理,超过一定的时间就会导致ANR。当然这只是ANR其中的一种出现原因。对于用于来说一旦开始交互事件没有响应,就有可能不停的触摸。这更增加了ANR的几率,对于开发者来说开发过程中一定尽量避免ANR出现的可能。
     ANR出现有原因的,要同时满足几个条件:
     1.主线程也就是必须在UI线程中相应超时。所以我们可以将耗时操作放在子线程中执行来避免这个条件出现。
     2.超时,必须超过一定的时间限制,不同的ANR的时间限制不一样,下面有介绍。
     3.交互时间或者特定操作。不同的ANR触发的时间不一样。
     主要有三种ANR类型
     1.KeyDispatchTimeOut(5s),主线程对用户交互事件5s中没有处理完毕(这种情况最常见):当app获得用户交互事件时(按键,触摸),系统先获得这个交互事件然后分派到这个app,分派到app之后再分派给对应的View(比如button)。如果在5s内,该交互事件没有处理完成就会通过一系列的回调函数,最终到AMS中处理该处理keyDispatchTimeOut超时。     
     一定要注意:出现这种ANR前提是有用户交互事件,如果没有交互事件,就算是主线程阻塞主线程也不会ANR,因为这个时候就没有事件派发。可以测试,如果仅仅是Thread.sleep(60000),不做任何交互都不会出现ANR(看具体的设备和系统,我的荣耀3c畅玩版就不会出现,HTC的就出现)。
     2.BroadcastTimeOut(10s)BroadcastReceiver 中的onReceiver方法执行超过10s。          
     3.ServiceTimeout(20s),service生命周期方法中执行事件超过20s。
     2和3中不会产生对话框形式(和设备有关。我的华为荣耀3c畅玩版、htc都出现了)。这三种anr的出现影响最大的就是第一个,但是三者之间并不是单一出现的,有可能receiver中执行时阻塞,用户交互,系统没法及时处理该交互,出现第一种;如果receiver超时后又出现第二种。不管怎样,避免这三种操作就能最大程度的避免ANR的出现。
     出现ANR时,有两个输出文件要重视:一个就是系统输出的log tag,一个就是/data/anr/traces.txt,这两个文件使我们定位ANR的利器
     先看系统的log:
     //ANR 在哪个进程发生了ANR
      05-14 15:24:10.519: E/ActivityManager(499): ANR in com.example.servicedemo, time=1037238
     //reson指明原因
      05-14 15:24:10.519: E/ActivityManager(499): Reason: Broadcast of Intent { act=com.service.ACTION flg=0x10 cmp=com.example.servicedemo/.MyReceiver }
     //ago 指明ANR之前cup使用情况     
     05-14 15:24:10.519: E/ActivityManager(499): CPU usage from 4973ms to -1016msago:
     //later指明ANR之后cpu使用情况
     05-14 15:24:10.519: E/ActivityManager(499): CPU usage from 472ms to 993mslater:

     再来看traces.txt内容:(目录:/data/anr/traces.txt)
     ----- pid 5085 at 2015-05-14 15:24:09 -----
     Cmd line: com.example.servicedemo
     DALVIK THREADS:
     (mutexes: tll=0 tsl=0 tscl=0 ghl=0)
     "main" prio=5 tid=1 TIMED_WAIT
       | group="main" sCount=1 dsCount=0 obj=0x40ae3490 self=0x15cad58
       | sysTid=5085 nice=0 sched=0/0 cgrp=default handle=1074472136
       | schedstat=( 0 0 0 ) utm=17 stm=6 core=0
       at java.lang.VMThread.sleep(Native Method)
       at java.lang.Thread.sleep(Thread.java:1047)
       at java.lang.Thread.sleep(Thread.java:1029)
       at com.example.servicedemo.MyReceiver.onReceive(MyReceiver.java:19)
       
       这两个信息是如何输出的:在ams中的appNotResponding函数:
       synchronized (this) {
               //在某些情况下忽略ANR
            // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
            if (mShuttingDown) {
                Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
                return;
            } else if (app.notResponding) {
                Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
                return;
            } else if (app.crashing) {
                Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
                return;
            }
         //.....
          // Log the ANR to the main log.
        StringBuilder info = mStringBuilder;
        info.setLength(0);
        info.append("ANR in ").append(app.processName);
        if (activity != null && activity.shortComponentName != null) {
            info.append(" (").append(activity.shortComponentName).append(")");
        }
        info.append("\n");
        if (annotation != null) {
            info.append("Reason: ").append(annotation).append("\n");
        }
        if (parent != null && parent != activity) {
            info.append("Parent: ").append(parent.shortComponentName).append("\n");
        }
     //指定traces文件保存地址
     File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids);

     String cpuInfo = null;
        if (MONITOR_CPU_USAGE) {
            updateCpuStatsNow();
            synchronized (mProcessStatsThread) {
                cpuInfo = mProcessStats.printCurrentState(anrTime);
            }
               //anr之前cpu负载信息
            info.append(processStats.printCurrentLoad());
            info.append(cpuInfo);
        }
        //anr之后cpu负载信息 
        info.append(processStats.printCurrentState(anrTime));
     //将anr信息加入到dropBox中(管理log的工具)
     addErrorToDropBox("anr", app, activity, parent, annotation, cpuInfo, tracesFile, null);
     // Unless configured otherwise, swallow ANRs in background processes & kill the process.
     //读取用户配置,是否显示后台进程anr对话框。不显示就直接杀死
        boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
        synchronized (this) {
               //设置为后台不显示anr dialong
            if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
                Slog.w(TAG, "Killing " + app + ": background ANR");
                EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
                        app.processName, app.setAdj, "background ANR");
                Process.killProcessQuiet(app.pid);//杀死进程
                return;
            }

           // Bring up the infamous App Not Responding dialog,发送信息给mHandler,提示可以显示ANR对话框
            Message msg = Message.obtain();
            HashMap map = new HashMap();
            msg.what = SHOW_NOT_RESPONDING_MSG;
            msg.obj = map;
            map.put("app", app);
            if (activity != null) {
                map.put("activity", activity);
            }
            mHandler.sendMessage(msg);

          mHandler中handleMessage函数中相应的处理为:
           case SHOW_NOT_RESPONDING_MSG: {
                synchronized (ActivityManagerService.this) {                                              
                    Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
                            mContext, proc, (ActivityRecord)data.get("activity"));
                    d.show();//显示anr对话框           
                }            
            } break;
     
     如何避免:ANR产生的原因是因为在主线程中执行了耗时的操作,所以将耗时操作方法在子线程中执行,可以使用handler+thread 或者AsyncTask方式。
     同时为了给用户好的体验,也有几个建议:     
     1.执行耗时操作时,给用户ui提示,比如进度对话框
     2.游戏类app用子线程计算。
     3.应用程序的初始化,可以使用一个splash或者过场动画,这种使用的很多比如一开始打开app时显示一张欢迎图片(天天动听),这样既给用户app正在响应的感觉,同时可以执行耗时操作。
0 0
原创粉丝点击