这是我参与「第四届青训营 」笔记创作活动的第7天
Android的消息机制-Handler机制
一、由来
由于在android开发规范的限制,我们不能在子线程更新UI,但是又需要在子线程处理耗时逻辑防止主线程被阻塞造成ANR,所以就有了Handler机制来切线程
不能在子线程更新UI的原因
UI线程不是线程安全的,多个线程操作UI可能会造成UI的不可预见状态
系统不对UI线程加锁机制的原因
- 锁机制会导致访问UI效率的降低,因为锁机制会阻塞一些线程的执行
- 锁机制会使UI访问的逻辑变得复杂
二、ThreadLocal
1、ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
2、一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如对于Handler来说,它需要 获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存 取。如果不采用ThreadLocal,那么系统就必须提供一个全局的哈希表供Handler查找指定线程的Looper,这样一来就必须提供一个类似于LooperManager的类 了,但是系统并没有这么做而是选择了ThreadLocal,这就是ThreadLocal的好处
3、ThreadLocal另一个使用场景是复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以 及代码入口的多样性,在这种情况下,我们又需要监听器能够贯穿整个线程的执行过程,这时就可以采用ThreadLocal让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。如果不采用ThreadLocal,那么可能是如下两种方法:第一种方法是将监听器通过参数的形式在函数调用栈中进行传递,第二种方法就是将监听器作为静态变量供线程访问。上述这两种方 法都是有局限性的。第一种方法的问题是当函数调用栈很深的时候,通过函数参数来传递监听器对象这几乎是不可接受的,这会让程序的设计看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如同时有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有10个线 程在并发执行呢?提供10个静态的监听器对象?这显然是不可思议的,而采用ThreadLocal,每个监听器对象都在自己的线程内部存储,根本就不会有方法2 的这种问题。
4、ThreadLocal的get和set方法所操作的对象都是当前线程的localValues对象的table数组,因此在不同线程中访问同一个ThreadLocal的set和 get方法,它们对ThreadLocal所做的读/写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据
三、消息队列的工作原理
消息队列在Android中指的是MessageQueue,MessageQueue主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作,插入和读取对应的方法 分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移 除。
- 注:消息队列是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势
四、Looper
1、Handler必须又looper才能工作
Looper.prepare();//Looper的创建
Handler handler=new Handler();
Looper.loop();//开启loop消息循环
//关于loop()方法:loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回了null。当Looper的quit方法被调用时,Looper就会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next方法就会返回null。也就是说,Looper必须退出,否则loop方法就会无限循环下去。loop方法会调用MessageQueue的next方法来获取新消息,而next是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.target.dispatchMessage(msg),这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,这样就成功地将代码逻辑切换到指定的线程中去执行了
Looper除了prepare方法外,还提供了prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare 方法来实现的。由于主线程的Looper比较特殊,所以Looper提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。
2、Looper也是可以退出的,Looper提供了quit和quitSafely来退出一个Looper,二者的区别是:quit会直接退出Looper,而quitSafely只是设定一个退出标记,然后把消息队列中的 已有消息处理完毕后才安全地退出。
3、Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false。
4、在子线程中,如果手动为其创 建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper
五、Handler的工作原理
Handler的工作主要包含消息的发送和接收过程。消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是通过 send的一系列方法来实现的
1、boolean sendMessage(Message msg):
2、boolean sendMeassageDelayed(Message msg,long delayMillis)
3、boolean sendMessageAtTime(Message msg,long uptimeMillis)
4、boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis)
Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开 始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入了处理消息的阶段
六、主线程的消息循环
ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方 法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程 就是主线程的消息循环模型
七、Handler之内存泄露
handler机制的使用
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
public static final int UPDATE_TEXT = 1;//用于表示更新TextView这个动作
private TextView text;
//handler是在主线程的!在这里接收子线程需要更新UI的message
private Handler handler = new Handler() {
//创建Handler 对象,并重写父类的handleMessage() 方法,在这里对具体的Message进行处
理。如果发现Message的what 字段的值等于UPDATE_TEXT ,就更新UI
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
// 在这里可以进行UI操作
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
...
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
//如果需要在在子线程更新UI就利用handler对象发送更新UI的消息
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default:
break;
}
}
}
3、解析异步消息处理机制
本质就是既然在子线程无法更新UI,那就在主线程更新UI,在这个过程子线程扮演的角色就是通知主线程去更新UI,一但在子线程需要更新UI了,那就handler.sendMessage(message)给主线程的handlerMessage(),在这个方法里面去执行UI操作
Android中的异步消息处理主要由4个部分组成:Message、Handler、MessageQueue和Looper。
- Message 线程之间传递的消息,可以在内部携带少量的信息,用于在不同线程之间交 换数据。
- Handler 处理者,发送和处理消息的。发送消息一般是使用Handler的sendMessage() 方法,而发出的消息经过处理后,最终会传递到Handler的handleMessage() 方法中。
- MessageQueue 消息队列,主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue 对象。
- Looper 每个线程中的MessageQueue的管家,调用Looper的loop() 方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage() 方法中。每个线程中也只会有一个Looper 对象。
4、异步消息处理的流程:
首先需要在主线程当中创建一个Handler 对象,并重写handleMessage() 方法。然后当子线程中需要进行UI操作时,就创建一个Message 对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的 handleMessage() 方法中。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,在这里就可以进行UI操作了。 一条Message经过这样一个流程的辗转调用后,也就从子线程进入到了主线程,从不能更新UI变 成了可以更新UI。
5、handler之内存泄露
private Handler handler=new Handler(){
@Override
public void handleMessage(){
}
}
但是这样创建handler对象可能会引发内存泄露,应该定义成静态内部类
handler可能出现内存泄露的原因:
- 在java中非静态内部类和匿名内部类都会隐式地拥有外部类的引用,而静态内部类则不会拥有外部类的引用
- 像上面创建handler的方式实际上handler就是内部类,而它拥有外部类的引用。所以当handler定义在activity里面的线程时,我们发送消息,在Message的target里面存放着发送该消息的handler对象,即消息里面包含了一个Handler实例的引用,并且就是通过这个handler实例来回调handleMessage方法进行处理。
- Handler通过发送Message与其他线程交互,Message发出之后是存储在目标线程的MessageQueue中的,而有时候Message 也不是马上就被处理的,可能会驻留比较久的时间。在Message类中存在一个成员变量 target,它强引用了handler实例,如果Message在Queue中一直存在,就会导致handler实例无法被回收,如果handler对应的类是非静态内部类或者匿名类 ,它持有外部Activity的引用,* 在循环引用同时满足GC Roots可达的时候*,使用finish销毁Activity,会出现外部类实例Activity并不会被回收,就会导致垃圾回收器无法回收这些内存,这就造成了外部类实例的泄露。
解决办法:
避免使用非静态的内部类,即使用静态的内部类,或者自己重新定义一个Myhandler类继承Handler类。另外,我们可以在里面增加一个成员变量来弱引用外部类实例,就可以调用外部类的方法。
法一:静态内部类
public class MainActivity extends Activity{
//把handler类设置为静态内部类
static private class MyHandler extends Handler{
//定义一个对外部类的弱引用
private WeakReference<MainActivity> mActivity;
//构造方法初始化弱引用对象
public MyHandler(MainActivity activity){
mActivity=new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg){
MainActivity activity=mACtivity.get();
super.handleMessage(msg);
}
}
//如果需要创建Runnable的话,Runnable也要定义为静态的
//因为他如果定义为匿名内部类的话同样也持有外部类的引用
private static final Runnable runnable=new Runnable(){
@Override
public void run(){
};
private final MyHandler handler=new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
new Thread(runnable).start();
}
}
法二:单独定义一个类继承自Handler类
public class MyHandler extends Handler{
private WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity){
mActivity =new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg){
MainActivity activity=mActivity.get();
//逻辑操作...
super.handleMessage(msg);
}
}
在Activity中的调用
public class MainActivity extends Activity{
private final MyHandler mHandler=new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
mHandler.sendEmptyMessage(0);
}
}
二、安卓网络编程
1、WebView
借助WebView控件可以实现在应用程序里嵌入一个浏览器,也就是可以在这里加载显示网页
Webview的使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = (WebView) findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);//设置支持js脚本
webView.setWebViewClient(new WebViewClient());//当需要从一个网页跳转到另一个网页时,我
设置目标网页仍然在当前WebView中显示,而不是打开系统浏览器。
webView.loadUrl("http://www.baidu.com");//传入url网址
}
}
ps:访问网络需要权限
<uses-permission android:name="android.permission.INTERNET" />
2、HttpURLConnection
private void sendRequestWithHttpURLConnection() {
// 开启线程来发起网络请求
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
InputStream in = connection.getInputStream();
// 对获取到的输入流进行读取
reader = new BufferedReader(new InputStreamReader(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
showResponse(response.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 在这里进行UI操作,将结果显示到界面上,
//在这里执行UI操作的原因是安卓不允许在子线程更新UI,
//而使用runOnUiThread()方法可以实现线程切换到主线程
responseText.setText(response);
}
});
}
3、解析json格式数据之JSONObject
private void parseJSONWithJSONObject(String jsonData) {
try {
//将返回的json数据作为参数传入JSONArray初始化创建一个json数组,每一个元素都是一个JSONObject对象
JSONArray jsonArray = new JSONArray(jsonData);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
//获取JSONObject对象里面的属性
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
//展示数据
Log.d("MainActivity", "id is " + id);
Log.d("MainActivity", "name is " + name);
}
} catch (Exception e) {
e.printStackTrace();
}
}