目录
一,Android中的多线程问题
1.模拟耗时工作
2.Android开启子线程
二,在子线程中更新UI
1.异步消息处理机制 Handler
2.使用runOnUiThread更新UI
一,Android中的多线程问题
Android用户界面是与用户交互的接口,对于用户的操作,Android迅速响应用户输入(200ms内)是一个重要目标。因此,一些耗时操作(如:后台下载,异步加载图片等)需要放在子线程中运行,否则会导致主线程阻塞。
1.模拟耗时工作
例如下面这段访问百度界面的代码,如果在主线程中运行的话就会出现android.os.Network-OnMainThreadException的报错,也就是在主线程中请求了网络操作,这是一种耗时操作。为了解决这个问题,就需要把操作放在子线程中运行。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
setListeners();
}
private void setListeners() {
btn_baidu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//获取百度链接
URL url = new URL("https://www.baidu.com/");
//获取输入流
InputStream inputStream = url.openStream();
byte[] bytes = new byte[1024];
//存储输入的信息
StringBuffer buffer = new StringBuffer();
while((inputStream.read(bytes)) != -1){
String str = new String(bytes, 0, bytes.length);
buffer.append(str);
}
Log.i("baidu", buffer.toString());
//关闭流
inputStream.close();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
2.Android开启子线程
在Android中开启线程的操作与在Java中一致,继承Thread类或实现Runnable接口,不了解的话可以阅读博客:Java线程基础:Thread Runnable 多线程 Synchronized 死锁...-CSDN博客。
例如下面用实现Runnable接口的方法来开启子线程,访问百度:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViews();
setListeners();
}
private void setListeners() {
btn_baidu.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//获取百度链接
URL url = new URL("https://www.baidu.com/");
//获取输入流
InputStream inputStream = url.openStream();
byte[] bytes = new byte[1024];
//存储输入的信息
StringBuffer buffer = new StringBuffer();
while((inputStream.read(bytes)) != -1){
String str = new String(bytes, 0, bytes.length);
buffer.append(str);
}
//在子线程中更改Ui界面,使用runOnUiThread
Log.i("baidu", buffer.toString());
//关闭流
inputStream.close();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
});
}
运行并查看日志,可以发现成功访问:
二,在子线程中更新UI
使用子线程解决异步执行又会带来新问题,那就是在Android中,只有UI线程(也叫主线程)可以更新UI界面,子线程不能更新。为了在子线程中更新UI,我们需要使用Android异步消息处理机制。
1.异步消息处理机制 Handler
Android中的异步消息处理主要由4个部分组成:Message,Handler,MessageQueue,Looper。
- Message:在线程之间传递的消息,Message中可以封装一些数据如:what(int型,表示Message的编号),obj(封装的Object对象),此外还有int型的arg1,arg2等;
- Handler:用于在线程间发送和处理消息,发送消息使用sendMessage()方法,处理消息使用handleMessage()方法;
- MessageQueue:消息队列,用于存放Handler发送的消息,这些消息直到被处理前,会一直存放在消息队列中。每个线程只会有一个MessageQueue对象;
- Looper:Looper是每个线程中MessageQueue的管家,调用Looper的loop方法后,会进入无限循环,每当发现MessageQueue中存在一条消息,就会将其取出,并传递到Handler的handleMessage()方法中,每个线程只会有一个Looper对象;
异步消息处理机制的基本流程为:
(1)首先在主线程中创建一个Handler对象,并重写handleMessage方法。
(2)当子线程需要更改UI时,就创建一个Message对象,并通过Handler将Message发送出去,Message消息会被添加到MessageQueue中等待处理,Looper会一直尝试从消息队列中取出消息,并传给Handler的handleMessage方法。
(3)Handler的构造器中我们传入了Looper.getMainLooper,所以handleMessage方法中的代码会在UI线程中运行,我们就可以放心地进行UI操作。
下面是代码实例(获取网络图片):
private void getImg() {
//1.在主线程中创建一个Handler对象,并重写handleMessage方法。
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 114514:
Bitmap bitmap = (Bitmap) msg.obj;
iv_img.setImageBitmap(bitmap);
Log.i("114514", "获取图片成功!");
break;
}
}
};
//设置监听
btn_getimg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//2.当子线程需要更改UI时,就创建一个Message对象
URL url = new URL("https://profile-avatar.csdnimg.cn/8e4c56733fdd4dda90854384976d4bb0_ih_lzh.jpg!1");
InputStream inputStream = url.openStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Message msg = handler.obtainMessage();
//封装bitmap对象和设置对象编号
msg.obj = bitmap;
msg.what = 114514;
//3.通过Handler将Message发送出去
handler.sendMessage(msg);
inputStream.close();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
});
}
2.使用runOnUiThread更新UI
runOnUiThread,在UI线程上运行指定的操作。如果当前线程是UI线程,则执行操作,如果当前线程不是UI线程,操作将被提交到UI线程的消息队列MessageQueue中。runOnUiThread只能在Activity中使用。
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//提交到消息队列
} else {
action.run();//操作执行
}
}
还是上面获取图片的例子,将Handler改为使用runOnUiThread更改UI:
private void getImg() {
//设置监听
btn_getimg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("https://profile-avatar.csdnimg.cn/8e4c56733fdd4dda90854384976d4bb0_ih_lzh.jpg!1");
InputStream inputStream = url.openStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
runOnUiThread(new Runnable() {
@Override
public void run() {
iv_img.setImageBitmap(bitmap);
}
});
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
}
});
}