首页 > 程序开发 > 移动开发 > Android >

Android多线程性能优化(一)

2016-06-06

上面给出了一个最简单的新建线程并执行任务,也是最不好管理的。为什么?run里面的方法很可能很耗时,直接new出来的线程没有一个对象去接收其句柄,连打断都做不到,只能等待内部错误停止或者任务执行完毕。就算不使用匿名类的方式操作

程序开发中,为了让程序表现的更快更流畅,我们会使用多线程来提升应用的并发性能。但多线程并发代码是一个棘手的问题,线程的生命周期处理不好就会造成内存泄漏。

    new Thread(){
        @Override
        public void run() {
            doSomeThing();
        }
    }.start();

上面给出了一个最简单的新建线程并执行任务,也是最不好管理的。为什么?run里面的方法很可能很耗时,直接new出来的线程没有一个对象去接收其句柄,连打断都做不到,只能等待内部错误停止或者任务执行完毕。就算不使用匿名类的方式操作,停止线程光用interrupt()是不够的,该方法只有在线程调用sleep()、join()、wait()交出时间片(阻塞)的时候,它将接收到一个中断异常(InterruptedException),从而提早结束该线程。所以一般都是在run方法里各种操作之前先判断下标志位,是否需要继续执行任务,向外暴露这个标志位的接口,让其变成可控线程。当然普通的thread是满足不了你的,你需要继承thread自行处理标志位的操作,当然如果写成内部类的话最好写成静态内部类,这样避免持有外部类的引用造成泄漏。

经常new Thread().start()的话会加大性能的开销,虽然并发量上去了(不要无止尽的新建线程,处理线程跟cpu核数有关,线程数超出CPU核数过多反而会降低效率,并发得不偿失,因为切换线程是要切换上下文环境的,此操作开销十分大),但是开销变大了。所以管理好线程的个数、复用成为重中之重。

这里写图片描述

你以为这样就结束了?
打出上面那段话我突然想起小时候看的《西游记后传》里的一句经典台词——我还没出力呢,你就趴下了。<喎"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="android多线程性能优化前篇-ui线程源码剖析">Android多线程性能优化前篇-UI线程源码剖析

ps:其实真正的标题在这(>_<:.),其实原本没打算写ui线程的,在整合资料的时候看到了,就顺便写写吧,没想到一写就写那么多…标题都对不上号了,只好修改了

上面说了Thread不足的地方,各位可以思考下有什么解决方案,下篇我再给大家介绍几个方案。

操作UI线程
- Activity.runOnUiThread(Runnable)
- View.post(Runnable) 、 View.postDelay(Runnable , long)
- Handler
- AsyncTask
- IntentService

首先是Handker这个大家天天用应该不用我说了吧

 public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

上面给出了runOnUiThread的源码,看到关键对象mHandler就知道了,当前线程如果不在主线程(UI线程)就调用activity内的全局handler进行UI操作。PS:在我打开源码之前就想到里里面估计也是有一个Handler来执行UI操作,没想到打开居然中标了,感觉最近还是有些许提升的。哈哈,装完逼,我继续讲~

 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

 public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
        return true;
    }

从源码可以看出当attachInfo不为空的时候会调用其内部持有的handler进行UI操作。那AttachInfo是什么鬼呢?

    /**
     * A set of information given to a view when it is attached to its parent
     * window.
     */
    final static class AttachInfo{
    //...
    }
    //在View中找到该终类,注释说明将一组信息附于父窗口
/**
         * Creates a new set of attachment information with the specified
         * events handler and thread.
         *
         * @param handler the events handler the view must use
         */
        AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;
        }

看到该构造就能知道很多东西了,会在别的地方传进一个handler,并且这个是ui线程的,为什么,这是根据注释猜测其来源应该是一个父视图(不是UI线程早就报错了>_<),在View中是找不到这个构造的调用的,既然是父视图那就找找看吧,ViewParent是个接口pass,ViewGroup也找不到pass,并且View和ViewGroup里的注释也都没有线索,接着又是各种搜跟视图的Window、PhoneWindow、DecorView等地方全都找不到,后面找着找着又回到了我之前找到的ViewRootImpl的这个类,打算后面一些写的,没想到居然在这里找到了。我的泪啊犹如西湖的水~这里写图片描述

ViewRootImpl首先这个类在正常情况下是看不到的,为什么?因为@hide 了呗,被隐藏起来了,我怎么找到的呢?打开sdk目录下的sources(前提是你有下源码),找到对应的api的目录里,打开搜索搜索类名,哈哈这样以后遇到打不开源码找不到的类你也可以用这种方法试试了,说不定就隐藏在源码中,至于怎么在ide中查看,我看到有人改源码吧@hide去掉,然后再编成android.jar 哈哈 这样太累了,多人开发的话 别人没有这个jar是要报错的(直接使用一些hide了的类),反射的话就没问题。至于其他方法求大神告知~(我猜想应该有这类插件把@hide字符串过滤的类显示出来)

public ViewRootImpl(Context context, Display display) {
    //...
     mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
    //...
}

final class ViewRootHandler extends Handler {
    //...
}
final ViewRootHandler mHandler = new ViewRootHandler();

在构造ViewRootImpl的时候就会new AttachInfo并把信息放入,在后面看到了mHandler的实现,继承了Handler,构造并没有修改,按照前一篇介绍Handler的文章里提到的,Handler的默认构造获取当前线程的Looper进行关联,从ViewRootImpl的构造和传入的Context就可以知道这肯定是运行在UI线程的构造。

