多线程并发

android中很多操作需要在主线程中执行,比如UI的操作,点击事件等等,但是如果主线程操作太多,占有的执行时间过长就会出现前面我们说的卡顿现象:

image1.jpg

为了减轻主线程操作过多,避免出现卡顿的现象,我们把一些操作复杂的消耗时间长的任务放到线程池中去执行。下面我们就来介绍android中几种线程的类。

1.AsyncTask

为UI线程与工作线程之间进行快速的切换提供一种简单便捷的机制。适用于当下立即需要启动,但是异步执行的生命周期短暂的使用场景。

它提供了一种简便的异步处理机制,但是它又同时引入了一些令人厌恶的麻烦。一旦对AsyncTask使用不当,很可能对程序的性能带来负面影响,同时还可能导致内存泄露。(关于内存泄漏在上面已经讲过)

使用AsyncTask需要注意的问题?

(1).在AsyncTask中所有的任务都是被线性调度执行的,他们处在同一个任务队列当中,按顺序逐个执行。一旦有任务执行时间过长,队列中其他任务就会阻塞。

image3.jpg

对于上面的问题,我们可以使用AsyncTask.executeOnExecutor()让AsyncTask变成并发调度。

(2).AsyncTask对正在执行的任务不具备取消的功能,所以我们要在任务代码中添加取消的逻辑(和上面Thread类似)

(3).AsyncTask使用不当会导致内存泄漏(可以参考内存泄漏一章)

2.HandlerThread

为某些回调方法或者等待某些任务的执行设置一个专属的线程,并提供线程任务的调度机制。

先来了解下Looper,Handler,MessageQueue

Looper: 能够确保线程持续存活并且可以不断的从任务队列中获取任务并进行执行。

Handler: 能够帮助实现队列任务的管理,不仅仅能够把任务插入到队列的头部,尾部,还可以按照一定的时间延迟来确保任务从队列中能够来得及被取消掉。

MessageQueue: 使用Intent,Message,Runnable作为任务的载体在不同的线程之间进行传递。

把上面三个组件打包到一起进行协作,这就是HandlerThread

image2.jpg

我们先来看下源码:

    public class HandlerThread extends Thread {

        public HandlerThread(String name, int priority) {

            super(name);

            mPriority = priority;

        }

        @Override

        public void run() {

            mTid = Process.myTid();

            Looper.prepare();

            synchronized (this) {

                mLooper = Looper.myLooper();

                notifyAll();

            }

            Process.setThreadPriority(mPriority);

            onLooperPrepared();

            Looper.loop();

            mTid = -1;

        }

        public Looper getLooper() {

            if (!isAlive()) {

                return null;

            }

            // If the thread has been started, wait until the looper has been created.

            synchronized (this) {

                while (isAlive() && mLooper == null) {

                    try {

                        wait();

                    } catch (InterruptedException e) {

                    }

                }

            }

            return mLooper;

        }

    }

从上面的源码发现,HandlerThread其实就是在线程中维持一个消息循环队列。下面我们看下使用:

    HandlerThread mHanderThread = new HandlerThread("hanlderThreadTest", Process.THREAD_PRIORITY_BACKGROUND);

    mHanderThread.run();

    Looper mHanderThreadLooper = mHanderThread.getLooper();

    Handler mHandler = new Handler(mHanderThreadLooper){

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            //子线程中执行

            ...

        }

    };

    //发送消息

    mHandler.post(new Runnable() {

        @Override

        public void run() {

            ...

        }

    }); 

3.IntentService

适合于执行由UI触发的后台Service任务,并可以把后台任务执行的情况通过一定的机制反馈给UI。

默认的Service是执行在主线程的,可是通常情况下,这很容易影响到程序的绘制性能(抢占了主线程的资源)。除了前面介绍过的AsyncTask与HandlerThread,我们还可以选择使用IntentService来实现异步操作。IntentService继承自普通Service同时又在内部创建了一个HandlerThread,在onHandlerIntent()的回调里面处理扔到IntentService的任务。所以IntentService就不仅仅具备了异步线程的特性,还同时保留了Service不受主页面生命周期影响的特点。

image5.jpg

使用IntentService需要特别注意的点:

(1).因为IntentService内置的是HandlerThread作为异步线程,所以每一个交给IntentService的任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。

(2).通常使用到IntentService的时候,我们会结合使用BroadcastReceiver把工作线程的任务执行结果返回给主UI线程。使用广播容易引起性能问题,我们可以使用LocalBroadcastManager来发送只在程序内部传递的广播,从而提升广播的性能。我们也可以使用runOnUiThread()快速回调到主UI线程。

