美文网首页面经
Android多线程模式---Thread,AsyncTask,

Android多线程模式---Thread,AsyncTask,

作者: 善笃有余劫 | 来源:发表于2018-07-26 23:04 被阅读28次

Android多线程

因为Android主线程用于处理界面相关事件,如果有耗时任务必须转到子线程执行。否则如果任务事件过长,导致主线程堵塞,造成ANR异常。通常网络操作,IO操作都要到子线程里面去执行。

Android多线程类型

线程池的使用是为了避免重复使用线程的过程中大量的创建和销毁线程造成的资源,缓存一定数量的线程在池子中。

Android的多线程本质就是Thread的使用,为了方便与主线程的通信和使用,在Thread的基础上又封装了AsyncTask,HandlerThread,IntentService。都有不同的特性和使用场景。

  1. AsyncTask,底层使用了线程池和Handler,方便开发者更新UI。
  2. HandlerThread是自带消息循环的线程,可以在内部使用Handler
  3. IntentService是一种服务,封装了HandlerThread可以在内部执行后台任务并且执行完毕自动退出。

下面一一做了解:

Thread以及Handler消息发送

关于Handler我们只需要new 一个Handler对象并重写handleMessage(Message msg)方法即可。

如下:

 private Handler myHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case THREAD_1:
                    if (msg.obj != null) {
                        spink.setVisibility(View.GONE);
                        image1.setImageBitmap((Bitmap) msg.obj);
                    }
                    break;
            }
            super.handleMessage(msg);
        }
    };

随后调用Handler.sendMessage()方法即可

开启线程的方式有两种,两种方法等价:

1.new Thread 实现 Runnable接口的run方法

new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = new Message();
                message.obj = ImageGetUtil.getNetImage("https://imgsa.baidu.com/forum/w%3D580/sign=32addc638bd6277fe9123230183a1f63/b4f4e0134954092368f82ba69e58d109b2de491e.jpg", MainActivity.this);
                message.what = THREAD_1;
                myHandler.sendMessage(message);
            }
        }).start();

2.继承Thread,重写run方法:

class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            Message message = new Message();
            message.obj = ImageGetUtil.getNetImage("https://imgsa.baidu.com/forum/w%3D580/sign=32addc638bd6277fe9123230183a1f63/b4f4e0134954092368f82ba69e58d109b2de491e.jpg", MainActivity.this);
            message.what = THREAD_1;
            myHandler.sendMessage(message);
        }
    }

new MyThread().start();

在run方法执行耗时任务后,发送消息给主线程即可。

AsyncTask

一种轻量级的异步任务执行类,无法执行大量数据访问。原因在于内部的线程池线程数量有限,如果挂起太长导致无法执行任务后面的任务。

核心方法

首先AsyncTask是泛型类,输入三种泛型,表示输入参数类型,进度更新类型和结果返回类型。
public abstract class AsyncTask<Params, Progress, Result>

