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

这里说的交互方式应该指的是如何在非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:Only
the 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正在响应的感觉,同时可以执行耗时操作。

时间: 2025-01-13 06:24:03

5.UI线程和非UI线程的交互方式的相关文章

SWT的UI线程和非UI线程

要理解UI线程,先要了解一下"消息循环"这个概念.链接是百度百科上的条目,简单地说,操作系统把用户界面上的每个操作都转化成为对应的消息,加入消息队列.然后把消息转发给对应的应用程序(一般来说,就是活动窗口),应用程序根据自己的逻辑处理这些消息. 如果应用程序处理某个消息事件的时候,用了很长的时间,这时候后续的消息无法及时得到处理,就会造成应用程序没有响应,也就是常说的"假死"状态. 所以,应用程序如果处理某个事件需要较长的时间,需要把这个操作放到一个另外的线程中进行

Android UI线程和非UI线程

UI线程及Android的单线程模型原则 当应用启动,系统会创建一个主线程(main thread). 这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,你的应用和Android的UI组件(components from the Android UI toolkit (components from the android.widget and android.view packages))发生交互. 所以main thread也叫UI thread也即UI线程. 系统不会为

关于 SWT 的UI线程和非UI线程

要理解UI线程,先要了解一下"消息循环"这个概念.链接是百度百科上的条目,简单地说,操作系统把用户界面上的每个操作都转化成为对应的消息,加入消息队列.然后把消息转发给对应的应用程序(一般来说,就是活动窗口),应用程序根据自己的逻辑处理这些消息. 如果应用程序处理某个消息事件的时候,用了很长的时间,这时候后续的消息无法及时得到处理,就会造成应用程序没有响应,也就是常说的"假死"状态. 所以,应用程序如果处理某个事件需要较长的时间,需要把这个操作放到一个另外的线程中进行

android脚步---如何看log之程序停止运行,和UI线程和非UI线程之间切换

经常运行eclipse时,烧到手机出现,“停止运行”,这时候得通过logcat查log了.一般这种情况属于FATAL EXCEPTION,所以检索FATAL 或者 EXCEPTION,然后往下看几行 例子: 11-26 16:18:17.949: E/AndroidRuntime(5363): FATAL EXCEPTION: Thread-19311-26 16:18:17.949: E/AndroidRuntime(5363): Process: com.scme.jiance, PID:

从头认识java-18.2 主要的线程机制(5)-守护线程与非守护线程

这一章节我们来讨论一下守护线程与非守护线程. 1.什么是守护线程?什么是非守护线程? 非守护线程:Java虚拟机在它全部非守护线程已经离开后自己主动离开. 守护线程:守护线程则是用来服务用户线程的,假设没有其它用户线程在运行,那么就没有可服务对象,也就没有理由继续下去. 2.同样点 大家都是线程.事实上能够互相切换 3.不同点:退出的时间点 退出的先后顺序: 非守护线程->守护线程->jvm 4.注意点: (1)设置守护线程须要在start之前,否在抛异常 package com.ray.ch

从头认识java-18.2 基本的线程机制(5)-守护线程与非守护线程

这一章节我们来讨论一下守护线程与非守护线程. 1.什么是守护线程?什么是非守护线程? 非守护线程:Java虚拟机在它所有非守护线程已经离开后自动离开. 守护线程:守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去. 2.相同点 大家都是线程,其实可以互相切换 3.不同点:退出的时间点 退出的先后顺序: 非守护线程->守护线程->jvm 4.注意点: (1)设置守护线程需要在start之前,否在抛异常 package com.ray.ch17;

Java中的守护线程和非守护线程(转载)

<什么是守护线程,什么是非守护线程> Java有两种Thread:“守护线程Daemon”(守护线程)与“用户线程User”(非守护线程). 用户线程:非守护线程包括常规的用户线程或诸如用于处理GUI事件的事件调度线程,Java虚拟机在它所有非守护线程已经离开后自动离开. 守护线程:守护线程则是用来服务用户线程的,比如说GC线程.如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去.(操作系统里面是没有所谓的守护线程的概念,只有守护进程一说,但是Java语言机制是构建在JVM

[Java基础] java的守护线程与非守护线程

最近重新研究Java基础知识,发现以前太多知识知识略略带过了,比较说Java的线程机制,在Java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) ,(PS:以前忽略了). 估计学过Unix开发但是没有细致学习Java的同学们会疑惑了,操作系统里面是没有所谓的守护线程的概念,只有守护进程一说,但是Java语言机制是构 建在JVM的基础之上的,意思是Java平台把操作系统的底层给屏蔽起来,所以它可以在它自己的虚拟的平台里面构造出对自己有利的机制,而语言或者说

守护线程与非守护线程的区别

守护线程与非守护线程 最近在看多线程的Timer章节,发现运用到了守护线程,感觉Java的基础知识还是需要补充. Java分为两种线程:用户线程和守护线程 所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分.因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程.反过来说,只要任何非守护线程还在运行,程序就不会终止. 守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机