(3).包含正在运行的IntentService的程序相比起纯粹的后台程序更不容易被系统杀死,该程序的优先级是介于前台程序与纯后台程序之间的。

4.Loader

对于3.0后ContentProvider中的耗时操作,推荐使用Loader异步加载数据机制。相对其他加载机制,Loader有那些优点呢?

提供异步加载数据机制

对数据源变化进行监听,实时更新数据

在Activity配置发生变化(如横竖屏切换)时不用重复加载数据

适用于任何Activity和Fragment

下面我们来看下Loader的具体使用:

我们以获得手机中所有的图片为例:

    getLoaderManager().initLoader(LOADER_TYPE, null, mLoaderCallback);

    LoaderManager.LoaderCallbacks<Cursor> mLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() {

        private final String[] IMAGE_COLUMNS={

                MediaStore.Images.Media.DATA,//图片路径

                MediaStore.Images.Media.DISPLAY_NAME,//显示的名字

                MediaStore.Images.Media.DATE_ADDED,//添加时间

                MediaStore.Images.Media.MIME_TYPE,//图片扩展类型

                MediaStore.Images.Media.SIZE,//图片大小

                MediaStore.Images.Media._ID,//图片id

        };

        @Override

        public Loader<Cursor> onCreateLoader(int id, Bundle args) {

            toggleShowLoading(true,getString(R.string.common_loading));

            CursorLoader cursorLoader = new CursorLoader(ImageSelectActivity.this,                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,IMAGE_COLUMNS,

                    IMAGE_COLUMNS[4] + " > 0 AND "+IMAGE_COLUMNS[3] + " =? OR " +IMAGE_COLUMNS[3] + " =? ",

                    new String[]{"image/jpeg","image/png"},IMAGE_COLUMNS[2] + " DESC");

            return cursorLoader;

        }

        @Override

        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

            if(data != null && data.getCount() > 0){

                ArrayList<String> imageList = new ArrayList<>();

                if(mShowCamera){

                    imageList.add("");

                }

                while (data.moveToNext()){

                    String path = data.getString(data.getColumnIndexOrThrow(IMAGE_COLUMNS[0]));

                    imageList.add(path);

                    Log.e("ImageSelect", "IIIIIIIIIIIIIIIIIIII=====>"+path);

                }

                //显示数据

                showListData(imageList);

                toggleShowLoading(false,getString(R.string.common_loading));

            }

        }

        @Override

        public void onLoaderReset(Loader<Cursor> loader) { 

        }  

onCreateLoader() 实例化并返回一个新创建给定ID的Loader对象

onLoadFinished() 当创建好的Loader完成了数据的load之后回调此方法

onLoaderReset() 当创建好的Loader被reset时调用此方法,这样保证它的数据无效

LoaderManager会对查询的操作进行缓存,只要对应Cursor上的数据源没有发生变化,在配置信息发生改变的时候(例如屏幕的旋转),Loader可以直接把缓存的数据回调到onLoadFinished(),从而避免重新查询数据。另外系统会在Loader不再需要使用到的时候(例如使用Back按钮退出当前页面)回调onLoaderReset()方法,我们可以在这里做数据的清除等等操作。

5.ThreadPool

把任务分解成不同的单元,分发到各个不同的线程上,进行同时并发处理。

线程池适合用在把任务进行分解,并发进行执行的场景。

系统提供ThreadPoolExecutor帮助类来帮助我们简化实现线程池。

image4.jpg

使用线程池需要特别注意同时并发线程数量的控制,理论上来说,我们可以设置任意你想要的并发数量,但是这样做非常的不好。因为CPU只能同时执行固定数量的线程数,一旦同时并发的线程数量超过CPU能够同时执行的阈值,CPU就需要花费精力来判断到底哪些线程的优先级比较高,需要在不同的线程之间进行调度切换。

一旦同时并发的线程数量达到一定的量级,这个时候CPU在不同线程之间进行调度的时间就可能过长,反而导致性能严重下降。另外需要关注的一点是,每开一个新的线程,都会耗费至少64K+的内存。为了能够方便的对线程数量进行控制,ThreadPoolExecutor为我们提供了初始化的并发线程数量,以及最大的并发数量进行设置。

    /**

     * 核心线程数

     * 最大线程数

     * 保活时间

     * 时间单位

     * 任务队列

     * 线程工厂

     */

    threadPoolExecutor = new ThreadPoolExecutor(

            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,

            linkedBlockingQueue, sThreadFactory);

    threadPoolExecutor.execute(runnable);

