1.Handler
Handler是用来结合线程的消息队列来发送、处理"Message对象"和"Runnable对象"的工具。
作用:1.在非UI线程中完成耗时操作,在UI线程中去更新UI。
2.可以在主线程中发送延时消息。
每一个Handler实例之后会关联一个线程和该线程的消息队列。也就是说,当你创建一个Handler的时候,从此开始,他就会自动关联到所在的线程/消息队列,然后它就会陆续把Message/Runnable分发到消息队列,并在他们出队的时候处理掉。因为android只允许在主线程中更新UI,Handler的目的就是作为线程通信的桥梁,进而再通过主线程更新UI。使用Handler这种异步回调机制,使我们可以再完成一个很长的任务后再做出相应的通知。
另外还可以可以在主线程中发送延时消息。
Handler和Message、MessageQueue、Looper之间的关系
Message
Handler接收与处理的消息对象
MessageQueue
消息队列,以先进先出队列形式管理所有的Message,且在初始化Looper对象时会创建一个与之关联的MessageQueue。每个线程最多只有一个MessageQueue。MessageQueue通常都是由Looper来管理,而主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。其他非主线程,不会自动创建Looper。
Looper
每个线程只能够一个Looper,管理MessageQueue,不断从中去除Message分发给对应的Handler处理。
通俗一点讲:当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送消息;而我们发送的消息会先到主线程的MessageQueue中进行等待,由Looper按先入先出顺序取出,再根据Message对象的what属性分发给对应的Handler进行处理。
Handler的主要用途
推送未来某个时间点将要执行的Message或者Runnable到消息队列
通过Handler+Message的方式更新UI
点击更改后前面的TextView改变文本内容
public class HandlerActivity extends AppCompatActivity {
private TextView mHandlerText;
private Button mHanderButtion;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mHandlerText = findViewById(R.id.handler_text);
mHanderButtion = findViewById(R.id.handler_buttion);
mHanderButtion.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = new Message();
message.what = 1;
Bundle bundle = new Bundle();
bundle.putString("text","Nice to meet you");
message.setData(bundle);
handler.sendMessage(message);
}
});
}
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what){
case 1:
mHandlerText.setText(msg.getData().getString("text"));
break;
default:
break;
}
}
};
}
我们发现Handler构造函数过时已过时,这是因为上面的代码存在定的内存泄露风险。
Handler应认为是属于内核的对象,内核和activity所在线程是异步的,当Activity被销毁时内核可能还在用这个Handler,于是内核不让释放Handler,于是这个Handler没用了,却错过了唯一一次被销毁 的机会,就这样占据着内存,这就是内存泄露。
在子线程中完成耗时操作,并发送消息完成UI更新
//主线程不做耗时操作,子线程不更新UI
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
myHandler.sendMessage(message);
} catch (InterruptedException e) {
myHandler.sendMessage(message);
e.printStackTrace();
}
}
}).start();
Post方法
post和sendMessage本质上是没有区别的,只是实际用法中有一点差别
post也没有独特的作用,post本质上还是用sendMessage实现的,post只是一中更方便的用法而已,下面程序可以发现post方法仍然是在主线程下
Log.d("HANDLER_XULIWEI","main "+Thread.currentThread().getId());
mPostButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
Log.d("HANDLER_XULIWEI","thread "+Thread.currentThread().getId());
myHandler.post(new Runnable() {
@Override
public void run() {
mHandlerText.setText("post方法");
Log.d("HANDLER_XULIWEI","post "+Thread.currentThread().getId());
}
});
}
}).start();
}
});
HandlerThread
1.什么场景下需要使用HandlerThread
1). 比较耗时,不易于放在主线程中执行的操作(不考虑第2点使用其他线程方式也可以)
2). 有多个耗时操作需要后台执行(如果不嫌麻烦也可以考虑使用多个TThread)
常用于每隔几秒钟更新数据或图片等。
HandlerThread内部维护了一个消息队列,避免多次创建和销毁子线程来进行操作。
2.HandlerThread的使用步骤
1). 创建HandlerThread对象
2). 执行start方法,启动HandlerThread的Looper循环
3). 在主线程中创建Handler对象并引用HandlerTherad的looper
4). 在Handler对象中加入各种消息的处理
5). 在需要的时候给步骤3创建的Handler对象发送消息
6). 不再需要HandlerThread的时候调用quit或者quitSafely停止HandlerThread
实现一个案例,在这个案例中,我们创建了两个Handler,一个用于更新UI线程的mUIHandler和一个用于异步下载图片的childHandler。最终的结果是childHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知ChildCallback的回调函数handleMessage方法去下载图片并告诉mUIHandler去更新UI界面,
博客上的动图
public class HandlerActivity extends AppCompatActivity {
private TextView mHandlerText;
private Button mHanderButtion;
//测试post方法的button
private Button mPostButton;
private ImageView mImageView;
MyHandler myHandler = new MyHandler(this);
/**
* 图片地址集合
*/
private String url[]={
"https://img-blog.csdn.net/20160903083245762",
"https://img-blog.csdn.net/20160903083252184",
"https://img-blog.csdn.net/20160903083257871",
"https://img-blog.csdn.net/20160903083257871",
"https://img-blog.csdn.net/20160903083311972",
"https://img-blog.csdn.net/20160903083319668",
"https://img-blog.csdn.net/20160903083326871"
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mHandlerText = findViewById(R.id.handler_text);
mHanderButtion = findViewById(R.id.handler_buttion);
mPostButton = findViewById(R.id.handler_post_buttion);
mImageView = findViewById(R.id.image_handler_thread);
//创建异步HandlerThread
HandlerThread handlerThread = new HandlerThread("downloadImage");
//必须先开启线程
handlerThread.start();
//子线程Handler
Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());
for (int i = 0;i<7;i++){
//每一秒更新图片
childHandler.sendEmptyMessageDelayed(i,1000*i);
}
}
class ChildCallback implements Handler.Callback{
@Override
public boolean handleMessage(@NonNull Message msg) {
//在子线程中进行网络请求
Bitmap bitmap = downloadUrlBitmap(url[msg.what]);
ImageModel imageModel = new ImageModel();
imageModel.url = url[msg.what];
imageModel.bitmap = bitmap;
Message message = new Message();
message.what = msg.what;
message.obj = imageModel;
//通知朱线程去更新UI
return false;
}
}
class ImageModel{
public String url;
public Bitmap bitmap;
}
private Bitmap downloadUrlBitmap(String urlString) {
HttpURLConnection urlConnection = null;
BufferedInputStream in = null;
Bitmap bitmap = null;
try {
final URL url = new URL(urlString);
urlConnection = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
bitmap = BitmapFactory.decodeStream(in);
} catch (final IOException e) {
e.printStackTrace();
}finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
try {
if (in != null) {
in.close();
}
} catch (final IOException e) {
e.printStackTrace();
}
}
return bitmap;
}
}
3 AsyncTask
AsyncTask是除了Handler外的另一种异步机制
public abstract class AsyncTask<Params, Progress, Result> {}
三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用java.lang.Void类型代替。
一个异步任务的执行一般包括以下几个步骤:
1.execute(Params... params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
2.onPreExecute(),在execute(Params... params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记。
3.doInBackground(Params... params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress... values)来更新进度信息。
4.onProgressUpdate(Progress... values),在调用publishProgress(Progress... values)时,此方法被执行,直接将进度信息更新到UI组件上。
5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。
在使用的时候,有几点需要格外注意:
1.异步任务的实例必须在UI线程中创建。
2.execute(Params... params)方法必须在UI线程中调用。
3.不要手动调用onPreExecute(),doInBackground(Params... params),onProgressUpdate(Progress... values),onPostExecute(Result result)这几个方法。
4.不能在doInBackground(Params... params)中更改UI组件的信息。
5.一个任务实例只能执行一次,如果执行第二次将会抛出异常
通过一个demo来实验AsyncTask的应用
实例说明:
- 点击按钮 则 开启线程执行线程任务
- 显示后台加载进度
- 加载完毕后更新UI组件
- 期间若点击取消按钮,则取消加载
主布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="200dp">
<Button
android:id="@+id/async_button"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:text="点击加载"/>
<TextView
android:id="@+id/async_text"
android:layout_below="@+id/async_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="还没开始加载!"/>
<ProgressBar
android:layout_below="@+id/async_text"
android:id="@+id/async_progress_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:progress="0"
android:max="100"
style="?android:attr/progressBarStyleHorizontal"/>
<Button
android:layout_below="@+id/async_progress_bar"
android:layout_centerInParent="true"
android:id="@+id/async_concel_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="取消"/>
</RelativeLayout>
- 主逻辑代码文件:HandlerActivity.java
-
//AsyncTask 测试demo mAsyncTaskText = findViewById(R.id.async_text); mAsyncTaskButton = findViewById(R.id.async_button); mAsyncTaskCancelButton = findViewById(R.id.async_concel_button); mAsyncProgressBar = findViewById(R.id.async_progress_bar); myTask = new MyTask(); mAsyncTaskButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 步骤3:手动调用execute(Params... params) 从而执行异步线程任务 * 注: * a. 必须在UI线程中调用 * b. 同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常 * c. 执行任务中,系统会自动调用AsyncTask的一系列方法:onPreExecute() 、doInBackground()、onProgressUpdate() 、onPostExecute() * d. 不能手动调用上述方法 */ myTask.execute(); } }); mAsyncTaskCancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 取消一个正在执行的任务,onCancelled方法将会被调用 myTask.cancel(true); } });
-
/** * 步骤1:创建AsyncTask子类 * 注: * a. 继承AsyncTask类 * b. 为3个泛型参数指定类型;若不使用,可用java.lang.Void类型代替 * 此处指定为:输入参数 = String类型、执行进度 = Integer类型、执行结果 = String类型 * c. 根据需求,在AsyncTask子类内实现核心方法 */ private class MyTask extends AsyncTask<String,Integer,String>{ // 方法1:onPreExecute() // 作用:执行 线程任务前的操作 @Override protected void onPreExecute() { mAsyncTaskText.setText("加载中"); } // 方法2:doInBackground() // 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果 // 注:必须复写,从而自定义线程任务 @Override protected String doInBackground(String... strings) { try { int count = 0; int length = 1; while (count<99){ count += length; // 可调用publishProgress()显示进度, 之后将执行onProgressUpdate() publishProgress(count); // 模拟耗时任务 Thread.sleep(50); } } catch (InterruptedException e) { e.printStackTrace(); } return null; } // 方法3:onProgressUpdate() // 作用:在主线程 显示线程任务执行的进度 @Override protected void onProgressUpdate(Integer... progresses) { mAsyncProgressBar.setProgress(progresses[0]); mAsyncTaskText.setText("loading..." + progresses[0] + "%"); } // 方法4:onPostExecute() // 作用:接收线程任务执行结果、将执行结果显示到UI组件 @Override protected void onPostExecute(String result) { // 执行完毕后,则更新UI mAsyncTaskText.setText("加载完毕"); } // 方法5:onCancelled() // 作用:将异步任务设置为:取消状态 @Override protected void onCancelled() { mAsyncTaskText.setText("已取消"); mAsyncProgressBar.setProgress(0); } }
4.线程的创建
- 1.通过继承Thread类创建线程类
-
public class MyThread extends Thread{ public static void main(String[] args){ new MyThread().start(); } public void run(){ .... } }
2.通过实现Runnable接口创建线程类
-
public class MyRunnable implement Runnable{ public static void main(String[] args){ new Thread(new MyRunnable(),"线程").start(); } public void run(){ .... } }
3.通过Callable和Future接口创建线程
-
Callable接口可以理解为Runnable的增强版,提供了一个call()方法可以作为线程执行体,它与Runnable的区别在于:
1、call()方法可以有返回值;
2、call()方法可以声明抛出异常;
-
Callable接口是JDK5后新增的接口,而且不是Runnable的子接口,所以Callable对象不能直接作为Thread的target。而且call方法还有一个返回值,
JDK1.5提供了Future接口来代表Callable接口里的call方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现Future接口, -
// 定义3个Callable类型的任务 FutureTask<String> task1 = new FutureTask<>(new MyCallable(0)); new Thread(task1).start(); FutureTask<String> task2 = new FutureTask<>(new MyCallable(2)); new Thread(task2).start(); try { Log.d("MyCallable",task1.get()); Log.d("MyCallable",task2.get()); } catch (Exception e) { Log.d("MyCallable",e.toString()); }
class MyCallable implements Callable<String> { //标志位 private int flag = 0; public MyCallable(int flag){ this.flag = flag; } @Override public String call() throws Exception { if(flag==0){ // 如果flag的值为0,则立即返回 return "flag = 0"; }else { // falg不为0或者1,则抛出异常 throw new Exception("Bad flag value!"); } } }
-
5.线程池的创建和使用
-
在android开发中经常会使用多线程异步来处理相关任务,而如果用传统的new Thread来创建一个子线程进行处理,会造成一些严重的问题:
1:在任务众多的情况下,系统要为每一个任务创建一个线程,而任务执行完毕后会销毁每一个线程,所以会造成线程频繁地创建与销毁。
2:多个线程频繁地创建会占用大量的资源,并且在资源竞争的时候就容易出现问题,同时这么多的线程缺乏一个统一的管理,容易造成界面的卡顿。
3:多个线程频繁地销毁,会频繁地调用GC机制,这会使性能降低,又非常耗时。
总而言之:频繁地为每一个任务创建一个线程,缺乏统一管理,降低性能,并且容易出现问题。
-
线程池使用的好处:
1:对多个线程进行统一地管理,避免资源竞争中出现的问题。
2:(重点):对线程进行复用,线程在执行完任务后不会立刻销毁,而会等待另外的任务,这样就不会频繁地创建、销毁线程和调用GC。
3:JAVA提供了一套完整的ExecutorService线程池创建的api,可创建多种功能不一的线程池,使用起来很方便。
几种常见的线程池
-
5.1 ThreadPoolExecutor 创建基本线程池
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
...
}
corePoolSize: 该线程池中核心线程的数量。
注意:线程池中存在核心线程与非核心线程,核心线程一旦创建会一直执行任务或等待任务到来,而非核心线程只在任务队列塞满任务时去执行多出的任务,并且非核心线程在等待一段时间后将会被回收,这个时间作为参数可调配,见下面的keepAliveTime参数。
maximumPoolSize:该线程池中最大线程数量。(区别于corePoolSize)
keepAliveTime:从字面上就可以理解,是非核心线程空闲时要等待下一个任务到来的时间,当任务很多,每个任务执行时间很短的情况下调大该值有助于提高线程利用率。注意:当allowCoreThreadTimeOut属性设为true时,该属性也可用于核心线程。
unit:上面时间属性的单位
workQueue:任务队列,后面详述。
threadFactory:线程工厂,可用于设置线程名字等等,一般无须设置该参数。
遵循规则
1.线程池中的线程数量未达到核心线程数量,会直接启动一个核心线程来执行任务。
2.如果线程池中的线程数量已经达到或者超过核心线程数量,那么任务会被插入到任务队列中等待执行。
3.如果任务队列已满,且未达到最大线程数,会启动一个非核心线程来执行任务队列中的任务。
4.如果线程池中的最大线程数达到线程池规定的最大值,那么线程池就会拒绝执行此任务,并通过handler参数告知调用者。
5.核心线程默认无超时机制,可调用allowCoreThreadTimeOut方法设置核心线程是否可有超时机制。
threadPoolButton = findViewById(R.id.thread_pool_execute);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,1, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));
threadPoolButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 0;i<10;i++){
final int finalI = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.d("Thread","任务: "+ finalI+ ",当前线程名"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
threadPoolExecutor.execute(runnable);
}
}
});
每隔2秒打印三个任务
5.2 FixedThreadPool (可重用固定线程数)
特点:参数为核心线程数,只有核心线程,无非核心线程,并且阻塞队列无界。
fixedThreadPoolButton = findViewById(R.id.single_thread_pool_execute);
ExecutorService singleThreadPoolExecutor = Executors.newFixedThreadPool(5);
singleThreadPoolButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 0;i<10;i++){
final int finalI = i;
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
Log.d("Thread","任务: "+ finalI+ ",当前线程名"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
fixedThreadPoolButton .execute(runnable);
}
}
});
每隔2秒打印5个任务
5.3 CachedThreadPool (按需创建)
//创建Cached线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
特点:没有核心线程,只有非核心线程,并且每个非核心线程空闲等待的时间为60s,采用SynchronousQueue队列。
- 因为没有核心线程,其他全为非核心线程,SynchronousQueue是不存储元素的,每次插入操作必须伴随一个移除操作,一个移除操作也要伴随一个插入操作。
- 当一个任务执行时,先用SynchronousQueue的offer提交任务,如果线程池中有线程空闲,则调用SynchronousQueue的poll方法来移除任务并交给线程处理;如果没有线程空闲,则开启一个新的非核心线程来处理任务。
- 由于maximumPoolSize是无界的,所以如果线程处理任务速度小于提交任务的速度,则会不断地创建新的线程,这时需要注意不要过度创建,应采取措施调整双方速度,不然线程创建太多会影响性能。
- 从其特点可以看出,CachedThreadPool适用于有大量需要立即执行的耗时少的任务的情况。
5.4 SingleThreadPool(单个核线的fixed)
//创建Single线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
5.5 ScheduledThreadPool(定时延时执行)
创建
//创建Scheduled线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
使用:
//延迟5s后启动,每1s执行一次
scheduledThreadPool.scheduleAtFixedRate(runnable,5,1,TimeUnit.SECONDS);
//启动后第一次延迟5s执行,后面延迟1s执行
scheduledThreadPool.scheduleWithFixedDelay(runnable,5,1,TimeUnit.SECONDS);