Handler的应用

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的应用

实例说明:

  1. 点击按钮 则 开启线程执行线程任务
  2. 显示后台加载进度
  3. 加载完毕后更新UI组件
  4. 期间若点击取消按钮,则取消加载

主布局文件 

<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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值