我们知道系统还提供了Executors类中几种线程池,下面我们来看下这些线程池的缺点:

newFixedThreadPool 和 newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。

newCachedThreadPool 和 newScheduledThreadPool:主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM

我们看到这些线程池但是有缺点的,所以具体使用那种方式实现要根据我们的需求来选择。

如果想要避开上面的问题,可以参考OKHttp中线程池的实现,OKHttp中队线程调度又封装了一层,使用安全且方便,有兴趣的可以去看看源码。

三.线程优先级

Android系统会根据当前运行的可见的程序和不可见的后台程序对线程进行归类,划分为forground的那部分线程会大致占用掉CPU的90%左右的时间片,background的那部分线程就总共只能分享到5%-10%左右的时间片。之所以设计成这样是因为forground的程序本身的优先级就更高,理应得到更多的执行时间。

image6.jpg

默认情况下,新创建的线程的优先级默认和创建它的母线程保持一致。如果主UI线程创建出了几十个工作线程,这些工作线程的优先级就默认和主线程保持一致了,为了不让新创建的工作线程和主线程抢占CPU资源,需要把这些线程的优先级进行降低处理,这样才能给帮组CPU识别主次,提高主线程所能得到的系统资源。

在Android系统里面,我们可以通过android.os.Process.setThreadPriority(int)设置线程的优先级,参数范围从-20到24,数值越小优先级越高。Android系统还为我们提供了以下的一些预设值,我们可以通过给不同的工作线程设置不同数值的优先级来达到更细粒度的控制。

image7.jpg

大多数情况下,新创建的线程优先级会被设置为默认的0,主线程设置为0的时候,新创建的线程还可以利用THREAD_PRIORITY_LESS_FAVORABLE或者THREAD_PRIORITY_MORE_FAVORABLE来控制线程的优先级。

二、HandlerThread的使用案例

主要代码如下:

activity_handler_thread.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical" android:layout_width="match_parent"

    android:layout_height="match_parent">

    <ImageView

        android:id="@+id/image"

        android:layout_width="match_parent"

        android:layout_height="match_parent" />

</LinearLayout>

1

2

3

4

5

6

7

8

9

10

HandlerThreadActivity.java

package com.zejian.handlerlooper;

import android.app.Activity;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.os.Bundle;

import android.os.Handler;

import android.os.HandlerThread;

import android.os.Message;

import android.widget.ImageView;

import com.zejian.handlerlooper.model.ImageModel;

import com.zejian.handlerlooper.util.LogUtils;

import java.io.BufferedInputStream;

import java.io.IOException;

import java.net.HttpURLConnection;

import java.net.URL;

/**

 * Created by zejian on 16/9/2.

 */

public class HandlerThreadActivity extends Activity {

    /**

     * 图片地址集合

     */

    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"

    };

    private ImageView imageView;

    private Handler mUIHandler = new Handler(){

        @Override

        public void handleMessage(Message msg) {

            LogUtils.e("次数:"+msg.what);

            ImageModel model = (ImageModel) msg.obj;

            imageView.setImageBitmap(model.bitmap);

        }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_handler_thread);

        imageView= (ImageView) findViewById(R.id.image);

        //创建异步HandlerThread

        HandlerThread handlerThread = new HandlerThread("downloadImage");

        //必须先开启线程

        handlerThread.start();

        //子线程Handler

        Handler childHandler = new Handler(handlerThread.getLooper(),new ChildCallback());

        for(int i=0;i<7;i++){

            //每个1秒去更新图片

            childHandler.sendEmptyMessageDelayed(i,1000*i);

        }

    }

    /**

     * 该callback运行于子线程

     */

    class ChildCallback implements Handler.Callback {

        @Override

        public boolean handleMessage(Message msg) {

            //在子线程中进行网络请求

            Bitmap bitmap=downloadUrlBitmap(url[msg.what]);

            ImageModel imageModel=new ImageModel();

            imageModel.bitmap=bitmap;

            imageModel.url=url[msg.what];

            Message msg1 = new Message();

            msg1.what = msg.what;

            msg1.obj =imageModel;

            //通知主线程去更新UI

            mUIHandler.sendMessage(msg1);

            return false;

        }

    }

    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;

    }

}

  在这个案例中,我们创建了两个Handler,一个用于更新UI线程的mUIHandler和一个用于异步下载图片的childHandler。最终的结果是childHandler会每个隔1秒钟通过sendEmptyMessageDelayed方法去通知ChildCallback的回调函数handleMessage方法去下载图片并告诉mUIHandler去更新UI界面,以上便是HandlerThread常规使用,实际上在android比较典型的应用是IntentService,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值