接下来就是ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
RunQueue这个也在ViewRootImpl内部,里面维护了一个HandlerAction的ArrayList,HandlerAction这个里面封装的是runnable action 和 long delayMillis,就是一个任务。postDelayed()就是把任务加入到集合中,这个add操作是synchronized块进行同步的。
void executeActions(Handler handler)这个方法传入一个handler用于执行集合中的任务。PS:至于源码我就不贴了,让大家自个动手试试搜hide的类。

AsyncTask
这个类想必大家都很熟悉了吧,我做Android以来第一个接触的前后台都能耍的线程就是它,不过后来就很少用了,至于原因代码书写起来比较麻烦,看着不够优雅,生命周期管理麻烦每个AsyncTask都需要处理等原因(ps:说白了你就是懒!为什么不直说呢)。

先来看看其需要重写的几个方法


protected abstract Result doInBackground(Params... params);

protected void onPreExecute() {
    }

protected void onProgressUpdate(Progress... values) {
    }


protected void onPostExecute(Result result) {
    }   

doInBackground作为AsyncTask必须重写的关键函数,该方法运行于后台线程。

//于AsyncTask构造函数中
mWorker = new WorkerRunnable() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

//设置该线程优先级为后台线程
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }

private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult(this, result));
        message.sendToTarget();
        return result;
    }

mFuture = new FutureTask(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occured while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };

WorkerRunnable只是一个实现了Callable接口的抽象类(然而实际实现推迟到了子类)。
FutureTask是一个实现了RunnableFuture,看名字就知道跟Runnable有关,其继承了Runnable, Future这两个接口。new FutureTask时把Callable放入用作为全局参数,在run中调用Callable.call方法来实现线程启动是运行call,即运行doInBackground。

又看到Handler类真是亲切啊(怎么到处都有你,谁让我要更新UI呢…)
该Handler只是重写了handleMessage方法,调用AsyncTask各方法。

public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }

启动AsyncTask

public final AsyncTask execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

public final AsyncTask executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

在调用execute方法执行时会调用静态的全局线程池来执行该方法,在执行之前运行onPreExecute(),然后才在线程池中执行mFuture即Runnable。

在duInBackgound中调用pushlishProgress方法,通过handler执行UI操作

protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult
(this, values)).sendToTarget(); } }

AsyncTaskResult中持有AsyncTask对象

在初始化WorkerRunnable时实现的Call函数中return postResult(doInBackground(mParams));
即doInBackground执行之后执行postResult。postResult也没做什么就是用handler发了一个消息而已。

private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult(this, result));
        message.sendToTarget();
        return result;
    }

收到消息的handler回去执行result.mTask.finish(result.mData[0]);

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

isCancelled()其实就是一个停止运行的标记位而已,就跟停止Thread一样,调用cancel(boolean mayInterruptIfRunning)来停止线程。

public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }

mayInterruptIfRunning这个参数是用来判断是否使用interrupt()方法停止doInBackground。

onCancelled(result)这个函数看名字就很明显了,生命周期函数,执行时机上面的源码已经有涉及了,停止线程后,finish中调用。

至此AsyncTask你必须知道的流程实现也就大概清楚了。

作为一个轻量级的异步线程,其优点我就不说了,说说缺点。

首先是AsyncTask不可重用。从写法来看就知道了execute();每个AsyncTask对象只能调用一次execute(),否者就会抛异常

 public final AsyncTask executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }
    //...
    }   

public static void execute(Runnable runnable) {
        sDefaultExecutor.execute(runnable);
    }

execute(Runnable command)该方法是静态的,不需要实例调用(刚才谁说的只能调用一次的?),然而这个方法我不推荐,因为线程不可控,不能打断。
sDefaultExecutor这个是什么?Executor 是个接口,是线程池的基础接口,里面的只有一个execute(Runnable command)方法,用来执行任务。线程池是什么?字面意思就是很多线程在其中的一个容器,至于具体的由下一篇来解释。
exec.execute(mFuture);AsyncTask任务最终执行的地方就是这个Executor 的实现类中。

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

该线程池是全局的,也就是说不管有多少个AsyncTask都是在这个线程池中执行。

private static class SerialExecutor implements Executor {
        final ArrayDeque mTasks = new ArrayDeque();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

SerialExecutor 的源码已经暴露了一切,任务可以添加多个,可是永远只能执行一个,并发不了多个。所以虽然AsyncTask的execute(Runnable runnable)的静态方法可以调用多次也不用书写繁重的AsyncTask然而人家并发量始终如一。

当然还有其他方法可以实现并行任务

 public final AsyncTask executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

那就是传入一个线程池,让任务运行在这个线程池里(但这个函数不是静态的需要实例调用,而且从源码上看也是不能二次使用的)…我自己用个线程池那我还用AsyncTask干嘛呢,Executor + Handler不就好了。

其他缺点就是持有外部引用容易造成内存泄漏,生命周期不好管理等问题。


其实开始并不打算写多少AsyncTask的,之前也没看过其源码,前面剖析了源码后,AsyncTask就停不下来了..>_<..

IntentService
剖析了这么多源码我想你们也发现了,只要是更新UI的操作,必i定会Handler的参与,这个就留给你们自行剖析,或者等我哪天开了Service再带你们飞

相关文章
最新文章
热点推荐