笔记。。。。。。。。。
Android的消息机制(Android消息机制是为了解决在子线程中无法访问UI的矛盾。)
描述:Android应用程序是通过消息来驱动的。Handler机制主要运用
1.)发送消息,在不同的线程间发送消息,使用的方法为sendXXX();
handler.sendEmptyMessage(int);//发送一个空的消息bb
handler.sendMessage(Message);//发送消息,消息中可以携带参数
handler.sendMessageAtTime(Message, long);//未来某一时间点发送消息
handler.sendMessageDelayed(Message, long);//延时Nms发送消息
2.)计划任务,在未来执行某任务,使用的方法为postXXX();
handler.post(Runnable);//提交计划任务马上执行
handler.postAtTime(Runnable, long);//提交计划任务在未来的时间点执行
handler.postDelayed(Runnable, long);//提交计划任务延时Nms执行
Handler机制扩展(推荐使用)
1.) Activity.runOnUiThread(Runnable)
2.)View.post(Runnable)
3.)使用AsyncTask代替Thread
1.为什么不允许在非主线程访问UI呢? 这是因为Android的UI控件不是线程安全的。并且UI访问没有锁机制,并发访问会导致控件处于不可预期的状态。
2.那为什么不对UI访问加上锁机制呢?
(1)这显然会让UI访问的逻辑变得极其复杂;
(2)锁机制自然会降低效率;
(3)锁机制还会阻塞某些进程的执行。
MessageQueue
采用以单链表为数据存储结构的消息列表。对外提供插入(enqueueMessage)和读取(next)。读取本身附带删除操作。单链表结构在插入和删除上比较有优势。读取是一个无限循环,如果消息队列中没有消息就阻塞,当有新消息的时候就返回这条消息,并将其从单链表中删除。
Looper
loop()方法是一个死循环,会调用消息队列中的next()方法,next是一个阻塞操作,只有next返回了新消息,Looper才会处理这条消息。跳出looper死循环的唯一条件是MessageQueue的读取方法next()返回null。当looper的quit()方法被调用时,消息队列就会被标记为退出状态,她的next()方法也就返回了null。所以looper必须退出,否则loop()方法会一直执行。
1.如何为一个线程创建Looper
(1)UI线程默认就会初始化Looper.
(2)子线程默认没有Looper,Handler创建钱必须手动创建Looper,否则会报错。
Looper.prepare();创建一个Looper Looper.loop();开启消息循环
(3)使用HandlerThread继承自Thread,和普通的thread不同的是其内部已经封装了Looper.
Handler
主要负责消息的发送和接收。
注:handler.post()和handler.sendMessage()原理都是封装成了Message。
##整个handler机制的原理
这篇文章写的很详细可以多看几遍:http://blog.csdn.net/ly502541243/article/details/52062179/
特别说明:
1.handler造成内存泄漏的原因 (Handler mHandler=new Handler)
在java中非静态内部类和匿名内部类都会隐式持有当前类的外部引用,由于Handler是非静态内部类所以其持有当前Activity的隐式引用,如果Handler没有被释放,其所持有的外部引用也就是Activity也不可能被释放,当一个对象一句不需要再使用了,本来该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏OOM。
2.解决方法
/**
* 主线程接收消息,更新UI
*/
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg != null) {
String message = "";
switch (msg.what) {
case 1:
message = (String) msg.obj;
//更新UI
Log.i("TAG", "请求成功--接收到的子线程消息=" + message);
break;
case 2:
Bundle bundle = msg.getData();//同Bundle bundle = (Bundle) msg.obj; 对应使用
String name = bundle.getString("name");
int age = bundle.getInt("age");
Log.i("TAG", "请求成功--接收到的子线程消息=" + "name=" + name + ",age=" + age);
break;
case 3:
message = (String) msg.obj;
Log.i("TAG", "请求成功--接收到的子线程消息=" + message);
break;
}
} else {
Log.i("TAG", "空消息!!!");
}
}
};
使用handler更新UI的几种常见方式:
方法一:
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作,可以去请求网络。。。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发消息给主线程
// Message message=new Message();
// message.what=1;//此条消息的标记,同一个handler可能会发送很多条消息,使用此标记进行区分
// message.arg1=10;//若仅仅传递一个整数,可以使用arg1
// message.obj="这是来自子线程的handlerMessage消息";//message要携带的数据
// handler.sendMessage(message);//发带有数据的消息给主线程
// handler.sendEmptyMessage(1);//发空消息
// handler.sendEmptyMessageDelayed(1,1000);//延迟1秒发消息
//为了节省新new对象的开销,最好使用obtainMessage()
Message msg = handler.obtainMessage(1);
msg.obj = "这是来自子线程方法1的handlerMessage消息";
msg.sendToTarget();
}
}).start();
方法二:
//法2,
handler.postDelayed(new Runnable() {
@Override
public void run() {
//耗时操作
Message msg = handler.obtainMessage(2);
Bundle bundle = new Bundle();
bundle.putString("name", "yhy");
bundle.putInt("age", 26);
msg.setData(bundle);//同msg.obj=bundle;
msg.sendToTarget();
}
}, 3000);
方法三:
new Thread("thread2") {
@Override
public void run() {
super.run();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//View特有的方法,可以在子线程中更新UI
text.post(new Runnable() {
@Override
public void run() {
text.setText("方法一更新UI啦》》》》");
Log.i("TAG", "方法一更新UI啦》》》》");
}
});
}
}.start();
方法四:
//方法二,注:1.用view.getContext()可以得到上下文。
//2.跳过context直接用new Activity().runOnUiThread(Runnable action)来切换到主线程。
CurrentCircleActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
//此时已经在主线程中了,可以更新UI了
text.setText("方法二更新UI啦。。。。。");
Log.i("TAG", "方法二更新UI啦。。。。。");
}
});
方法五:
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过Looper.getMainLooper()方法,已经处在主线程中,可以更新UI了
Handler mHandler = new Handler(Looper.getMainLooper());
mHandler.post(new Runnable() {
@Override
public void run() {
text.setText("更新UI啦");
Log.i("TAG", "更新UI啦");
}
});
}
}).start();
方法六:
/**
* 该类中方法的执行顺序依次为:onPreExecute,doInBackground,onPostExectue
* 3秒更新进度条操作
*/
private class MyAsyncTask extends AsyncTask<String, Integer, String> {
/**
* 在主线程中执行
* 在execute被调用后首先执行
* 一般用来在执行后台任务前对UI做一些标记
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.i("TAG", "onPreExecute...");
}
/**
* 子线程中执行,执行一些耗时操作,关键方法
* 在执行的过程哄可以调用publishprogress()来更新进度信息
*
* @param strings
* @return
*/
@Override
protected String doInBackground(String... strings) {
Log.i("TAG", "doInBackground....");
int count = 0;
for (int i = 0; i < 10; i++) {
try {
count++;
publishProgress((count % 100) * 10);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "耗时任务执行完成";
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressbar.setProgress(values[0]);
Log.i("TAG", "onProgressUpdate..." + values[0] + "%");
}
/**
* 在主线程中,当后台操作结束时,此方法会被调用
* 计算结果将作为参数传递到此方法中,直接将结果显示在UI上
*
* @param s
*/
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.i("TAG", "onPostExecute..." + "子线程执行结束,结果为=" + s);
}
/**
* 在主线程中执行,当异步任务取消后,会回调该方法。在该方法内可以更新UI
*/
@Override
protected void onCancelled() {
super.onCancelled();
progressbar.setProgress(0);
Log.i("TAG", "onCancelled...");
}
@Override
protected void onCancelled(String s) {
super.onCancelled(s);
Log.i("TAG", "onCancelled..." + s);
}
}
原理:
1.android的消息机制主要指的是handler的运行机制,以及底层的messgeQueue和Lopper的工作过程。Handdler的主要作用是负责线程间通信。在网络请求完成后,通过handler告诉UI线程来更新UI,并传递数据。
(1)主线程可以默认使用handler的原因是,因为主线程ActivityThread被创建时就会默认初始化Looper,而子线程没有,需要我们自己创建。 (2)系统为什么不允许在子线程中访问UI呢?这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问会导致UI控件处于不可预测的状态。那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:首先,加上锁机制会让UI的访问逻辑变得复杂;其次,锁机制会减低UI的访问效率,因为锁机制会阻塞某些线程的执行。鉴于以上两点,最简单高效的方法就是使用单线程模型来处理UI操作。只需要通过Handler来切换一下UI访问的执行线程即可。2.ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类。(Looper的作用域是线程,并且不同的线程有不同的Looper,那么通过ThreadLocal就可以轻松的实现Looper在线程中的存取)从ThreadLocal的get和set方法中可以看出,它们操作的都是当前线程的localValues对象的table数组,因此不同线程中访问同一个LocalThread的get和set方法,它们对LocalThread的读写操作都位于各自线程的内部,所以ThreadLocal可以在多线程中互不干扰的存储和修改数据。
3.消息队列MessageQueue的工作原理 MessaeQueue主要包含两个操作:插入enqueueMessage()和读取next()(注:其中读取的本身伴随着删除操作)。MessageQueue实际上是以单链表数据结构来维护消息列表的。
4.Looper的工作原理
在Android的消息机制中,Looper主要负责不停的循环查看MessageQueue中是否有新消息,如果有则立刻处理,没有则一直阻塞在那里。
handler创建时会采用当前线程的Looper来构建内部消息循环系统,如果当前线程没有Looper,则需要我没收到去创建(创建方式:1.Looper.prepare();并调用Looper.loop();开启消息循环。只有调用了loop方法消息循环系统才真正的起作用。2.可以通过Looper.getMainLooper();在任何地方获取主线程的Looper。另外手动创建Looper后,在不需要的时候记得使用quitSafely()退出Looper),否则会报错。
demo:代码
package com.yhy.testviewdemo.activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.yhy.testviewdemo.R;
import com.yhy.testviewdemo.bean.PieData;
import com.yhy.testviewdemo.view.PieView;
import java.util.ArrayList;
/**
* 自定义圆状图
*/
public class CurrentCircleActivity extends AppCompatActivity implements View.OnClickListener{
private TextView text;
private ProgressBar progressbar;
private MyAsyncTask myAsyncTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);//去掉标题
setContentView(R.layout.activity_current_circle);
text = (TextView) findViewById(R.id.text);
text.setOnClickListener(this);
progressbar = (ProgressBar) findViewById(R.id.progressbar);
sendMeaasge();
upteUI();
myAsyncTask = new MyAsyncTask();
myAsyncTask.execute();//执行异步请求
}
/**
* 主线程接收消息,更新UI
*/
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg != null) {
String message = "";
switch (msg.what) {
case 1:
message = (String) msg.obj;
//更新UI
Log.i("TAG", "请求成功--接收到的子线程消息=" + message);
break;
case 2:
Bundle bundle = msg.getData();//同Bundle bundle = (Bundle) msg.obj; 对应使用
String name = bundle.getString("name");
int age = bundle.getInt("age");
Log.i("TAG", "请求成功--接收到的子线程消息=" + "name=" + name + ",age=" + age);
break;
case 3:
message = (String) msg.obj;
Log.i("TAG", "请求成功--接收到的子线程消息=" + message);
break;
}
} else {
Log.i("TAG", "空消息!!!");
}
}
};
/**
* 发送消息
*/
public void sendMeaasge() {
//法1,
new Thread(new Runnable() {
@Override
public void run() {
//耗时操作,可以去请求网络。。。
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//发消息给主线程
// Message message=new Message();
// message.what=1;//此条消息的标记,同一个handler可能会发送很多条消息,使用此标记进行区分
// message.arg1=10;//若仅仅传递一个整数,可以使用arg1
// message.obj="这是来自子线程的handlerMessage消息";//message要携带的数据
// handler.sendMessage(message);//发带有数据的消息给主线程
// handler.sendEmptyMessage(1);//发空消息
// handler.sendEmptyMessageDelayed(1,1000);//延迟1秒发消息
//为了节省新new对象的开销,最好使用obtainMessage()
Message msg = handler.obtainMessage(1);
msg.obj = "这是来自子线程方法1的handlerMessage消息";
msg.sendToTarget();
}
}).start();
//法2,
handler.postDelayed(new Runnable() {
@Override
public void run() {
//耗时操作
Message msg = handler.obtainMessage(2);
Bundle bundle = new Bundle();
bundle.putString("name", "yhy");
bundle.putInt("age", 26);
msg.setData(bundle);//同msg.obj=bundle;
msg.sendToTarget();
}
}, 3000);
//发一条空消息
new Thread("thread1") {
@Override
public void run() {
super.run();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = Message.obtain();
message.obj = "发一条空消息";
handler.sendEmptyMessage(1);
}
}.start();
}
/**
* 更新UI
*/
private void upteUI() {
//方法一
new Thread("thread2") {
@Override
public void run() {
super.run();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//View特有的方法,可以在子线程中更新UI
text.post(new Runnable() {
@Override
public void run() {
text.setText("方法一更新UI啦》》》》");
Log.i("TAG", "方法一更新UI啦》》》》");
}
});
}
}.start();
//方法二,注:1.用view.getContext()可以得到上下文。
//2.跳过context直接用new Activity().runOnUiThread(Runnable action)来切换到主线程。
CurrentCircleActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
//此时已经在主线程中了,可以更新UI了
text.setText("方法二更新UI啦。。。。。");
Log.i("TAG", "方法二更新UI啦。。。。。");
}
});
//方法三,
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过Looper.getMainLooper()方法,已经处在主线程中,可以更新UI了
Handler mHandler = new Handler(Looper.getMainLooper());
mHandler.post(new Runnable() {
@Override
public void run() {
text.setText("更新UI啦");
Log.i("TAG", "更新UI啦");
}
});
}
}).start();
}
@Override
public void onClick(View v) {
//取消异步请求,回调AsyncTask中的onCancelled()方法
myAsyncTask.cancel(true);
}
/**
* 该类中方法的执行顺序依次为:onPreExecute,doInBackground,onPostExectue
* 3秒更新进度条操作
*/
private class MyAsyncTask extends AsyncTask<String, Integer, String> {
/**
* 在主线程中执行
* 在execute被调用后首先执行
* 一般用来在执行后台任务前对UI做一些标记
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
Log.i("TAG", "onPreExecute...");
}
/**
* 子线程中执行,执行一些耗时操作,关键方法
* 在执行的过程哄可以调用publishprogress()来更新进度信息
*
* @param strings
* @return
*/
@Override
protected String doInBackground(String... strings) {
Log.i("TAG", "doInBackground....");
int count = 0;
for (int i = 0; i < 10; i++) {
try {
count++;
publishProgress((count % 100) * 10);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "耗时任务执行完成";
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressbar.setProgress(values[0]);
Log.i("TAG", "onProgressUpdate..." + values[0] + "%");
}
/**
* 在主线程中,当后台操作结束时,此方法会被调用
* 计算结果将作为参数传递到此方法中,直接将结果显示在UI上
*
* @param s
*/
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
Log.i("TAG", "onPostExecute..." + "子线程执行结束,结果为=" + s);
}
/**
* 在主线程中执行,当异步任务取消后,会回调该方法。在该方法内可以更新UI
*/
@Override
protected void onCancelled() {
super.onCancelled();
progressbar.setProgress(0);
Log.i("TAG", "onCancelled...");
}
@Override
protected void onCancelled(String s) {
super.onCancelled(s);
Log.i("TAG", "onCancelled..." + s);
}
}
}
运行结果:
06-12 16:09:29.071 9905-9905/com.yhy.testviewdemo I/TAG: 方法二更新UI啦。。。。。
06-12 16:09:29.071 9905-9905/com.yhy.testviewdemo I/TAG: onPreExecute...
06-12 16:09:29.071 9905-10358/com.yhy.testviewdemo I/TAG: doInBackground....
06-12 16:09:29.077 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...10%
06-12 16:09:29.095 9905-9905/com.yhy.testviewdemo I/TAG: width=: 1080--oldw=0
06-12 16:09:29.095 9905-9905/com.yhy.testviewdemo I/TAG: height=: 1635--oldh=0
06-12 16:09:31.072 9905-9905/com.yhy.testviewdemo I/TAG: 请求成功--接收到的子线程消息=null
06-12 16:09:31.073 9905-9905/com.yhy.testviewdemo I/TAG: 更新UI啦
06-12 16:09:32.072 9905-9905/com.yhy.testviewdemo I/TAG: 请求成功--接收到的子线程消息=这是来自子线程方法1的handlerMessage消息
06-12 16:09:32.073 9905-9905/com.yhy.testviewdemo I/TAG: 方法一更新UI啦》》》》
06-12 16:09:32.073 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...20%
06-12 16:09:32.073 9905-9905/com.yhy.testviewdemo I/TAG: 请求成功--接收到的子线程消息=name=yhy,age=26
06-12 16:09:35.074 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...30%
06-12 16:09:38.074 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...40%
06-12 16:09:41.075 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...50%
06-12 16:09:44.075 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...60%
06-12 16:09:47.075 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...70%
06-12 16:09:50.076 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...80%
06-12 16:09:53.076 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...90%
06-12 16:09:56.076 9905-9905/com.yhy.testviewdemo I/TAG: onProgressUpdate...100%
06-12 16:09:56.852 22134-22134/? W/KeyguardUpdateMonitor: ChargingSpeed Wattage: -1 ST: 5000000 --> 7500000
06-12 16:09:59.076 9905-9905/com.yhy.testviewdemo I/TAG: onPostExecute...子线程执行结束,结果为=耗时任务执行完成
点击TextView执行onClick()方法后执行结果:(ps:取消异步请求操作)
06-12 16:14:10.388 10497-10497/com.yhy.testviewdemo I/TAG: 方法二更新UI啦。。。。。
06-12 16:14:10.389 10497-10497/com.yhy.testviewdemo I/TAG: onPreExecute...
06-12 16:14:10.389 10497-10539/com.yhy.testviewdemo I/TAG: doInBackground....
06-12 16:14:10.403 10497-10497/com.yhy.testviewdemo I/TAG: width=: 1080--oldw=0
06-12 16:14:10.403 10497-10497/com.yhy.testviewdemo I/TAG: height=: 1635--oldh=0
06-12 16:14:10.404 10497-10497/com.yhy.testviewdemo I/TAG: onProgressUpdate...10%
06-12 16:14:12.389 10497-10497/com.yhy.testviewdemo I/TAG: 请求成功--接收到的子线程消息=null
06-12 16:14:12.396 10497-10497/com.yhy.testviewdemo I/TAG: 更新UI啦
06-12 16:14:13.390 10497-10497/com.yhy.testviewdemo I/TAG: 请求成功--接收到的子线程消息=这是来自子线程方法1的handlerMessage消息
06-12 16:14:13.391 10497-10497/com.yhy.testviewdemo I/TAG: onProgressUpdate...20%
06-12 16:14:13.391 10497-10497/com.yhy.testviewdemo I/TAG: 请求成功--接收到的子线程消息=name=yhy,age=26
06-12 16:14:13.392 10497-10497/com.yhy.testviewdemo I/TAG: 方法一更新UI啦》》》》
06-12 16:14:38.392 10497-10497/com.yhy.testviewdemo I/TAG: onCancelled...
06-12 16:14:38.392 10497-10497/com.yhy.testviewdemo I/TAG: onCancelled...耗时任务执行完成
学习博客:点击打开链接https://blog.csdn.net/da_caoyuan/article/details/52931007