执行顺序,四个核心方法:

  1. onPreExecute:UI线程中调用,在调用excute方法后会立即被调用,这个方法适用于初始化task,比如显示后台任务进度条;

  2. doInBackground:非UI线程中调用,在onPreExecute执行后调用,这个方法适用于执行耗时的操作,excute方法中的Params参数就是通过这个方法传递的,该方法的返回值就是Task执行的结果,在doInBackground方法执行过程中,可以通过publishProgress方法来更新后台任务的执行进度;

  3. onProgressUpdate:UI线程中调用,在publishProgress方法后被调用,这个方法用于在后台任务执行过程中显示任务的进度UI,比如它可以用于显示进度条动画或者显示进度文本;

  4. onPostExecute:UI线程中调用,后台任务执行完成后被调用,Task返回的结果以参数的形式传递到该方法中。

  5. 还有一个方法 onCancelled`只有任务被取消才会调用,同时onPostExecute不再被调用

具体实现和使用

class IAsyncTask extends AsyncTask<String, Float, List<Bitmap>> {
        /**
         * 异步任务之前 UI的更改
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            spink.setVisibility(View.VISIBLE);
        }


        /**
         * 线程池中执行异步任务
         *
         * @param strings
         * @return
         */
        @Override
        protected List<Bitmap> doInBackground(String... strings) {
            /**
             * 这里执行多个任务 使用的就是线程池
             */
            List<Bitmap> list = new ArrayList<>();
            int i = 0;
            for (String string : strings) {
                i++;
                String url = string;
                Bitmap bitmap = null;
                URLConnection connection;
                InputStream is;
                try {
                    connection = new URL(url).openConnection();
                    is = connection.getInputStream();
                    //为了更清楚的看到加载图片的等待操作,将线程休眠3秒钟.
                    Thread.sleep(3000);
                    BufferedInputStream bis = new BufferedInputStream(is);
                    //通过decodeStream方法解析输入流
                    bitmap = BitmapFactory.decodeStream(bis);
                    is.close();
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新进度
                if (i == 1) {
                    publishProgress(100f,0f,0f);
                } else if (i == 2) {
                    publishProgress(100f,100f,0f);
                } else if (i == 3) {
                    publishProgress(100f,100f,100f);
                }

                list.add(bitmap);
            }
            return list;
        }

        /**
         * 主线程执行 后台任务执行 onProgressUpdate 方法后调用 用于更新进度
         *
         * @param values
         */
        @Override
        protected void onProgressUpdate(Float... values) {
            super.onProgressUpdate(values);
            textView.setText("进度:第一张" + values[0] + "%"+"第二张"+values[1]+"%"+"第三张"+values[2]+"%");
        }

        /**
         * 取得执行结果
         *
         * @param
         */
        @Override
        protected void onPostExecute(List<Bitmap> bitmaps) {
            super.onPostExecute(bitmaps);
            if (bitmaps.size() > 0) {
                spink.setVisibility(View.GONE);
                image1.setImageBitmap(bitmaps.get(0));
                image2.setImageBitmap(bitmaps.get(1));
                image3.setImageBitmap(bitmaps.get(2));
            }
        }
    }

调用

new IAsyncTask().execute("https://imgsa.baidu.com/forum/w%3D580/sign=32addc638bd6277fe9123230183a1f63/b4f4e0134954092368f82ba69e58d109b2de491e.jpg", "https://imgsa.baidu.com/forum/w%3D580/sign=bb7a7ccdb499a9013b355b3e2d940a58/e84c98de9c82d15868f5f9688c0a19d8bd3e42bd.jpg", "https://imgsa.baidu.com/forum/w%3D580%3B/sign=e19d5486d1c451daf6f60ce386c65366/eac4b74543a9822635b4e7ff8682b9014b90ebf6.jpg");
image.png

分析:

这里传入了多个string参数,在doInBackground中分别取出执行任务,其实这里用到了线程池,但是只是用到了一个线程。所以只是在一个线程里面执行了多个任务。

为了真正使用到线程池,我们可以多次创建不同的AsyncTask对象并开始任务。

 new IAsyncTask().execute()
 new IAsyncTask().execute()
 new IAsyncTask().execute()
 new IAsyncTask().execute()

同时我们可以发现,这几个任务是从上到下串行的,既只有上一个任务完成或取消才会继续执行下一个AsyncTask任务。

更重要的是,不只是IAsyncTask的实例会串行执行,而是一个进程的所有继承于AsyncTask的实例都会串起来执行任务。

AsyncTask的工作原理和源码分析篇幅较长,我重新开篇写:

AsyncTask的使用场景

AsyncTask 运用的场景就是我们需要进行一些耗时的操作,耗时操作完成后更新主线程,或者在操作过程中对主线程的UI进行更新。比如简单的下载任务。

不适合两种任务:一种是耗时太长的任务,因为某个AsyncTask耗时过长,导致整个进程的AsyncTask等待,不划算。第二种是简单的I/O 比如大量多次的数据库读写。开销不大但是次数多,可以在单独的线程完成。而不必使用AsyncTask挂起耗时。

HandlerThread

https://blog.csdn.net/u011240877/article/details/72905631

HandlerThread 内容很简单,就是集成了Handler和Looper的线程。可以发消息给HandlerThread 。

那么HandlerThread 的产生意义是什么呢,也就是我们为什么要发消息给子线程呢?通常我们从子线程发消息给主(UI)线程就是为了更新UI做事件处理。其实就是线程需要执行一个又耗时又是多任务的的任务,既反复执行某个耗时任务,需要用到HandlerThread。HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅。

使用场景

为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。
HandlerThread 比较合适处理那些在工作线程执行,需要花费时间偏长的任务。我们只需要把任务发送给 HandlerThread,然后就只需要等待任务执行结束的时候通知返回到主线程就好了。
另外很重要的一点是,一旦我们使用了 HandlerThread,需要特别注意给 HandlerThread 设置不同的线程优先级,CPU 会根据设置的不同线程优先级对所有的线程进行调度优化。

总结来说:

HandlerThread比较适用于单线程+异步队列的场景,比如IO读写操作,耗时不多而且也不会产生较大的阻塞。对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。

最常见的例子就是实时更新(轮询),每隔多少时间获取一下最新资讯。

大家想想轮询能不能放在UI线程?当然可以。比如下面这样
写在主线程的

public void pollThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                }catch (InterruptedException e){

                }
                Message message = new Message();
                message.obj = "data________________________";
                message.what = POLL_THREAD;
                myHandler.sendMessage(message);
            }
        }).start();
    }

调用thread

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button3:
                pollThread();
                break;
        }
    }

主线程的Handler,注意重新调用了Thread

private Handler myHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case POLL_THREAD:
                    Log.e("pollThread",(String)msg.obj);
                    pollThread();
                    break;
            }
            super.handleMessage(msg);
        }
    };

输出:

image.png

基本也做到了轮询,但是产生的问题有两个:

1.产生了大量的Thread对象,消耗了大量内存。
2.每次轮询都要MainLooper来循环处理,增加了主线程的UI压力,可能导致卡顿。

HandlerThread的轮询

开启线程执行任务

1.实例化一个HandlerThread
2.执行start方法开始线程
3.实例化HandlerThread的Handler对象mThreadHandler ,使用HandlerThread的Looper
4.重写mThreadHandler 的handleMessage方法,处理发送给mThreadHandler 的消息。
5.在handleMessage方法中,处于子线程内部,可以一边执行耗时任务一边使用handler发送消息。

值得注意的是 这里有两个Handler可以选择,一个是HandlerThread的Handler,一个是UI线程的Handler
如果只需要内部循环处理,只需要使用mThreadHandler即可
如果需要进行UI线程的,使用mMainHandler才行

 private void initThread() {
        mHandlerThread = new HandlerThread("check-message-coming");
        mHandlerThread.start();

        mThreadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {

                //处于子线程中 执行异步任务

                try {
                    //模拟耗时
                    Thread.sleep(2000);

//                    mMainHandler.post(new Runnable() {
//                        @Override
//                        public void run() {
//                            String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
//                            result = String.format(result, (int) (Math.random() * 3000 + 1000));
//                            Log.e("updatePollThread", result);
//                            textView2.setText(Html.fromHtml(result));
//                        }
//                    });

                    //发到主线程
                    String result = "实时更新中,当前大盘指数:<font color='red'>%d</font>";
                    result = String.format(result, (int) (Math.random() * 3000 + 1000));
                    Message message=new Message();
                    message.what=POLL_THREAD_2;
                    message.obj=result;
                    mMainHandler.sendMessage(message);



                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


                //请求数据后 判断是否继续轮询 为true 继续,发送message给自己 继续调用 访问网络数据
                if (isUpdateInfo) {
                    mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
                }
            }
        };

    }

使ThreadHandler值得注意的地方;
1.如果做轮询,可在onResume和onPause方法中分别开启和关闭线程,更改了 isUpdateInfo 参数之后 都需要调用一次 sendEmptyMessage 来更新 运行状态

@Override
    protected void onResume() {
        super.onResume();
        //开始查询
        isUpdateInfo = true;
        mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }

    @Override
    protected void onPause() {
        super.onPause();
        //停止查询
        //以防退出界面后Handler还在执行
        isUpdateInfo = false;
        mThreadHandler.removeMessages(MSG_UPDATE_INFO);
    }

2.因为HandlerThread 的run方法是一个死循环 不能和正常的thread一样执行run方法后被回收 所以需要在onDestroy主动停止。

 @Override
    protected void onDestroy() {
        super.onDestroy();
        //释放资源
        mHandlerThread.quit();
      //下面的方法在18之后添加的
       //mHandlerThread.quitSafely();
    }

常见面试题

1.请介绍下 AsyncTask 的内部实现,适用的场景,执行过程?

AsyncTask 内部也是 Handler 机制来完成的,只不过 Android 提供了执行框架来提供线程池来执行相应地任务。

因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务,比如 HTTP 请求。而大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有线程来执行其他的任务,导致的情形是会发生 AsyncTask 根本执行不了的问题。

正常执行流程:

1.onPreExecute:UI线程中调用,在调用excute方法后会立即被调用,这个方法适用于初始化task,比如显示后台任务进度条;

2.doInBackground:非UI线程中调用,在onPreExecute执行后调用,这个方法适用于执行耗时的操作,excute方法中的Params参数就是通过这个方法传递的,该方法的返回值就是Task执行的结果,在doInBackground方法执行过程中,可以通过publishProgress方法来更新后台任务的执行进度;

3.onProgressUpdate:UI线程中调用,在publishProgress方法后被调用,这个方法用于在后台任务执行过程中显示任务的进度UI,比如它可以用于显示进度条动画或者显示进度文本;

4.onPostExecute:UI线程中调用,后台任务执行完成后被调用,Task返回的结果以参数的形式传递到该方法中。

相关文章

网友评论

    本文标题:Android多线程模式---Thread,AsyncTask,

    本文链接:https://www.haomeiwen.com/subject/lwwumftx.html