Android异步技术

技术考量和源码探究

Posted by MrCodeSniper on November 13, 2017

串行:依次执行多个线程任务 。并行:一起执行多个线程任务(或者说给每个线程分配不固定的执行时间)。

前言

Android平台原生就提供了非常丰富的异步处理技术,选择时需要考虑具体业务需求而定 但以下这些都是我们必须考量的范畴

1.尽量使用更少的cpu和内存

2.为应用提供更好的性能与更快的响应

3.实现使用复杂性低

4.代码是否符合规范是否易于维护

正文

Thread

我们知道进程是操作系统资源调度和分配的基本单位 而线程作为进程的组成部分,进程的所有线程共享所在进程的资源 线程作为实际执行任务的基本单元 如何控制如何使用是Android中一个非常重要的课题。

public class Thread implements Runnable {
  .....//实现Runnable接口
}

public  interface Runnable {//Runnable接口规定了执行具体业务逻辑代码的功能
    public abstract void run();
}

而android的thread是基于linux内核的pthreads实现的

创建Thread

1.自定义类继承Thread在run函数中重写具体逻辑

public  MyThread extends Thread{}
MyThread  mt=new MyThread();
mt.start();

2.自定义Runnable 其中包含具体run函数逻辑作为参数传入Thread构造方法并执行

new Thread(Runnable runnable).start();

应用层线程类型

1.主线程

刷新ui,运行android组件

2.后台线程

也叫做工作线程 程序员创建的线程默认为子线程 需要自己实现执行体 在Linux系统中 主线程和后台线程是一样的 是在WindowManager中出现了分歧

3.Binder线程

实现不同进程间线程的通信
例如服务进程的注册服务线程 客户进程的获取服务线程线程 ,ContentProvider数据处理线程等

Thread扩展

1.HandlerThread

HandlerThread是Thread的子类 它的定位为:可以实现异步操作的子线程

我们进源码看看吧(这里只放了有关的代码)

Looper mLooper;//在全局中引入了
protected void onLooperPrepared() {}//提供了looper循环队列前的复写函数
@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        // 内部sThreadLocal.set(new Looper(quitAllowed));
        synchronized (this) {
            mLooper = Looper.myLooper();//return sThreadLocal.get();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
//ps:ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对
//应的副本.此处looper创建并存放在ThreadLocal中
    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;
    }

我们从上面的代码可以看,他在run方法中创建了Looper而Looper中持有MessageQueue的引用,而且提供了创建与此looper关联的handler 这样子线程就具备了处理消息的能力,通过handler可以让主线程向子线程发送信息 实现了异步通信

优点:简单易用实现异步通信 线程安全 缺点:队列中任务可能会出现堵塞

IntentService

我们知道service的各个生命周期都是在主线程运行的,本身不具备异步的功能,为了能使service能处理耗时任务所以引入了IntentService

IntentService是继承了service的抽象类 需要继承并实现抽象类并在onHandleIntent中处理耗时操作,那我们从这个方法入手进入源码看看

private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    
 @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");//创建HandlerThread
        thread.start();

        mServiceLooper = thread.getLooper();//获取子线程关联的looper
        mServiceHandler = new ServiceHandler(mServiceLooper);//将looper与servicehandler关联
    }

IntentService使用了handlerthread,在service启动时会发送消息并执行onHandleIntent((Intent)msg.obj);实现在service中执行耗时任务

2.Executors FrameWork (线程管理框架)

这个框架为android系统提供的功能: 1.创建线程池管理线程 2.检测线程错误 3.批量顺序执行进程 4.等待进程执行完成,获取进程执行结果

它提供的功能能使用户能自定义自己的线程管理工具

这个框架的基础接口Executor接口规定了线程任务的执行 而自定义线程池是以ThreadPoolExecutor为基础

ThreadPoolExecutor tp1= (ThreadPoolExecutor) Executors.newFixedThreadPool(5);//创建固定大小的线程池
ThreadPoolExecutor tp2= (ThreadPoolExecutor)Executors.newCachedThreadPool();//新任务会创建新线程处理 空闲久的线程会销毁 可变大小先吃
ThreadPoolExecutor tp3= (ThreadPoolExecutor)Executors.newSingleThreadExecutor(); //单个线程处理单个任务
ThreadPoolExecutor tp4= (ThreadPoolExecutor)Executors.newScheduledThreadPool(3);//可以延迟执行任务也可以定期执行任务

线程池的使用场景 1.适合执行长期的任务,性能好很多 2.适合执行异步程序比较多的场景 3.适合一个任务一个任务执行的场景 4.适合周期性执行任务的场景

ps:核心线程数:核心线程会一直在线程池中 即使没有任务要处理 即使有空闲线程 也会创建新的线程直到达到核心线程数

AsyncTask(线程管理框架的扩展)

AsyncTask我们非常熟悉的用来处理异步任务的类 是对Executors框架的封装,其实为线程池+handler的组合

new AsyncTask<Void,Void,Void>(){

            @Override
            protected Void doInBackground(Void... voids) {
                //只有这个方法在子线程执行
                return null;
            }


            @Override
            protected void onPostExecute(Void aVoid) {
                super.onPostExecute(aVoid);
            }
        }.execute();

让我们深入源码看看

public abstract class AsyncTask<Params, Progress, Result> {
    ...//AsyncTask 为抽象类,其中有三个泛型。第一个是传递的参数类型,第二个是任务执行的进度,第三个是任务执行的结果类型了。
}

