大家都知道Android中UI操作必须放在主线程中,耗时操作比如网络请求和数据库查询需要放在子线程中。为此Android提供了像Handler和AsycTask这样的框架来给开发者使用。通常情况下我们都是在子线程中耗时获取数据后再通知给主线程去更新UI,但也有些其他的情况。
我曾经有一次面试的时候被问到一个问题,子线程中进行耗时操作,主线程如何同步获取这个子线程执行的结果?也就是实现一个异步操作的同步返回。
打个比方,我向外提供了一个getXXXList()的方法,这个方法是同步的,返回值是一个List,但是获取这个List是耗时操作,现在需要让外部用户直接调用这个方法获取List,不需要外部去开线程操作。
首先定义一个类用来模拟Android的线程限制。
package com.example.testapp1;
import android.os.Looper;
import java.util.ArrayList;
public class DemoList {
private ArrayList<String> mList;
public DemoList() {
mList = new ArrayList<>();
mList.add("0");
mList.add("1");
mList.add("2");
mList.add("3");
mList.add("4");
}
public ArrayList<String> getList() throws Exception{
if (isMainThread()){
//主线程中调用抛出异常
throw new Exception("can not get list in main thread!!!");
}
Thread.sleep(1000);
return mList;
}
private boolean isMainThread(){
return (Looper.getMainLooper().getThread() == Thread.currentThread());
}
}
再编写一个helper来提供同步获取list的方法
package com.example.testapp1;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
public class GetListHelper {
private DemoList mDemoList;
private GetListLisenter mLisenter;
public GetListHelper(GetListLisenter lisenter) {
this.mDemoList = new DemoList();
this.mLisenter = lisenter;
}
public ArrayList<String> getList(){
ExecutorService executor = Executors.newSingleThreadExecutor();
FutureTask<ArrayList<String>> getListTask = new FutureTask<>(new getListCallable());
executor.submit(getListTask);
try {
ArrayList<String> list = getListTask.get();
//可以使用接口回调通知activity更新UI
// for (String string:list){
// mLisenter.setText(list.get(0));
// }
return list;
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
executor.shutdown();
return null;
}
class getListCallable implements Callable<ArrayList<String>>{
@Override
public ArrayList<String> call() throws Exception {
ArrayList<String> list = mDemoList.getList();
return list;
}
}
}
这里使用了Callable接口,很多人都知道开启线程可以继承Thread类或者实现Runnable结构。其实并法包中还提供了Callable接口,这个接口和Runnable的区别就是它是有返回值的。将其放入FutureTask中交给线程池执行,执行完成后可以在FutureTask对象get到返回值。
//在Activity中定义一个点击事件,同步调用这个getList方法
public void getList(View view) {
ArrayList<String> list = new GetListHelper(this).getList();
StringBuilder text = new StringBuilder();
for (String str : list){
text.append(str);
}
mTextView.setText(text);
}
这样就完成了一个同步返回异步执行结果的逻辑。其实这样做是hang住了主线程去等待子线程结果,很显然没有Handler,RxJava等优雅。
类似的还可以使用并发包中的CountDownLatch 来实现这个效果
等待锁,是一个同步辅助类
用来同步执行任务的一个或者多个线程
不是用来保护临界区或者共享资源
CountDownLatch
countDown()计数减1
await()等待latch变成0
package com.example.testapp1;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchTest {
//初始化CountDownLatch,设置需要等待的线程数量
final CountDownLatch latch = new CountDownLatch(1);
volatile ArrayList<String> mList;
public ArrayList<String> getmList(){
//主线程
final DemoList demoList = new DemoList();
//子线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
try {
mList = demoList.getList();
} catch (Exception e) {
e.printStackTrace();
}
latch.countDown();
}
});
executorService.shutdown();
try {
//等待子线程执行完毕
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
return mList;
}
}
mainActivity中调用
ArrayList<String> list = new CountDownLatchTest().getmList();
和之前是一样的效果。
可以看到主线程中执行耗时操作,虽然不超过5s不会ANR,但是UI上能看到明显的卡顿,所以如果遇到类似的情景还是直接用Handler或者观察者吧。以上代码纯属想到了之前那道面试题写着玩的~