基本线程实现(Thread & Runnable)
- 继承Thread类
Thread类是Java中实现多线程的具体类,封装了所需线程操作。在Android开发中用于实现多线程
// 步骤1:自定义线程类(继承自Thread类)
class MyThread extends Thread{
// 步骤2:复写run(),定义线程的行为
@override
public void run(){...//定义线程行为
}
}
// 步骤3:实例化线程对象
MyThread mt = new MyThread("thread_1");
// 步骤4:通过线程对象控制线程状态,如运行start、睡眠sleep、停止stop……
mt.start(); // 开启线程
- 实现Runnable接口
一个与多线程相关的抽象接口,仅定义1个方法=run(),在Android开发中用于实现多线程
// 步骤1:创建线程辅助类,实现Runnable接口
class MyThread implements Runnable{
// 步骤2:复写run(),定义线程行为
public void run(){}
}
// 步骤3:创建线程辅助对象
MyThread mt = new MyThread();
// 步骤4:创建线程对象,并传入线程辅助类对象
// Runnable接口没有对线程的支持,必须创建线程Thread类的实例,由Thread创建的线程执行线程行为
Thread td = new Thread(mt);
// 步骤5:通过线程对象控制线程状态,如运行start、睡眠sleep、停止stop……
// 当调用start()方法时,线程对象会自动回调线程辅助类对象的run()
td.start();
- 实现Callable接口
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。使用FutureTask类来包装Callable对象,通过调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
public class CallableThreadTest implements Callable<Integer> {
public static void main(String[] args) {
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
if (i == 20) {
new Thread(ft, "有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:" + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
}
线程池(ThreadPoolExecutor)
简介 & 优势
线程的创建和销毁,都涉及到系统调用,消耗系统资源,所以就引入了线程池技术,避免频繁的线程创建和销毁
Java使用Executors接口表示线程池,具体实现类是ThreadPoolExecutor
- 通过复用缓存在线程池中的线程 降低 线程创建&销毁 造成的性能开销。
- 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 防止线程并发数量过多,抢占系统资源从而导致阻塞。
- 提高线程的可管理性。使用线程池可以对线程进行统一的分配,调优和监控,如延时执行,定时循环执行等。
使用
// 线程池的构造
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
ExecutorService service = new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
// 线程池的使用
// 可以通过execute和submit两种方式来向线程池提交一个任务。
// execute()方法
// 使用execute来提交任务时,由于execute方法没有返回值,所以说我们也就无法判定任务是否被线程池执行成功。
service.execute(new Runnable() {
public void run() {
System.out.println("execute方式");
}
});
// submit()方法
// 使用submit来提交任务时,它会返回一个future,我们就可以通过这个future来判断任务是否执行成功,还可以通过future的get方法来获取返回值。
// 如果子线程任务没有完成,get方法会阻塞住直到任务完成
// 而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时候有可能任务并没有执行完。
Future<Integer> future = service.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("submit方式");
return 2;
}
});
try {
Integer number = future.get();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 线程池关闭
// 调用线程池的shutdown()或shutdownNow()方法来关闭线程池
// shutdown原理:将线程池状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
// shutdownNow原理:将线程池的状态设置成STOP状态,然后中断所有任务(包括正在执行的)的线程,并返回等待执行任务的列表。
工作原理
线程池的工作原理与源码解读
【细谈Java并发】谈谈线程池:ThreadPoolExecutor
核心参数
ThreadPoolExecutor参数最全的构造方法(根据需求配置参数)
// 构造函数源码分析
public ThreadPoolExecutor (int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory
RejectedExecutionHandler handler)
参数 | 定义 | 说明 |
---|---|---|
corePoolSize | 核心线程数 | 线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程 核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态) |
maximumPoolSize | 线程池所能容纳最大线程数 | 线程总数 = 核心线程数 + 非核心线程数 当线程总数达到该数值之后,新任务会被阻塞 |
keepAliveTime | 非核心线程 限制超时时长 | 一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁 |
unit | 指定keepAliveTime参数的时间单位 | 枚举类型,keepAliveTime的单位,常用TimeUnit.MILLSECONDS毫秒、TimeUnit.SECOND秒、TimeUnit.MINUTE分 |
workQueue | 任务队列 | 维护着等待执行的Runnable对象 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务 |
threadFactory | 线程工厂 | 在线程池创建新线程的方式,这是一个接口。 实例化时需要实现他的Thread newThread(Runnable r)方法 |
handler | 用于抛出异常 |
源码分析
/**
* 在未来的某个时刻执行给定的任务。这个任务用一个新线程执行,或者用一个线程池中已经存在的线程执行
* 如果任务无法被提交执行,要么是因为这个Executor已经被shutdown关闭,要么是已经达到其容量上限,任务会被当前的RejectedExecutionHandler处理
*/
public void execute(Runnable command) {
int c = ctl.get();
// 1. 如果当前运行线程数< corePoolSize,则开启一个线程执行命令Command
// Command为该线程执行的第一个命令
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 如果当前线程数>= corePoolSize,则将任务添加到workQueue
if (isRunning(c) && workQueue.offer(command)) {
// 线程入队成功,再次检验校验位(线程池在入队后状态可能会发生变化)
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);// shutdown,则线程池不再接受新任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);// 当前运行线程数< corePoolSize
}
// 3. 如果放入workQueue失败(队列满了),则开启一个新的线程执行任务
else if (!addWorker(command, false))
// 4. 创建线程失败(当前线程数>= maxmumPoolSize || shutdown),调用reject拒绝接受任务
reject(command);
}
/* 补充 */
// addWorker
// addWorker方法的主要工作是在线程池中创建一个新的线程并执行(如果满足线程池状态和界限),firstTask参数 用于指定新增的线程执行的第一个任务。
private boolean addWorker(Runnable firstTask, boolean core){
...
w = new Worker(firstTask);
final Thread t = w.thread;
...
t.start(); //启动时会调用Worker类中的run方法,Worker本身实现了Runnable接口,所以一个Worker类型的对象也是一个线程。
}
// Worker.class
// 线程池中的每一个线程被封装成一个Worker对象,ThreadPool维护的其实就是一组Worker对象
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
...
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
// 从execute方法开始,Worker使用ThreadFactory创建新的工作线程
this.thread = getThreadFactory().newThread(this);
}
public void run() {
// 调用runWorker方法执行
runWorker(this);
}
...
}
// runWorker
final void runWorker(Worker w) {
// runWorker通过getTask不断从 阻塞队列WorkQueue 获取任务,然后执行任务
// 如果getTask返回null,进入processWorkerExit方法,整个线程结束
...
while (task != null || (task = getTask()) != null){
....
task.run();
}
}
总结:
- 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务;
- 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中;
- 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务;
- 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
AsyncTask
菜鸟教程——AsyncTask
Android面试系列文章2018之Android部分AsyncTask机制篇
是什么?能解决什么问题?
一个Android已封装好的轻量级异步类,属于抽象类,使用时需要实现子类。
它本质上是一个封装了 线程池 和 Handler 的异步框架。
线程池:缓存线程+复用线程,避免频繁创建 & 销毁线程 所带来的系统开销
用于:
- 异步任务,如在工作线程中执行耗时任务
- 消息传递,如实现工作线程 & 主线程 之间通信,即将工作线程处理结果传递给主线程,并在主线程中执行相关UI操作
- 和Handler一样用于处理异步任务,不过相对于前者,AsyncTask代码量更为轻量级,且后台是一个线程池,在异步任务数据比较庞大时更有优势。且使用更为简便、快捷。
三个泛型参数作用 & 四个方法?每个方法在哪个线程执行?
- 三个参数
当定义一个类来继承AsyncTask这个类时,需要为其指定3个泛型参数,用来控制AsyncTask子类执行线程各个任务时各个阶段的返回类型。
public abstract class AsyncTask<Params, Progress, Result> {
...
}
参数 | 说明 |
---|---|
Params | 开始异步任务执行时传入的参数类型,对应execute(params)中传递的参数 |
Progress | 异步任务执行过程中,返回下载进度值的类型 |
Result | 异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致 |
不需要指定类型时可以写成void
2. 四个方法
在主线程中执行异步任务时myAsyncTask.execute(params)时,AsyncTask会按照如下四个步骤分别执行
方法名 | 作用 | 调用时期 | 所在线程 | 说明 |
---|---|---|---|---|
onPreExecute() | 执行异步任务前的操作 | 执行 异步任务前自动调用 | 主线程 | 用于UI组件初始化操作,如显示进度条对话框 |
*doInBackground(Params params) | 执行异步任务(接收输入参数并返回异步任务执行结果) | onPreExecute执行结束后,开始执行 异步任务时自动调用 | 子线程(后台线程池中开启一个工作线程执行) | 执行网络请求等耗时操作 |
onProgressUpdate(Progress values) | 在主线程中显示 工作线程任务执行的进度 | 当任务状态发生变化时(通过publishProgress方法)自动调用 | 主线程 | 在doInBackground中调用publishProgress(Progress) 的方法来将我们的进度实时传递给 onProgressUpdate 方法来更新 |
onPostExecute(Result result) | 接收异步任务执行结果,并将结果显示到UI组件 | 异步任务执行结束时自动调用 | 主线程 | 显示异步任务处理结果 |
- 基本使用
- 创建AsyncTask子类,为3个泛型参数指定类型;若不使用,可用void类型代替。
private class MyTask extends AsyncTask<String, Integer, String> {
// 方法1:onPreExecute()
// 作用:执行 线程任务前的操作
@Override
protected void onPreExecute() {
text.setText("加载中");
// 执行前显示提示
}
// 方法2:doInBackground()
// 作用:接收输入参数、执行任务中的耗时操作、返回 线程任务执行的结果
// 此处通过计算从而模拟“加载进度”的情况
@Override
protected String doInBackground(String... params) {
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) {
progressBar.setProgress(progresses[0]);
text.setText("loading..." + progresses[0] + "%");
}
// 方法4:onPostExecute()
// 作用:接收线程任务执行结果、将执行结果显示到UI组件
@Override
protected void onPostExecute(String result) {
// 执行完毕后,则更新UI
text.setText("加载完毕");
}
// 方法5:onCancelled()
// 作用:将异步任务设置为:取消状态
@Override
protected void onCancelled() {
text.setText("已取消");
progressBar.setProgress(0);
}
}
- 创建Async子类的实例对象(任务实例)。必须在UI线程中创建,且同一个AsyncTask实例对象只能执行1次,若执行第2次会抛出异常。
MyTask mTask = new MyTask();
- 手动调用execute()从而执行异步线程任务,必须在UI线程中调用
mTask.execute();
实现原理?
黑马视频:AsyncTask 源码
Carson_Ho:AsyncTask的原理 及其源码分析
AsyncTask基本使用
public class MyAsyncTask extends AsyncTask<String,Integer,Bitmap>{
@Override
protected void onPreExecute() {
super.onPreExecute();
//这里是在异步操作之前执行,运行在UI线程,一般显示给用户:此时即将要去加载图片了
}
@Override
protected Object doInBackground(java.lang.String... strings) {
//这里执行耗时操作,运行在子线程,如网络请求图片的操作
return null;
}
@Override
protected void onProgressUpdate(Integer... values) {
//此方法运行于UI线程,一般用来更新进度
//progressBar.setProgress(values[0]);
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Object result) {
//此方法中result是doInBackground执行完成后返回的,而且此方法运行在UI线程,更新UI
super.onPostExecute(result);
}
}
MyAsyncTask.execute("params"); // AsyncTask调用execute,开始执行异步任务
手动调用execute(Params… params),开始执行异步任务
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
// sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
// execute只能调用一次,若要多次执行任务,需创建新的AsyncTask
// 1. 判断AsyncTask当前执行状态
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;
// 2.onPreExecute() 方法在主线程中运行
// 主线程中调用execute方法,execute中调用onPreExecute(),因此该方法也在主线程中运行
onPreExecute();
// 3.添加参数到任务中
// mWorker.mParams 保存了execute(params)方法中的参数
// *补充1 mWorker 可理解为当前任务对象
// mWorker 为 Callable 类型对象,实例化时复写call()方法,调用doInBackground(params)
// mWorker 实例化 --> Async构造方法
mWorker.mParams = params;
// 4.执行任务
// #补充2:mFutrue 可理解为当前任务的包装对象
// mFuture 继承自 FutureTask
// FutureTask 为Runnable 类型对象,保存callable类型变量(mWorker)
// 实例化复写run方法,执行mWorker.call(),调用doInBackground(params)
// 即在线程池的子线程中执行doInBackground,因此doInBackground在子线程中执行
// *补充3:
// 此处的exec = sDefaultExecutor = 任务队列 线程池类(SerialExecutor)的对象
// 从线程池中取线程 mFuture 并执行mFuture的run方法
exec.execute(mFuture);
return this;
}
// 补充1 WorkerRunnable类的构造函数
// private final WorkerRunnable<Params, Result> mWorker;
// private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
// // Callable也是任务;与Runnable区别:Callable<T>存在返回值=其泛型
// Params[] mParams;
// }
// 补充2 FutureTask类的构造函数(一个包装任务的包装类)
// private final FutureTask<Result> mFuture;
// public class FutureTask<V> implements RunnableFuture<V> {
// public FutureTask(Callable<V> callable) {
// if (callable == null)
// throw new NullPointerException();
// this.callable = callable;
// this.state = NEW;
// }
// public void run() {
// ... 调用callable(mWorker)的call方法,执行doInBackground,并活动返回值
// result = c.call();
// ... 处理返回值,set(result)内部最终会调用FutureTask的done()
// set(result);
// }
// public interface RunnableFuture<V> extends Runnable, Future<V>
// 补充3:
/**
* 分析1:exec.execute(mFuture)
* 说明:属于任务队列 线程池类(SerialExecutor)的方法
*/
private static class SerialExecutor implements Executor {
// SerialExecutor = 静态内部类
// 即 是所有实例化的AsyncTask对象公有的
// SerialExecutor 内部维持了1个双向队列;
// 容量根据元素数量调节
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
// execute()被同步锁synchronized修饰
// 即说明:通过锁使得该队列保证AsyncTask中的任务是串行执行的
// 即 多个任务需1个个加到该队列中;然后 执行完队列头部的再执行下一个,以此类推
public synchronized void execute(final Runnable r) {
// 将实例化后的FutureTask类 的实例对象传入
// 即相当于:向队列中加入一个新的任务
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();->>分析2
}
}
});
// 若当前无任务执行,则去队列中取出1个执行
if (mActive == null) {
scheduleNext();
}
}
// 分析2
protected synchronized void scheduleNext() {
// 1. 取出队列头部任务
if ((mActive = mTasks.poll()) != null) {
// 2. 执行取出的队列头部任务
// 即 调用执行任务线程池类(THREAD_POOL_EXECUTOR)
/**
* 源码分析:THREAD_POOL_EXECUTOR.execute()
* 说明:
* a. THREAD_POOL_EXECUTOR实际上是1个已配置好的可执行并行任务的线程池
* b. 调用THREAD_POOL_EXECUTOR.execute()实际上是调用线程池的execute()去执行具体耗时任务
* c. 而该耗时任务则是初始化WorkerRunnable实例对象时复写的call()
*/
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
AsyncTask构造方法
- 创建了1个WorkerRunnable类 的实例对象 & 复写了call()方法
- 创建了1个FutureTask类 的实例对象 & 复写了 done()方法
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
public AsyncTask() {
// 1. 初始化WorkerRunnable变量mWorker = 一个可存储参数的Callable对象
// 复写call()方法,调用doInBackground(params)
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// 执行异步操作 = 耗时操作,即 我们使用过程中复写的耗时任务
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
// 2. 初始化FutureTask变量 = 1个FutureTask
mFuture = new FutureTask<Result>(mWorker) {
// 结束doInBackground(params)后获得返回值会调用done
// done()简介:FutureTask内的Callable执行完后的调用方法
@Override
protected void done() {
try {
// get()函数获取返回值result
// *postResultIfNotInvoked调用postResult(result)
// 即将执行完成的任务结果通过InternalHandler传递到UI进程
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);
}
}
};
}
// 任务的结果result和任务标识MESSAGE_POST_RESULT通过InternalHandler传递到UI线程
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
// 创建Handler对象 ->> 源自InternalHandler类
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
// 发送消息到Handler中
message.sendToTarget();
return result;
}
InternalHandler 接收 子线程 发送消息
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
// Handler 实现
// onPostExecute(result)运行在主线程
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
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:
// 若收到的消息 = MESSAGE_POST_RESULT
// 则通过finish() 将结果通过Handler传递到主线程
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// 若收到的消息 = MESSAGE_POST_PROGRESS
// 则回调onProgressUpdate()通知主线程更新进度的操作
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
源码流程图
总结
- AsyncTask派生出的子类可以实现不同的异步任务,每个异步任务均提交到线程池中执行,且只能执行一次(执行多次会出现异常)
- 需要执行一个异步任务时,AsyncTask在主线程中调用execute方法。execute中会首先调用onPreExecute,因此这个方法在主线程中执行。
- 接着,AsyncTask从线程池中取处一个子线程执行doInBackground方法来执行异步的任务,因此这个方法在子线程中执行。
- 任务执行完成后,AsyncTask会获取结果后并通过Handler向主线程发送消息,AsyncTask内部的InternalHandler收到消息并调用onPostExecute,因此这个方法在主线程中执行。
不足之处 & 解决方法?
- 内存泄漏:
如果AsyncTask被声明为Activity的非静态的内部类,那么AsyncTask会保留一个对创建了AsyncTask的Activity的引用。如果Activity已经被销毁,AsyncTask的后台线程还在执行,它将继续在内存里保留这个引用,导致Activity无法被回收,引起内存泄露。
解决方法很简单,让内部持有外部的弱引用即可解决 - 生命周期
在Activity的onDestory()中及时对AsyncTask进行回收,调用其cancel()方法来保证程序的稳定性。 - 结果丢失
当屏幕旋转或内存不足时,当前的Activity被回收,如果此时AsyncTask被声明为Activity的非静态内部类,由于AsyncTask持有的是回收之前Activity的引用,导致AsyncTask更新的结果对象为一个无效的Activity的引用,这就是结果丢失。 - 并行或串行
在1.6(Donut)之前: 在第一版的AsyncTask,任务是串行调度。一个任务执行完成另一个才能执行。由于串行执行任务,使用多个AsyncTask可能会带来有些问题。所以这并不是一个很好的处理异步(尤其是需要将结果作用于UI试图)操作的方法。1.6-2.3: 所有的任务并发执行,这会导致一种情况,就是其中一条任务执行出问题了,会引起其他任务出现错误。3.0之后AsyncTask又修改为了顺序执行,并且新添加了一个函数 executeOnExecutor(Executor),如果您需要并行执行,则只需要调用该函数,并把参数设置为并行执行即可。
Handler
子线程一定不能更新UI吗?为什么Android系统不建议子线程访问UI?
为什么不能在子线程中更新UI?
子线程可以更新UI(在系统还未检测当前更新UI的线程是否是UI线程之前执行操作)
谷歌提出:“UI更新一定要在UI线程里实现” 这一规则原因如下:
目的在于提高移动端更新UI的效率和和安全性,以此带来流畅的体验。原因是:
Android的UI访问是没有加锁的,多个线程可以同时访问更新操作同一个UI控件。也就是说访问UI的时候,android系统当中的控件都不是线程安全的,这将导致在多线程模式下,当多个线程共同访问更新操作同一个UI控件时容易发生不可控的错误,而这是致命的。
所以Android中规定只能在UI线程中访问UI,这相当于从另一个角度给Android的UI访问加上锁,一个伪锁。
定义 & 作用 & 六大概念
- 定义
一种Android消息传递/异步通信机制 - 作用
在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,保证多线程并发更新UI时 线程安全,实现异步消息的处理。 - 六大概念
概念 | 定义 | 作用 | 说明 |
---|---|---|---|
主线程/UI线程 Main Thread | 当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程 | 用于处理UI相关的事件 | Android OS中,一个进程被创建之后,同时会自动开启一条主线程(当前Activity),主线程创建一个Looper和一个MessageQueue |
子线程/工作线程 Work Thread | 手动开启的线程 | 用于执行耗时操作,如网络请求、数据加载等 | |
消息 Message | 线程间通讯的数据单元(Handler 发送 & 响应的消息对象) | 存储子线程发送给UI线程的通信信息 | |
消息队列 Message Queue | 用来存放Message对象的数据结构 | 用来存放Handler发送过来的消息,不按照FIFO规则执行,而是将Message以单链表的方式串联起来的(适用于插入消息MessageQueue.enqueue和取出MessageQueue.next),等待Looper的抽取 维护所有顶层应用对象(Activities, Broadcast receivers等)以及主线程创建的窗口 | MessageQueue对象不需要手动创建,而是由Looper对象对其进行管理,一个线程最多只可以拥有一个MessageQueue |
循环器 Looper | MessageQueue的管理者 MessageQueue与Handler通信媒介 | 消息循环,包括 消息获取:循环取出消息队列MessageQueue中消息 消息分发:将取出的消息发送给对应的处理者Handler | 在一个线程中,如果存在Looper对象,则必定存在MessageQueue对象,并且只存在一个Looper对象和一个MessageQueue对象。在Android系统中,除了主线程有默认的Looper对象,其它线程默认是没有Looper对象。如果想让我们新创建的线程拥有Looper对象时,我们首先应调用Looper.prepare()方法,然后再调用Looper.loop()方法 |
处理者 Handler | 消息的处理者 主线程与子线程的通信媒介 | Handler的作用是把消息加入特定(主线程)的消息队列中:Handler.sendMessage 处理Looper分发过来的消息:Handler.dispatchMessage |
MessageQueue,Handler和Looper三者之间的关系
一个线程Thread绑定一个循环器Looper和一个消息队列MessageQueue,对应多个处理者Handler。MessageQueue可以存放来自不同Handler发送的消息,Looper可以将消息分发给对应的Handler进行处理。
使用
步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
步骤2:在主线程中创建Handler实例
步骤3:创建工作线程(AsyncTask、Thread、Runnable)处理耗时操作,并创建需要发送的消息对象Message,并通过引用主线程的Handler发送
步骤4:开启工作线程
Handler使用方式 因发送消息到消息队列的方式不同而不同,共分为2种:使用Handler.sendMessage()、使用Handler.post()
- sendMessage(Message msg) 发送一个消息对象到消息队列
public class MainActivity extends AppCompatActivity {
public TextView mTextView;
public Handler mHandler;
// 步骤1:(自定义)新创建Handler子类(继承Handler类) & 复写handleMessage()方法
class Mhandler extends Handler {
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
// 根据不同线程发送过来的消息,执行不同的UI操作
// 根据 Message对象的what属性 标识不同的消息
switch (msg.what) {
case 1:
mTextView.setText("执行了线程1的UI操作");
break;
case 2:
mTextView.setText("执行了线程2的UI操作");
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.show);
// 步骤2:在主线程中创建Handler实例
mHandler = new Mhandler();
// 采用继承Thread类实现多线程演示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 步骤3:创建所需的消息对象
Message msg = Message.obtain();
msg.what = 1; // 消息标识
msg.obj = "A"; // 消息内存存放
// 步骤4:在工作线程中 通过Handler发送消息到消息队列中
mHandler.sendMessage(msg);
}
}.start();
// 步骤5:开启工作线程(同时启动了Handler)
// 此处用2个工作线程展示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通过sendMessage()发送
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
mHandler.sendMessage(msg);
}
}.start();
}
}
- post(Runnable r) 将一个线程加入线程队列。
- post不需要外部创建消息对象,而是内部根据传入的Runnable对象封装消息对象并通过sendMessageDelayed(getPostMessege®)放入消息队列。并通过复写Runnable对象的run()通过回调处理消息。
- 本质上,post内部是使用sendMessage实现,他们本质上没有区别。post使用更为简单。
public class MainActivity extends AppCompatActivity {
public TextView mTextView;
public Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.show);
// 步骤1:在主线程中创建Handler实例
mHandler = new Handler();
// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容
new Thread() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通过psot()发送,需传入1个Runnable对象
mHandler.post(new Runnable() {
@Override
public void run() {
// 指定操作UI内容
mTextView.setText("执行了线程1的UI操作");
}
});
}
}.start();
// 步骤3:开启工作线程(同时启动了Handler)
// 此处用2个工作线程展示
new Thread() {
@Override
public void run() {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText("执行了线程2的UI操作");
}
});
}
}.start();
}
}
Android 消息机制(工作原理 & 源码分析)
- 工作原理
Handler 机制的工作流程主要包括4个步骤:异步通信准备 → 消息发送 → 消息循环 → 消息处理
步骤 | 说明 | 备注 |
---|---|---|
异步通信准备 | 在主线程中创建 循环器Looper 对象、消息队列MessageQueue 对象、Handler 对象 | 三者均位于主线程 当MessageQueue创建后,Looper自动进入消息循环 此时Handler 自动绑定了 Looper 和 MessageQueue |
消息发送 | 工作线程 通过Handler 发送消息Message 到消息队列MessageQueue中 | 消息内容一般是UI操作 发送消息通过Handler.sendMessage(Message msg)和Handler.post(Runnable r)发送 入队一般通过MessageQueue.enqueueMessage(Message)处理 |
消息循环 | 包括 消息出队 和 消息分发 两个步骤 消息出队:Looper循环取出消息队列MessageQueue中的消息Message 消息分发:Looper将取出的消息Message发送给创建消息的处理者Handler | 如果在消息循环的过程中,消息队列MessageQueue为空队列时,线程阻塞 |
消息处理 | 消息处理者Handler 接受 Looper 发送过来的消息Message,并根据Messge进行UI操作 |
- 源码分析
黑马:Handler源码分析
Carson_Ho:深入详解Handler机制源码
Handler.sendMessage(Message msg)
基本用法
/**
* 此处以 匿名内部类 的使用方式为例
*/
// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
private Handler mhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
};
// 步骤2:创建消息对象
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
// 多线程可采用AsyncTask、继承Thread类、实现Runnable
mHandler.sendMessage(msg)
- 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
- 当创建Handler对象时,则通过 构造方法 自动关联当前线程的Looper对象 & 对应的消息队列对象(MessageQueue),从而 自动绑定了 实现创建Handler对象操作的线程
- 实例化Handler对象需要复写handleMessage(Message msg)方法,对Looper分发的Message进行处理
/**
* 具体使用
*/
private Handler mhandler = new Handler(){
// 通过复写handlerMessage()从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需执行的UI操作
}
};
/**
* 源码分析:Handler的构造方法
* 作用:初始化Handler对象 & 绑定线程
* 注:
* a. Handler需绑定 线程才能使用;绑定后,Handler的消息处理会在绑定的线程中执行
* b. 绑定方式 = 先指定Looper对象,从而绑定了 Looper对象所绑定的线程(因为Looper对象本已绑定了对应线程)
* c. 即:指定了Handler对象的 Looper对象 = 绑定到了Looper对象所在的线程
*/
public Handler() {
this(null, false);
// ->>分析1
}
/**
* 分析1:this(null, false) = Handler(null,false)
*/
public Handler(Callback callback, boolean async) {
...// 仅贴出关键代码
// 1. 指定Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// Looper.myLooper()作用:获取当前线程的Looper对象;若线程无Looper对象则抛出异常
// 即 :若线程中无创建Looper对象,则也无法创建Handler对象(并抛出异常)
// 故 若需在子线程中创建Handler对象,则需先创建Looper对象
// 主线程中会自动创建Looper对象
// 2. 绑定消息队列对象(MessageQueue)
mQueue = mLooper.mQueue;
// 获取该Looper对象中保存的消息队列对象(MessageQueue)
// 至此,保证了handler对象 关联上 Looper对象中MessageQueue
}
- 步骤1前隐式操作:创建循环器Looper & 循环队列MessageQueue 并进行 消息循环
- 创建循环器Looper & 循环队列MessageQueue
/**
* 源码分析1:Looper.prepare()
* 作用:为当前线程(子线程) 创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue)
* 注:需在子线程中手动调用该方法
*/
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 1. 判断sThreadLocal是否为null,否则抛出异常
//即 Looper.prepare()方法不能被调用两次 = 1个线程中只能对应1个Looper实例
// 注:sThreadLocal = 1个ThreadLocal对象,是线程本地存储区,用于存储线程的变量
sThreadLocal.set(new Looper(true));
// 2. 若为初次Looper.prepare(),则创建Looper对象 & 存放在ThreadLocal变量中
// 注:Looper对象是存放在Thread线程里的
// 源码分析Looper的构造方法->>分析a
}
/**
* 分析a:Looper的构造方法
* 创建Looper 同时会自动创建一个消息队列对象MessageQueue
**/
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
// 1. 创建1个消息队列对象(MessageQueue)
// 即 当创建1个Looper实例时,会自动创建一个与之配对的消息队列对象(MessageQueue)
mRun = true;
mThread = Thread.currentThread();
}
/**
* 源码分析2:Looper.prepareMainLooper()
* 作用:为 主线程(UI线程) 创建1个循环器对象(Looper),同时也生成了1个消息队列对象(MessageQueue)
* 注:该方法在主线程(UI线程)创建时自动调用,即 主线程的Looper对象自动生成,不需手动生成
*/
// 在Android应用进程启动时,会默认创建1个主线程(ActivityThread,也叫UI线程)
// 创建时,会自动调用ActivityThread的1个静态的main()方法 = 应用程序的入口
// main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象
/**
* 源码分析:main()
**/
public static void main(String[] args) {
... // 仅贴出关键代码
Looper.prepareMainLooper();
// 1. 为主线程创建1个Looper对象,同时生成1个消息队列对象(MessageQueue)
// 方法逻辑类似Looper.prepare()
// 注:prepare():为子线程中创建1个Looper对象
ActivityThread thread = new ActivityThread();
// 2. 创建主线程
Looper.loop();
// 3. 自动开启 消息循环 ->>下面将详细分析
}
- 创建主线程时,会自动调用ActivityThread的1个静态的main();而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象
- 生成Looper & MessageQueue对象后,则会自动进入消息循环:Looper.loop()
- 主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象则需手动通过Looper.prepare()创建
- 在子线程若不手动创建Looper对象 则无法生成Handler对象
- 根据Handler的作用(在主线程更新UI),故Handler实例的创建场景 主要在主线程
- 消息循环
/**
* 源码分析: Looper.loop()
* 作用:消息循环,即不断从消息队列MessageQueue中获取消息Message、并分发消息到Handler,知道消息为空时退出循环
* 特别注意:
* a. 主线程的消息循环不允许退出,即无限循环
* b. 子线程的消息循环允许退出:调用消息队列MessageQueue的quit()
*/
public static void loop() {
...// 仅贴出关键代码
// 1. 获取当前Looper的消息队列(MessageQueue)
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
// 2. 消息循环(通过for循环 => 死循环:主线程保持不退出原因)
for (;;) {
// 2.1 从消息队列中取出消息
// 消息队列的顺序维护使用单链表形式维护的,将消息队列中第一条数据取出来,并将第二条数据编程第一条
Message msg = queue.next();
if (msg == null) {
// 若取出的消息为空,则线程阻塞,退出循环
return;
}
// 2.2 获取msg的目标Handler,分发消息Message到对应的Handler
msg.target.dispatchMessage(msg);
// 把消息Message派发给消息对象msg的target属性(1个handler对象)
// handler对象调用其回调方法对消息进行处理 ->>分析1
// 3. 释放消息占据的资源
msg.recycle();
}
}
/**
* 分析1:dispatchMessage(msg)
* 定义:属于处理者类(Handler)中的方法
* 作用:派发消息到对应的Handler实例 & 根据传入的msg作出对应的操作
*/
public void dispatchMessage(Message msg) {
// 1. 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息
// 则执行handleCallback(msg),即回调Runnable对象里复写的run()->> 分析2
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 2. 若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息(即此处需讨论的)
// 则执行handleMessage(msg),即回调复写的handleMessage(msg) ->> 分析3
handleMessage(msg);
}
}
/**
* 分析2:handleCallback(msg)
**/
private static void handleCallback(Message message) {
message.callback.run();
// Message对象的callback属性 = 传入的Runnable对象
// 即回调Runnable对象里复写的run()
}
/**
* 分析3:handleMessage(msg)
* 注:该方法 = 空方法,在创建Handler实例时复写 = 自定义消息处理方式
**/
public void handleMessage(Message msg) {
... // 创建Handler实例时复写
}
- 消息循环的操作 = 消息出队 + 分发给对应的Handler实例
- 分发给对应的Handler的过程:根据出队消息的归属者通过dispatchMessage(msg)进行分发,最终回调复写的handleMessage(Message msg),从而实现 消息处理 的操作
特别注意:在进行消息分发时(dispatchMessage(msg)),会进行1次发送方式的判断:
- 若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息,则直接回调Runnable对象里复写的run()
- 若msg.callback属性为空,则代表使用了sendMessage(Message
msg)发送消息,则回调复写的handleMessage(msg)
- 步骤2:创建消息对象
- Message类内部维护一个Message池,用于消息对象复用
- 若消息池有可复用消息,则返回;否则return new Message()
/**
* 具体使用
*/
Message msg = Message.obtain(); // 实例化消息对象
msg.what = 1; // 消息标识
msg.obj = "AA"; // 消息内容存放
/**
* 源码分析:Message.obtain()
* 作用:创建消息对象
* 注:创建Message对象可用关键字new 或 Message.obtain()
*/
public static Message obtain() {
// Message内部维护了1个Message池,用于Message消息对象的复用
// 使用obtain()则是直接从池内获取
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
// 建议:使用obtain()”创建“消息对象,避免每次都使用new重新分配内存
}
// 若池内无消息对象可复用,则还是用关键字new创建
return new Message();
}
- 步骤3:在工作线程(AsynTask、Thread、Runnable)中 发送消息到消息队列中
- 将Handler实例保存到Message的target属性中(使Looper分发消息时能找到对应处理器)
- 将消息入队到绑定线程的消息队列中
/**
* 具体使用
*/
mHandler.sendMessage(msg);
/**
* 源码分析:mHandler.sendMessage(msg)
* 定义:属于处理器类(Handler)的方法
* 作用:将消息 发送 到消息队列中(Message ->> MessageQueue)
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
// ->> 最终调用 sendMessageAtTime(msg, uptimeMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1. 获取对应的消息队列对象(MessageQueue)
MessageQueue queue = mQueue;
// 2. 调用了enqueueMessage方法 ->>分析1
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
* 分析1:enqueueMessage(queue, msg, uptimeMillis)
**/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1. 将msg.target赋值为this,即 :把 当前的Handler实例对象作为msg的target属性(Message实例保存了发送该msg的Handler信息)
msg.target = this;
// 因此,当Looper的loop()中消息循环时,会从消息队列中取出每个消息msg,然后执行msg.target.dispatchMessage(msg)去处理消息
// 实际上则是将该消息派发给对应的Handler实例
// 2. 调用消息队列的enqueueMessage()
// 即:Handler发送的消息,最终是保存到消息队列
// queue.enqueueMessage(msg, uptimeMillis)属于消息队列类(MessageQueue)的方法,入队,即 将消息 根据时间 放入到消息队列中
// 采用单链表实现:提高插入消息、删除消息的效率
return queue.enqueueMessage(msg, uptimeMillis);
}
// 之后,随着Looper对象的无限消息循环
// 不断从消息队列中取出Handler发送的消息 & 分发到对应Handler
// 最终回调Handler.handleMessage()处理消息
总结
步骤 | 核心方法 | 说明 |
---|---|---|
主线程创建时 | Looper.prepare() Looper.loop() | 在ActivityThread.java主线程入口类,自动创建1个Looper,1个MessageQueue 并进入消息循环(不断从消息队列中取出消息Message并分发给相应的处理器msg.target.dispatchMessage(msg)) |
创建Handler实例 | Handler构造方法 Handler.handleMessage() | 绑定当前线程(Looper & MessageQueue) 复写回调方法对Looper分发的消息进行处理 |
创建消息对象 | Message.obtain() | 从Message池获取或新建Message对象 |
通过Handler发送消息到消息队列中 | Handler.sendMessage() | Message.target保存Handler信息 调用MessageQueue.enqueueMessage()将消息放入消息队列中 |
内存泄露 & 解决方案
- 内存泄露
当一个对象已经不再被使用时,本该被回收但却因为有另外一个正在使用的对象持有它的引用从而导致它不能被回收,仍停留在堆内存中。这就导致了内存泄漏。 - Handler内存泄露原因
(1)当Handler消息队列 还有未处理完的消息/正在处理的消息时,消息队列中的Message持有Handler实例的引用
(2)Handler = 非静态内部类/匿名内部类 ,故默认持有外部类(Activity实例)的引用
(3)该引用关系会一直保持,直到Handler消息队列中所有消息处理完毕(未被处理 / 正处理的消息 -> Handler实例 -> 外部类)
(4)在Handler消息队列 还有未处理的消息 / 正在处理消息时,此时若需销毁外部类MainActivity(Handler的生命周期 > 外部类的生命周期),但由于上述引用关系,垃圾回收器(GC)无法回收MainActivity,从而造成内存泄漏。
- Handler内存泄露解决
静态内部类 + 弱引用 - 将Handler的子类设置成 静态内部类
静态内部类 不默认持有外部类的引用,从而使得 “未被处理 / 正处理的消息 -> Handler实例 -> 外部类” 的引用关系 的引用关系 不复存在。 - 使用WeakReference弱引用持有Activity实例
弱引用的对象拥有短暂的生命周期。在垃圾回收器线程扫描时,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主线程创建时便自动创建Looper & 对应的MessageQueue
// 之后执行Loop()进入消息循环
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1. 实例化自定义的Handler类对象->>分析1
//注:
// a. 此处并无指定Looper,故自动绑定当前线程(主线程)的Looper、MessageQueue;
// b. 定义时需传入持有的Activity实例(弱引用)
showhandler = new FHandler(this);
// 2. 启动子线程1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 1;// 消息标识
msg.obj = "AA";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
// 3. 启动子线程2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2;// 消息标识
msg.obj = "BB";// 消息存放
// b. 传入主线程的Handler & 向其MessageQueue发送消息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定义Handler子类
// 设置为:静态内部类
private static class FHandler extends Handler{
// 定义 弱引用实例
private WeakReference<Activity> reference;
// 在构造方法中传入需持有的Activity实例
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity实例
reference = new WeakReference<Activity>(activity); }
// 通过复写handlerMessage() 从而确定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到线程1的消息");
break;
case 2:
Log.d(TAG, " 收到线程2的消息");
break;
}
}
}
}
面试
HandlerThread
简介
一个Android 已封装好的轻量级异步通信类。
用于实现多线程(开启工作线程执行耗时操作),异步通信与消息传递(工作线程与主线程之间通信)
本质上是通过继承Thread类和封装Handler类的使用,从而使得创建新线程和与其他线程进行通信变得更加方便易用
具体使用
// 步骤1:创建HandlerThread实例对象
// 传入参数 = 线程名字,作用 = 标记该线程
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
// 步骤2:启动线程
mHandlerThread.start();
// 步骤3:创建工作线程Handler & 复写handleMessage()
// 作用:关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
// 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
// 步骤4:使用工作线程Handler向工作线程的消息队列发送消息
// 在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
// 步骤5:结束线程,即停止线程的消息循环
mHandlerThread.quit();
工作原理 & 源码分析
内部原理 = Thread类 + Handler类机制,即:
- 通过继承Thread类,快速地创建1个带有Looper对象的新工作线程
- 通过封装Handler类,快速创建Handler & 与其他线程进行通信
- 创建HandlerThread实例对象
- HandlerThread类继承自Thread类
- 创建HandlerThread类对象 = 创建Thread类对象 + 设置线程优先级 = 新开1个工作线程 + 设置线程优先级
/**
* 具体使用
* 传入参数 = 线程名字,作用 = 标记该线程
*/
HandlerThread mHandlerThread = new HandlerThread("handlerThread");
/**
* 源码分析:HandlerThread类的构造方法
*/
public class HandlerThread extends Thread {
// 继承自Thread类
int mPriority; // 线程优先级
int mTid = -1; // 当前线程id
Looper mLooper; // 当前线程持有的Looper对象
// HandlerThread类有2个构造方法
// 区别在于:设置当前线程的优先级参数,即可自定义设置 or 使用默认优先级
// 方式1. 默认优先级
public HandlerThread(String name) {
// 通过调用父类默认的方法创建线程
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
// 方法2. 自定义设置优先级
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
...
}
- 启动线程
- 为当前工作线程(即步骤1创建的线程)创建1个Looper对象 & MessageQueue对象
- 通过持有锁机制来获得当前线程的Looper对象
- 发出通知:当前线程已经创建mLooper对象成功
- 工作线程进行消息循环,即不断从MessageQueue中取消息 & 派发消息
/**
* 具体使用
*/
mHandlerThread.start();
/**
* 源码分析:此处调用的是父类(Thread类)的start(),最终回调HandlerThread的run()
*/
@Override
public void run() {
// 1. 获得当前线程的id
mTid = Process.myTid();
// 2. 创建1个Looper对象 & MessageQueue对象
Looper.prepare();
// 3. 通过持有锁机制来获得当前线程的Looper对象
synchronized (this) {
mLooper = Looper.myLooper();
// 发出通知:当前线程已经创建mLooper对象成功
// 此处主要是通知getLooper()中的wait()
notifyAll();
// 此处使用持有锁机制 + notifyAll() 是为了保证后面获得Looper对象前就已创建好Looper对象
}
// 4. 设置当前线程的优先级
Process.setThreadPriority(mPriority);
// 5. 在线程循环前做一些准备工作
// 该方法实现体是空的,子类可实现 / 不实现该方法
onLooperPrepared();
// 6. 进行消息循环,即不断从MessageQueue中取消息 & 派发消息
Looper.loop();
mTid = -1;
}
}
- 创建工作线程Handler & 复写handleMessage()
将创建的Handler 与 工作线程的Looper对象绑定,从而将Handler绑定工作线程
/**
* 具体使用
* 作用:将Handler关联HandlerThread的Looper对象、实现消息处理操作 & 与其他线程进行通信
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
Handler workHandler = new Handler( handlerThread.getLooper() ) {
@Override
public boolean handleMessage(Message msg) {
...//消息处理
return true;
}
});
/**
* 源码分析:handlerThread.getLooper()
* 作用:获得当前HandlerThread线程中的Looper对象
*/
public Looper getLooper() {
// 若线程不是存活的,则直接返回null
if (!isAlive()) {
return null;
}
// 若当前线程存活,再判断线程的成员变量mLooper是否为null
// 直到线程创建完Looper对象后才能获得Looper对象,若Looper对象未创建成功,则阻塞
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
// 此处会调用wait方法去等待
wait();
} catch (InterruptedException e) {
}
}
}
// 上述步骤run()使用 持有锁机制 + notifyAll() 获得Looper对象后
// 则通知当前线程的wait()结束等待 & 跳出循环
// 最终getLooper()返回的是在run()中创建的mLooper对象
return mLooper;
}
- 使用工作线程Handler向工作线程的消息队列发送消息
/**
* 具体使用
* 作用:在工作线程中,当消息循环时取出对应消息 & 在工作线程执行相关操作
* 注:消息处理操作(HandlerMessage())的执行线程 = mHandlerThread所创建的工作线程中执行
*/
// a. 定义要发送的消息
Message msg = Message.obtain();
msg.what = 2; //消息的标识
msg.obj = "B"; // 消息的存放
// b. 通过Handler发送消息到其绑定的消息队列
workHandler.sendMessage(msg);
/**
* 源码分析:workHandler.sendMessage(msg)
* 此处的源码即Handler的源码,故不作过多描述
*/
- 结束线程,即停止线程的消息循环
IPC
带你了解android的IPC机制
Android 中的 IPC 方式
Android Binder机制及AIDL使用
IPC 概述?
- IPC简介
IPC是Inter-Process Communication的缩写,含义就是跨进程通信。
在Android中,为每一个应用程序都分配了一个独立的虚拟机,不同虚拟机在内存分配上都有不同的地址空间,进程间相互独立、隔离。因此互相访问数据需要借助其他手段。
IPC主要包含两部分:(1)序列化:将对象转化为字节(Serialiazable、Parcelable)(2)Binder机制 - 实现IPC的方式
方式 | 说明 | 特点 |
---|---|---|
Bundle | 在Android中三大组件(Activity,Service,Receiver)都支持在Intent中传递Bundle数据,由于Bundle实现了Parcelable接口(一种特有的序列化方法),所以它可以很方便的在不同的进程之间进行传输 | 四大组件间的进程间通信方式,简单易用,但只能是单方向的简单数据传输,使用有一定的局限性 |
文件共享 | 将对象序列化之后保存到文件中,在通过反序列,将对象从文件中读取出来。此方式对文件的格式没有具体的要求,可以是文件、XML、JSON等 | 文件共享方式也存在着很大的局限性,如并发读/写问题,如读取的数据不完整或者读取的数据不是最新的。不适合高并发场景,并且无法做到进程间的及时通信 |
Messenger | 通过Messenger来进行进程间通信,在Messenger中放入我们需要传递的数据,实现进程间数据传递。Messenger只能传递Message对象,Messenger是一种轻量级的IPC方案,它的底层实现是AIDL | Messenger内部消息处理使用Handler实现的,所以它是以串行的方式处理客服端发送过来的消息的,如果有大量的消息发送给服务器端,服务器端只能一个一个处理,如果并发量大的话用Messenger就不合适了,而且Messenger的主要作用就是为了传递消息,很多时候我们需要跨进程调用服务器端的方法,这种需求Messenger就无法做到了。 |
AIDL | 用于生成可以在Android设备上两个进程之间进行进程间通信(IPC)的代码 | 如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。AIDL是IPC的一个轻量级实现,Android也提供了一个工具,可以自动创建Stub(类架构,类骨架) |
ContentProvider | ContentProvider(内容提供者)是Android中的四大组件之一,为了在应用程序之间进行数据交换,Android提供了ContentProvider,ContentProvider是不同应用之间进行数据交换的API,一旦某个应用程序通过ContentProvider暴露了自己的数据操作的接口,那么不管该应用程序是否启动,其他的应用程序都可以通过接口来操作接口内的数据,包括数据的增、删、改、查等操作 | 使用受限,只能根据特定规则访问数据 |
Socket | Socket也是实现进程间通信的一种方式,Socket也称为“套接字”(网络通信中概念),通过Socket也可以实现跨进程通信,Socaket主要还是应用在网络通信中 |
Binder机制 简介 & 原理?
carson_ho:Android跨进程通信:图文详解 Binder机制 原理
- Binder 简介
(1)Binder机制 是Android中实现跨进程通信(IPC)的方式
(2)Binder驱动 是连接Service进程、Client进程和Service Manager进程的一种虚拟的物理设备驱动
(3)Binder类 是一个实现了IBinder接口的类,常用用于代理模式中(AMS、AIDL) - Binder 机制
- 通信模型 & 工作原理
(1)进程隔离 & 跨进程通信模型 - 进程隔离
为了保证 安全性 & 独立性,一个进程 不能直接操作或者访问另一个进程,即Android的进程是相互独立、隔离的,只能运行在自己进程所拥有的虚拟地址空间。
对于用户空间,不同进程之间是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的。Client端与Server端进程往往采用ioctl等方法与内核空间的驱动进行交互。 - 跨进程通信(IPC)基本原理
(2)Binder 跨进程通信模型 & 工作原理
- Client进程、Server进程 & Service Manager 进程之间的交互 都必须通过Binder驱动(使用 open 和 ioctl文件操作函数),而非直接交互
Client进程、Server进程 & Service Manager进程属于进程空间的用户空间,不可进行进程间交互
Binder驱动 属于 进程空间的 内核空间,可进行进程间 & 进程内交互 - Binder驱动 & Service Manager进程 属于 Android基础架构(Android平台,由系统实现);而Client 进程 和 Server 进程 属于Android应用层(由开发者实现)
Binder 驱动创建一块接收缓存区。实现Service 进程用户空间 与 Client内核缓存区的地址映射。
因此用户通过系统调用(copy_from_user)发送数据到内核缓冲区时,也相当于发送到了Server进程的用户空间;
同理,Server将执行的结果写入共享的接受缓存区时,也相当于发送到了内核缓存区,用户通过系统调用(copy_to_user)从内核缓存区接受Server进程返回的数据。
Binder只需要通过一次数据拷贝便可实现进程间的数据传递。
- 优点
- 对比 Linux (Android基于Linux)上的其他进程通信方式(管道、消息队列、共享内存、
信号量、Socket),Binder 机制的优点有:
- Binder 跨进程调用服务流程
跨进程调用系统服务的简单示例,实现浮动窗口部分代码:
//获取WindowManager服务引用
WindowManager wm = (WindowManager) getSystemService(getApplication().WINDOW_SERVICE);
//布局参数layoutParams相关设置略...
View view = LayoutInflater.from(getApplication()).inflate(R.layout.float_layout, null);
//添加view
wm.addView(view, layoutParams);
- 注册服务(addService): 在Android开机启动过程中,Android会初始化系统的各种Service,并将这些Service向ServiceManager注册(即让ServiceManager管理)。这一步是系统自动完成的。
- 获取服务(getService): 客户端想要得到具体的Service直接向ServiceManager要即可。客户端首先向ServiceManager查询得到具体的Service引用,通常是Service引用的代理对象,对数据进行一些处理操作。即第2行代码中,得到的wm是WindowManager对象的引用。
- 使用服务: 通过这个引用向具体的服务端发送请求,服务端执行完成后就返回。即第6行调用WindowManager的addView函数,将触发远程调用,调用的是运行在systemServer进程中的WindowManager的addView函数。
使用服务的具体执行过程
(1)Client通过获得一个Server的代理接口Proxy,对Server提供的方法进行调用。代理接口Proxy中定义的方法与Server中定义的方法是一一对应的。
(2)Client调用某个代理接口中的方法时,代理接口的方法会将Client传递的参数打包成Parcel对象。代理接口将Parcel发送给内核中的Binder Driver。
(BinderDriver底层工作:Client进程通过 系统调用 copy_from_user将数据发送到内核空间缓存区,由于 Client内核空间缓存区 & Server用户空间 存在地址映射关系,因此相当于发送到Server进程用户空间,Binder通知Server进行解包)
(3)Server会读取Binder Driver中的请求数据,解包Parcel对象,调用相应的方法并将结果打包返回给Binder Driver。
(Binder底层工作:Server将结果写入自己的用户空间,由于 Client内核空间缓存区 & Server用户空间 存在地址映射关系,因此相当于发送到内核缓存区,Binder通知Client获得结果,Client进程 通过 系统调用 copy_to_user() 从内核缓存区接收Server进程返回数据)
(4)Client的代理接口Proxy收到结果会解包并将真正的数据传送给Client。
整个的调用过程是一个同步过程,在Server处理的时候,Client会Block住。因此Client调用过程不应在主线程。 - 代理模式在Binder中的使用 / 从应用层面剖析Android Binder机制
从应用层面剖析Android Binder机制
从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当你bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
(1)AIDL中代理模式
人人都会设计模式:代理模式–Proxy
Binder实现中含有Proxy类。
Proxy将客户端的请求参数通过Parcel包装后通过Binder传到远程服务端,远程服务端解析数据并执行对应的操作,同时客户端线程挂起,当服务端方法执行完毕后,再将返回结果写入到另外一个Parcel中并将其通过Binder传回到客户端Proxy,客户端Proxy会解析但会数据包中的内容并将原始结果返回给客户端真正调用者,至此,整个Binder的工作过程就完成了。
由此可见,Binder是一个典型的代理者模式,Parcel对象就在这个通道中跨进程传输。
(2)AMS中代理模式
- IActivityManager作为ActivityManagerProxy和ActivityManagerNative的公共接口,所以两个类具有部分相同的接口,可以实现合理的代理模式;
- ActivityManagerProxy代理类是ActivityManagerNative的内部类;
- ActivityManagerNative是个抽象类,真正发挥作用的是它的子类ActivityManagerService(系统Service组件)。
- ActivityManager是一个客户端,为了隔离它与,有效降低甚至消除二者的耦合度,在这中间使用了ActivityManagerProxy代理类,所有对的访问都转换成对代理类的访问,这样ActivityManager就与解耦了,这是典型的proxy的应用场景。
- ActivityManagerService是系统统一的Service,运行在独立的进程中;通过系统ServiceManger获取;ActivityManager运行在一个进程里面,ActivityManagerService运行在另一个进程内,对象在不同的进程里面,其地址是相互独立的;采用Binder机制跨进程通信,所以我们可以得出这是一个RemoteProxy。
(3)activity.bindService()
什么是AIDL?如何使用AIDL?AIDL工作原理(结合Binder)?
- AIDL定义
AIDL (Android Interface Definition Language) 是一种接口定义语言,用于生成可以在Android设备上两个进程之间进行进程间通信(Interprocess Communication, IPC)的代码。如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数,来完成进程间通信。 - AIDL使用
AIDL一共分为三部分,分别是客户端、服务端和AIDL接口。
- AIDL接口:用来传递的参数,提供进程间通信。
(1)创建.aidl文件,即接口IService(IService.aidl),用于提供暴露的方法(传递的参数)
IService.aidl
interface IService {
// 定义暴露的方法
void callMethodService();
}
Make Project后,系统会自动生成IService.java文件(位于gen/package目录下)
该接口会自动生成Stub类,继承了Binder 类,同时实现了IService接口。Stub是AIDL自动生成一个实现AIDL接口的专门用于进程间通信的中间人(IBinder)类。
public interface IService extends android.os.IInterface{
public static abstract class Stub extends android.os.Binder implements com.itheima.remoteservice.IService{
private static final java.lang.String DESCRIPTION = "com.itheima.remoteservice.IService";
public Stub(){
this.attachInterface(this,DESCRIPTION);
}
}
// 将IBinder 转换为 Iservice 类型
// 可用于生成一个中间人
public static com.itheima.remoteservice.Iservice asInterface(android.os.IBinder obj){
if(obj == null){return null;}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTION);
if((iin!=null)&&(iin instanceof com.itheima.remoteservice.IService)){
return (com.itheima.remoteservice.IService)iin;
}
}
}
(2)服务端需要创建一个中间人实现IService接口,从而提供的服务方法;客户端需要获取IService接口从而得到中间人并调用服务方法。因此需要保证服务端客户端拥有同一个AIDL文件。AIDL规定拥有相同包名的AIDL文件为同一个ADIL文件。
在服务端和客户端下创建相同的包名,放入相同的IService.aidl,并由AIDL(系统)自动生成相同的IService.java文件(可序列化参数)
- 服务端/远程进程:运行在其他应用中的服务,用于提供服务。
(1)编写服务端代码,定义一个中间人对象,并实现接口(extends Binder implements IService = extends Stub)。并在onBinder中返回中间人
public class RemoteService extends Service{
@override
public IBinder onBind(Intent intent){
// 2. 返回定义的中间人对象
return new MyBinder();
}
// RemoteService 提供的服务方法
public void methodService(){
System.out.println("Msg From RemoteService");
}
// 1. 定义一个中间人对象 IBinder
// extends Binder implements IService = extends Stub
private class MyBinder extends Stub {
@override
public void callMethodService(){
this.methodService();
}
}
}
(2)AndroidManifest.xml 配置服务
<service android:name="com.itheima.remoteservice.RemoteService">
<intent-filter>
<action android:name="com.itheima.remoteservice"/>
</intent-filter>
</setvice>
总结:服务端创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。
- 客户端/本地进程:运行在自己应用的服务,用于调用远程服务
(1)在MainActivity中通过asInterface方法获取中间人对象,并执行服务端方法
public class MainActivity extends Activity{
private MyConn conn;
private IService iservce; // 中间人对象(extends Binder)
@override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 1. 调用bindService获取中间人对象
Intent intent = new Intent();
intent.setAction("com.itheima.remoteservice"); // 通过意图过滤器为服务建立关系
conn = new MyConn();
// 2. 连接服务,获取中间人对象
bindService(intent,conn,flags);
}
// 监视服务的状态
private class MyConn implements Connection{
@override
public void onServiceConnected(ComponentName name,IBinder service){
// 连接成功,通过连接服务获取中间人对象(获取中间人IBinder对象方式变化)
// Stub.asInterface(service) 将IBinder 转化为实现AIDL接口的 中间人对象(IService)
iservice = Stub.asInterface(service);
}
@override
public void onServiceDisconnected(ComponentName name){
//连接失败
}
}
public void click(View v){
try{
//3. 通过中间人对象调用相应方法
iService.callMethodService();
}catch(RemoteException e){e.printStackTrace();}
}
@override
protected void onDestroy(){
unbindService(conn);
super.onDestroy();
}
}
总结:绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。
- AIDL应用场景
某个游戏(如欢乐斗地主)需要支付时打开支付宝应用进行支付 - AIDL文件解析(手写aidl)
.aidl文件用于向客户端暴露远程服务提供方法,用于跨进程通信。经过JVM编译后生成对应java文件,生成一个继承IInterface类的接口。
包含2部分: - 实现IBookService的本地实现类Stub
- 暴露给客户端的服务方法
public interface IBookService extends IInterface {
// 继承IInterface的接口
// 包含两部分
// 1. 实现IBookService的本地实现类Stub
public abstract class Stub extends Binder implements IBookService {...}
// 2. 暴露给客户端的服务方法
List<Book> getBooks() throws RemoteException;
void addBook(Book book) throws RemoteException;
}
实现IBookService的本地实现类Stub,包含1个构造函数,2个重要方法,一个代理内部类
- 构造函数 public Stub() {}
- asInterface
- onTransact
- Stub 内部类 Proxy
public abstract class Stub extends Binder implements IBookService {
private static final String DESCRIPTOR = "com.xud.ipc.server.IBookService";
public Stub() {
// 服务端获取Binder,实现IBookService方法并用键值对的形式将Stub保存到Binder
this.attachInterface(this, DESCRIPTOR);
}
public static IBookService asInterface(IBinder binder) {
// 客户端获取Binder,若为相同进程则返回对应Stub,否则返回Stub.Proxy
if (binder == null)
return null;
// Binder中查找IInterface
IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
if (iin != null && iin instanceof IBookService)
return (IBookService) iin;
return new Proxy(binder);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
// 客户端通过调用 remote.transcat方法后,Binder会通知服务端onTranscat接收数据并根据code执行相应方法
switch (code) {
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSAVTION_getBooks:
data.enforceInterface(DESCRIPTOR);
List<Book> result = this.getBooks();
reply.writeNoException();
reply.writeTypedList(result);
return true;
case TRANSAVTION_addBook:
data.enforceInterface(DESCRIPTOR);
Book arg0 = null;
if (data.readInt() != 0) {
arg0 = Book.CREATOR.createFromParcel(data);
}
this.addBook(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
public static final int TRANSAVTION_getBooks = IBinder.FIRST_CALL_TRANSACTION;
public static final int TRANSAVTION_addBook = IBinder.FIRST_CALL_TRANSACTION + 1;
public class Proxy implements IBookService {...}
}
实现IBookService的代理类Proxy
Proxy是一个典型的静态代理模式,Proxy并没有实现IBookService中的方法,而是通过remote将方法请求传递到Server进程,也即是上面的Stub类处理,而remote是一个BinderProxy,包含2部分:
- 构造方法 public Proxy(IBinder remote) {}
- 接口方法的实现
public class Proxy implements IBookService {
// 代理类,客户端通过Proxy执行服务端方法
private static final String DESCRIPTOR = "com.xud.ipc.server.IBookService";
private IBinder remote;
public Proxy(IBinder remote) {
// return new Proxy(binder); remote本质上是服务端Stub的引用
this.remote = remote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public IBinder asBinder() {
return remote;
}
// 实现IBookService接口的方法
// 1. 将数据进行序列化
// 2. 并调用服务端的方法remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0);
@Override
public List<Book> getBooks() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
List<Book> result;
try {
data.writeInterfaceToken(DESCRIPTOR);
remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0);
replay.readException();
result = replay.createTypedArrayList(Book.CREATOR);
} finally {
replay.recycle();
data.recycle();
}
return result;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
replay.readException();
} finally {
replay.recycle();
data.recycle();
}
}
}
- AIDL工作原理
Binder机制运行主要包括三部分:注册服务、获取服务和使用服务。注册服务和获取服务主要涉及C的内容,不予介绍。主要介绍使用服务时AIDL工作原理。
.aidl文件生成的java代码是一个接口文件,继承了android.os.IInterface,用于声明暴露给Client端的具体服务。这个接口生成的java文件包括两部分: - 静态内部抽象类Stub
- 声明了具体的功能的抽象方法(aidl文件中声明的方法)
(1)Binder对象的获取
Binder是实现跨进程通信的基础,那么Binder对象在服务端和客户端是共享的,是同一个Binder对象。在客户端通过Binder对象获取实现了IInterface接口的对象来调用远程服务,然后通过Binder来实现参数传递。 - 服务端获取Binder对象并保存IInterface接口对象
服务端创建binder实现Stub类并实现AIDL抽象函数
private IBookManager.Stub mbinder = new IBookManager.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
//添加书本
if (!mBookList.contains(book)) {
mBookList.add(book);
}
}
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
};
Binder中两个关键方法:
// Binder具有被跨进程传输的能力是因为它实现了IBinder接口。系统会为每个实现了该接口的对象提供跨进程传输
public class Binder implement IBinder {
// Binder具有的完成特定任务的能力是通过它的IInterface的对象获得的
// IInterface 实现接口方法的binder
// attachInterface方法会将(descriptor,plus)作为(key,value)对存入Binder对象中的一个Map对象中,Binder对象可通过attachInterface方法持有一个IInterface对象(即plus)的引用,并依靠它获得完成特定任务的能力。
void attachInterface(IInterface plus, String descriptor)
// 从IBinder中继承而来
// 根据key值(即参数 descriptor)查找相应的IInterface对象
IInterface queryLocalInterface(Stringdescriptor)
}
在服务端进程,通过实现private IBookManager.Stub mbinder = new IBookManager.Stub() {}抽象类,获得Binder对象。 并保存了IInterface对象。
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
- 客户端获取Binder对象并获取IInterface接口对象
通过bindService获得Binder对象
MyClient.this.bindService(intentService, mServiceConnection, BIND_AUTO_CREATE);
然后通过Binder对象获得IInterface对象。
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
//通过服务端onBind方法返回的binder对象得到IBookManager的实例,得到实例就可以调用它的方法了
mIBookManager = IBookManager.Stub.asInterface(binder);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mIBookManager = null;
}
};
其中asInterface(binder)方法如下:
public static com.lvr.aidldemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.lvr.aidldemo.IBookManager))) {
return ((com.lvr.aidldemo.IBookManager) iin);
}
return new com.lvr.aidldemo.IBookManager.Stub.Proxy(obj);
}
先通过queryLocalInterface(DESCRIPTOR);查找到对应的IInterface对象,然后判断对象的类型,如果是同一个进程调用则返回IBookManager对象,由于是跨进程调用则返回Proxy对象,即Binder类的代理对象。
(2)客户端调用服务端方法
实现IBookService的代理类Proxy,Proxy是一个典型的静态代理模式,Proxy并没有实现IBookService中的方法,而是通过remote将方法请求传递到Server进程,交给Stub类处理。
public class Proxy implements IBookService {
private static final String DESCRIPTOR = "com.xud.ipc.server.IBookService";
private IBinder remote;
public Proxy(IBinder remote) {
this.remote = remote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public IBinder asBinder() {
return remote;
}
@Override
public List<Book> getBooks() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
List<Book> result;
try {
data.writeInterfaceToken(DESCRIPTOR);
remote.transact(Stub.TRANSAVTION_getBooks, data, replay, 0);
replay.readException();
result = replay.createTypedArrayList(Book.CREATOR);
} finally {
replay.recycle();
data.recycle();
}
return result;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (book != null) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
remote.transact(Stub.TRANSAVTION_addBook, data, replay, 0);
replay.readException();
} finally {
replay.recycle();
data.recycle();
}
}
}
客户端获得了Binder类的代理对象Proxy,并且通过代理对象获得了IInterface对象,那么就可以调用接口的具体实现方法了,来实现调用服务端方法的目的。
以addBook方法为例,调用该方法后,客户端线程挂起,等待唤醒:
@Override public void addBook(com.lvr.aidldemo.Book book) throws android.os.RemoteException
{
..........
//第一个参数:识别调用哪一个方法的ID
//第二个参数:Book的序列化传入数据
//第三个参数:调用方法后返回的数据
//最后一个不用管
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
..........
}
省略部分主要完成对添加的Book对象进行序列化工作,然后调用transact方法。
Proxy对象中的transact调用发生后,会引起系统的注意,系统意识到Proxy对象想找它的真身Binder对象(系统其实一直存着Binder和Proxy的对应关系)。于是系统将这个请求中的数据转发给Binder对象,Binder对象将会在onTransact中收到Proxy对象传来的数据,于是它从data中取出客户端进程传来的数据,又根据第一个参数确定想让它执行添加书本操作,于是它就执行了服务端相应操作,并把结果写回reply。代码概略如下:
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.lvr.aidldemo.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.lvr.aidldemo.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//这里调用服务端实现的addBook方法
this.addBook(_arg0);
reply.writeNoException();
return true;
}
然后在transact方法获得_reply并返回结果,本例中的addList方法没有返回值。
客户端线程被唤醒。因此调用服务端方法时,应开启子线程,防止UI线程堵塞,导致ANR。
因此对于Binder跨进程通信过程,可以总结为:
- 创建IService.aidl文件,声明暴露给Client的方法。系统将自动创建对应IService.java继承IInterface,包含Stub类和一系列暴露的抽象方法。作为跨进程通信的接口。
Stub类:继承Binder,实现IService接口。Server通过创建Stub实例,实现接口中定义的方法,处理Client的调用请求。包含一个构造函数、两个重要的方法和一个代理类:
- public Stub():创建Stub实例,在Binder中保存IInterface对象,服务端获取Binder
- public static IService asInterface(IBinder binder):根据key值查找相应的IInterface对象,客户端获取Binder
- protected boolean onTranscat(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags):进程间通信的方法,Client调用Proxy对象的transact后,会将数据传给Server的onTransact,执行Client想要执行的方法
抽象方法:IService中声明的暴露给Client的方法
- Server进程端创建Stub实例,实现接口方法,使之可以处理Client进程端的调用请求;
- Client进程端持有BinderProxy,当要调用IService中的方法时,实际上通过BinderProxy.transact()方法调用,经过Binder驱动跨进程传递之后,最终找到Server端onTransact()执行;
- Server的onTransact收到Client传来的数据后,根据code来执行Client调用的方法。