1.线程池

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;//核心线程数为 CPU 的核心数量 + 1
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//最大线程数为 CPU 的核心数量 * 2 + 1
private static final int KEEP_ALIVE = 1;//过剩线程的存活时间为1s

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};

private static final BlockingQueue<Runnable> sPoolWorkQueue=//sPoolWorkQueue阻塞式线程队列最大个数为128即任务处理数+任务等待数>128会出现异常
        new LinkedBlockingQueue<Runnable>(128);

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

2.执行器

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    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);
        }
    }
}

AsyncTask默认执行器即SerialExecutor

可以从 SerialExecutor 的内部看到,是循环地取出 mActive ,并且把 mActive 放置到上面的 THREAD_POOL_EXECUTOR 中去执行

这意味着执行器和任务都是串行一个个运行的

3.Handler

private static InternalHandler sHandler;

private static final int MESSAGE_POST_RESULT = 0x1;
private static final int MESSAGE_POST_PROGRESS = 0x2;

private static class InternalHandler extends Handler {
    public InternalHandler() {
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    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;
        }
    }
}

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

@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
    final AsyncTask mTask;
    final Data[] mData;

    AsyncTaskResult(AsyncTask task, Data... data) {
        mTask = task;
        mData = data;
    }
}

AsyncTask 内部实现的一个 Handler , InternalHandler 的 handleMessage 方法中,根据消息类型分别有不同的处理

执行器处理回结果通过handler发送消息 handler 通过消息类型获取数据进行不同的回调

4.构造器

private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
    
/**
 * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
 */
public AsyncTask() {
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            mTaskInvoked.set(true);

            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //noinspection unchecked
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            return postResult(result);
        }
    };

    mFuture = new FutureTask<Result>(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 occurred while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                postResultIfNotInvoked(null);
            }
        }
    };
}

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

@WorkerThread
protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();//发送
    }
}

private void postResultIfNotInvoked(Result result) {
    final boolean wasTaskInvoked = mTaskInvoked.get();
    if (!wasTaskInvoked) {
        postResult(result);
    }
}

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();//handler发送消息
    return result;
}

private static Handler getHandler() {
    synchronized (AsyncTask.class) {
        if (sHandler == null) {
            sHandler = new InternalHandler();
        }
        return sHandler;
    }
}

在构造器中创建了 WorkerRunnable 和 FutureTask 的对象,而在 WorkerRunnable 内部的 call 方法中会去执行需要我们重写的 doInBackground 方法 接着就是将结果通过handler发送并执行回调了

5.execute方法源码

@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}

@MainThread
public final AsyncTask<Params, Progress, Result> 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;
}

@MainThread
protected void onPreExecute() {
}

在上面中可以看到我们平常使用的 execute 方法会去调用 executeOnExecutor 方法。而在 executeOnExecutor 方法内又会去调用 onPreExecute 方法。这也就是为什么 onPreExecute 方法是在任务开始运行之前调用的原因了。

默认AsyncTask是串行执行任务的,若想并行执行 只要选取并行执行器即可

All.

子线程真的不能更新UI吗?

如果我们在子线程更新ui会出现下面的错误👇

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views 
 at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
 at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
```                                                   
我们跟进代码

```java
void checkThread() {
    if (mThread != Thread.currentThread()) {//这里做了一个判断
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

mThread

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        mDisplayAdjustments = display.getDisplayAdjustments();
        mThread = Thread.currentThread();//此处初始化
        ...
        }

那ViewRootImpl是在何时被初始化的呢?

下面是ActivityThread类下的函数

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
            ...
if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//⬅️深入
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
            ...
}

深入到WindowMangerImpl的addview,继续深入

  @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//⬅️深入
    }

发现他是调用WindowManagerGlobal的方法实现的,最后我们找到了最终实现addView的方法: View的加载最后就是在这里实现的,而ViewRootImpl的实例化也在这里👇

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            ...
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

到此处 我们发现 ViewRootImpl初始化时 是在ActivityThread,ActivityThread作为app代码的入口 是在主线程运行的 即ViewRootImpl持有对主线程的引用 子线程中更新ui因为Thread.currentThread()为子线程 并不是子线程所以报错

那搞清楚了原因,我们能实现在子线程更新ui吗?

我们经过上面的分析已经知道 因为线程的不同导致异常无法添加view 之前的window解析机制我们知道只有通过WindowManager我们才能对window进行操作 那我们可以直接跳过判断直接调用WindowManagerImpl.addview直接将我们的视图加到window上 马上动手试试吧!

 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                TextView tx = new TextView(SecondActivity.this);
                tx.setText("子线程");
                tx.setBackgroundColor(Color.WHITE);
                ViewManager viewManager = SecondActivity.this.getWindowManager();
                WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                        200, 200, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
                        WindowManager.LayoutParams.TYPE_TOAST, PixelFormat.OPAQUE);
                viewManager.addView(tx,layoutParams);
                Looper.loop();
            }
        }).start();

结果ui

成功了!谁说子线程不能更新ui, 但是我们只是在论证一种Android用主线程更新ui有它的理由 大概是因为如果子线程更新UI可能导致线程之间抢夺资源和死锁等线程安全问题而不允许在子线程中更新UI吧

End.

感言

源码真的是一环套一环 学的越多你会发现知识体系是连环着的 正是这些模块连接运作才会有我们如此之棒的系统出现

读源码真的很重要 拿他人的成果为受人以鱼 读他人解析的是学他们的思路 是学之以技 自己读源码才是 受几以渔

希望你我都能做一个真正的捕鱼人。