android之handle
异步消息处理机制Handler : http://www.cnblogs.com/lyajs/p/5347990.html
Android 中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue 和Looper。
1. Message
Message 是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。(字段有what arg1 arg2 obj)
2. Handler
Handler 主要是用于发送和处理消息的。发送消息一般是使用Handler 的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。
3. MessageQueue
MessageQueue 是消息队列,它主要用于存放所有通过Handler 发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue
对象。
4. Looper
Looper 是每个线程中的MessageQueue 的管家,调用Looper 的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler 的handleMessage()方法中。每个线程中也只会有一个Looper 对象。
异步消息流程梳理:
1、主线程上创建一个Handler对象,并重写handleMessage()方法。
2、当子线程中需要UI操作,创建Message对象,并通过Handler将这条信息发送出去。
3、消息被添加到MessageQueue的队列中等待被处理、Looper会一直尝试从MessageQueue中取出待处理消息、分发会Handler的handleMessage()方法中。
**因为handler是在主线程中创建,所以handleMessage()方法中代码也会在主线程中运行,于是我们可以安心对UI操作了。
测试 结构图:
package com.example.multithreadind012; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; public class MainActivity extends Activity { private Button btn; private TextView tv; private ListView lv; private BaseAdapter adapter; private List<User> userList = new ArrayList<User>(); // Runnable是一个接口,不是一个线程,一般线程会实现Runnable。 private Runnable doInBackground1; private Runnable doInBackground2; // 1.跟着主线程走,可以碰UI // 2.能够接受子线程发送的消息(Message) // 子线程类本身不可以发信息 private Handler handler; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); System.out.println("UI_MainThread ~~~~ id:" + Thread.currentThread().getId()); // 模拟数据访问产生数据 for (int i = 0; i < 5; i++) { User u = new User(); u.setUsername("小明" + i); u.setSex("女" + i); userList.add(u); } tv = (TextView) findViewById(R.id.textView1); btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener() { public void onClick(View v) { // 1.访问数据库或者互联网(但会卡的) // 2.更新界面 Thread t1 = new Thread(doInBackground1); t1.start(); Thread t2 = new Thread(doInBackground2); t2.start(); } }); // BaseAdapter就Android应用程序中经常用到的基础数据适配器,它的主要用途是将一组数据传到像ListView、Spinner、Gallery及GridView等UI显示组件,它是继承自接口类Adapter, adapter = new BaseAdapter() { public int getCount() { return userList.size(); } public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = MainActivity.this.getLayoutInflater(); View view; if (convertView == null) { view = inflater.inflate(R.layout.item, null);// 创建 } else { view = convertView; // 复用 } TextView tv_username = (TextView) view .findViewById(R.id.username); TextView tv_sex = (TextView) view.findViewById(R.id.sex); tv_username.setText(userList.get(position).getUsername()); tv_sex.setText(userList.get(position).getSex()); return view; } public Object getItem(int position) { return null; } public long getItemId(int position) { return 0; } }; lv = (ListView) findViewById(R.id.listView1); // 为ListView绑定Adapter lv.setAdapter(adapter); handler = new Handler() { // 1.消息msg来自于子线程 // 2.消息可以多个,采用msg.what识别 // 3.处理消息,一般就会更新UI // 4.此方法可以参考onPostExecute public void handleMessage(Message msg) { super.handleMessage(msg); // what 是用来保存消息标示的 int msgwhat = msg.what; System.out.println("handler ~~~~ 已经收到消息,消息what:"+msgwhat+",id:" + Thread.currentThread().getId()); if (msgwhat == 1) { // 更新helloworld tv.setText("子线程让我更新" + msgwhat); } if (msgwhat == 2) { // 更新ListView adapter.notifyDataSetChanged(); } } }; // 子线程代码1 doInBackground1 = new Runnable() { public void run() { System.out.println("sub_Thread ~~~~ 子线程1启动,id:" + Thread.currentThread().getId()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } // 1.访问数据库或者互联网,不在UI进程,所以不卡 Message msg = new Message(); // 对消息一个识别号,便于handler能够识别 msg.what = 1; handler.sendMessage(msg); System.out.println("sub_Thread ~~~~ 子线程1已经发送消息给handler"); } }; // 子线程代码1 doInBackground2 = new Runnable() { public void run() { System.out.println("sub_Thread ~~~~ 子线程2启动,id:" + Thread.currentThread().getId()); try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } Message msg = new Message(); // 对消息一个识别号,便于handler能够识别 msg.what = 2; // handler.sendMessage(msg); handler.sendMessageDelayed(msg, 500); // 访问互联网,下载最新的,更新data,但不碰界面 for (User user : userList) { user.setSex("不男不女"); } System.out.println("sub_Thread ~~~~ 子线程2已经发送消息给handler"); } }; } }
package com.example.multithreadind012; import java.util.List; import android.os.AsyncTask; import android.widget.BaseAdapter; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.example.multithreadind012.R; public class MyTask extends AsyncTask { private BaseAdapter adapter; private List<User> userList; private MainActivity activity; public MyTask(MainActivity activity) { this.activity = activity; } // 1.所有耗时的代码,写到这里来(数据库、蓝牙、网络服务) // 2.绝对不能碰UI protected Object doInBackground(Object... params) { userList = (List<User>) params[0]; adapter = (BaseAdapter) params[1]; for (int i = 0; i < userList.size(); i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } userList.get(i).setUsername("小红" + i); userList.get(i).setSex("男" + i); publishProgress(i); } // 返回给前端 return "天气:22度"; } // 准备 protected void onPreExecute() { Toast.makeText(activity, "开始准备", Toast.LENGTH_SHORT).show(); } // 做完后执行 // onProgressUpdate()方法用于更新异步执行中,在主线程中处理异步任务的执行信息 protected void onPostExecute(Object result) { String r = result.toString(); TextView tv = (TextView) activity.findViewById(R.id.textView1); tv.setText("访问完成!" + r); } // 分步完成 protected void onProgressUpdate(Object... values) { // 0,1,2,3,4 int bar = Integer.parseInt(values[0].toString()); bar = (bar + 1) * 20; ProgressBar progressBar = (ProgressBar) activity .findViewById(R.id.progressBar1); // 更改进度条进度 progressBar.setProgress(bar); adapter.notifyDataSetChanged(); } }
package com.example.multithreadind012; public class User { private String username; private String sex; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } }
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.multithreadind012.MainActivity" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignTop="@+id/textView1" android:layout_marginRight="53dp" android:text="Button" /> <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/button1" android:layout_marginTop="84dp" > </ListView> <ProgressBar android:id="@+id/progressBar1" style="?android:attr/progressBarStyleHorizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/listView1" android:layout_alignRight="@+id/button1" android:layout_below="@+id/button1" android:layout_marginTop="28dp" /> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:id="@+id/username" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="60dp" android:textSize="45dp" /> <TextView android:id="@+id/sex" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="60dp" android:textSize="45dp" /> </LinearLayout>
====================================================================
handler之内存泄露
Handler也是造成内存泄露的一个重要的源头,主要Handler属于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的
,Handler引用Activity会存在内存泄露。
Handler 的生命周期与Activity 不一致
- 当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
- 当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
handler 引用 Activity 阻止了GC对Acivity的回收
- 在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
-
如果外部类是Activity,则会引起Activity泄露 。
当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
如何避免修?
- 使用显形的引用,1.静态内部类。 2. 外部类
- 使用弱引用 2. WeakReference
原文: http://www.cnblogs.com/devinzhang/archive/2011/12/30/2306980.html
知识点总结补充:
很多初入Android或Java开发的新手对Thread、Looper、Handler和Message仍然比较迷惑,衍生的有HandlerThread、java.util.concurrent、Task、AsyncTask由于目前市面上的书籍等资料都没有谈到这些问题,今天就这一问题做更系统性的总结。我们创建的Service、Activity以及Broadcast均是一个主线程处理,这里我们可以理解为UI线程。但是在操作一些耗时操作时,比如I/O读写的大文件读写,数据库操作以及网络下载需要很长时间,为了不阻塞用户界面,出现ANR的响应提示窗口,这个时候我们可以考虑使用Thread线程来解决。
对于从事过J2ME开发的程序员来说Thread比较简单,直接匿名创建重写run方法,调用start方法执行即可。或者从Runnable接口继承,但对于Android平台来说UI控件都没有设计成为线程安全类型,所以需要引入一些同步的机制来使其刷新,这点Google在设计Android时倒是参考了下Win32的消息处理机制。
1. 对于线程中的刷新一个View为基类的界面,可以使用postInvalidate()方法在线程中来处理,其中还提供了一些重写方法比如postInvalidate(int left,int top,int right,int bottom) 来刷新一个矩形区域,以及延时执行,比如postInvalidateDelayed(long delayMilliseconds)或postInvalidateDelayed(long delayMilliseconds,int left,int top,int right,int bottom) 方法,其中第一个参数为毫秒
2. 当然推荐的方法是通过一个Handler来处理这些,可以在一个线程的run方法中调用handler对象的 postMessage或sendMessage方法来实现,Android程序内部维护着一个消息队列,会轮训处理这些,如果你是Win32程序员可以很好理解这些消息处理,不过相对于Android来说没有提供 PreTranslateMessage这些干涉内部的方法。
3. Looper又是什么呢? ,其实Android中每一个Thread都跟着一个Looper,Looper可以帮助Thread维护一个消息队列,但是Looper和Handler没有什么关系,我们从开源的代码可以看到Android还提供了一个Thread继承类HanderThread可以帮助我们处理,在HandlerThread对象中可以通过getLooper方法获取一个Looper对象控制句柄,我们可以将其这个Looper对象映射到一个Handler中去来实现一个线程同步机制,Looper对象的执行需要初始化Looper.prepare方法就是昨天我们看到的问题,同时推出时还要释放资源,使用Looper.release方法。
4.Message 在Android是什么呢? 对于Android中Handler可以传递一些内容,通过Bundle对象可以封装String、Integer以及Blob二进制对象,我们通过在线程中使用Handler对象的sendEmptyMessage或sendMessage方法来传递一个Bundle对象到Handler处理器。对于Handler类提供了重写方法handleMessage(Message msg) 来判断,通过msg.what来区分每条信息。将Bundle解包来实现Handler类更新UI线程中的内容实现控件的刷新操作。相关的Handler对象有关消息发送sendXXXX相关方法如下,同时还有postXXXX相关方法,这些和Win32中的道理基本一致,一个为发送后直接返回,一个为处理后才返回 .
5. java.util.concurrent对象分析,对于过去从事Java开发的程序员不会对Concurrent对象感到陌生吧,他是JDK 1.5以后新增的重要特性作为掌上设备,我们不提倡使用该类,考虑到Android为我们已经设计好的Task机制,这里不做过多的赘述,相关原因参考下面的介绍:
6. 在Android中还提供了一种有别于线程的处理方式,就是Task以及AsyncTask,从开源代码中可以看到是针对Concurrent的封装,开发人员可以方便的处理这些异步任务。
Android Handler机制: http://blog.csdn.net/stonecao/article/details/6417364
Handler对Activity finish影响。
在开发的过程中碰到一个棘手的问题,调用Activity.finish函数Acitivity没有执行生命周期的ondestory函数,后面查找半天是因为有一个handler成员,
因为它有一个delay消息没有处理,调用Activity.finish,Activity不会马上destory,所以记得在Ativity finish前清理一下handle中的未处理的消息,这样Activity才会顺利的destory
Android异步消息处理(一): http://www.2cto.com/kf/201109/105866.html
什么是异步消息处理:
对于普通的线程来说,执行完run()方法内的代码后线程就结束了。而异步消息处理线程是指:线程启动后会进入一个无限循环体之中,每执行一次,从线程内部的消息队列中取出一个消息,并回调相应的消息处理函数,执行完一个消息后则继续循环。如果消息队列为空,线程会暂停(一般也就是我们调用休眠方法),直到消息队列中又新的消息。
异步消息处理特点:
从上面的描述可以看出,异步消息处理其实就是一种线程机制,只不过这种机制用的上的地方非常多,最后就单独提炼了“异步消息处理”这个名词。
异步消息处理的使用情况:
一般情况下,如果任务具有以下两个特点,就可以使用异步消息处理机制:
1.任务常驻内存(编程中体现就是run()方法中是无限循环),比如用于处理用户事件的任务。
2.任务需要根据外部传递的消息做不同的操作。
通用的实现异步消息处理机制的方式:
1.每个异步线程内部包含一个消息队列,用来缓存消息。
2.线程的执行中使用while(true)进行无限循环,循环体中从消息队列取出消息,并根据消息的来源,回调相应的消息处理函数(从这里可以看出:异步消息处理,消息的具体处理并不是异步消息处理机制负责的,异步消息处理机制只是负责转发消息给处理函数)
3.其他外部线程可以向本线程的消息队列发送消息,由于有两个或以上的线程访问消息队列,那么,消息队列内部的读写操作必须进行加锁。
Android中异步消息处理的实现方式:
在线程内部有一个或多个Handler对象,外部线程通过该Handler对象的引用向本线程发送异步消息,消息通过Handler对象加入消息队列(MessageQueue)。线程内部只有一个MessageQueue对象,线程的run()方法从MessageQueue中读取消息,并回调Handler对象中的回调函数handleMessage()处理消息。
在编程中应该注意的问题:
1.由于异步消息处理机制的一个重要环节是MessageQueue,所以,在发送消息之前,必须确定MessageQueue已经创建。在android中,给应用程序员提供的创建MessageQueue的接口是:Looper.prepare()。
2.由于异步消息处理机制是一个循环线程,而循环的启动也是由程序员控制的,所以,在你要处理消息之前,应该启动循环。在android中,给应用程序员提供的启动循环的接口是:Looper.loop()。
3.细心的朋友可能发现,在activity中使用Handler发送,处理消息时,并没有上面两步,其实是系统帮我们做了的。在创建Activity之前,系统就会为Activity创建一个异步消息处理线程。
Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系: http://blog.csdn.net/lmj623565791/article/details/38377229
Android Handler 异步消息处理机制的妙用 创建强大的图片加载类: http://blog.csdn.net/lmj623565791/article/details/38476887