1、当一项资源需要适合预以空间换时间的时候,可以使用单例(比如 获取一张图片) 2、锁和条件变量(饿死的问题)、submit和Future 3、需要预加载框架
事情原由:代码评审中有一个固定图片需要转成base64,但是base64是一个耗时的操作,代码中我是这样实现的每次进入Activity都会进行进行将本地图片转化成base64,这样会导致性能和耗时都大大的增加了,还存在因为cpu很卡的情况下导致加载失败情况。
这种情况下我们首先需要想到怎么做能减少base64转化的次数是吧,然后我们就想到了static,但是图片转化Base64是耗时的,我们没法一下子就能获取得到。这样我们就有两种方案
方案一 使用锁和条件变量进行处理
代码实现:
public class CommonConfig {
public static CommonConfig instance = new CommonConfig();
private volatile String imgBase64;
// 手动锁 这个默认的是一种不公平锁NonfairSync 如果new ReentrantLock(true) 就是公平锁FairSync
private ReentrantLock reentrantLock = new ReentrantLock();
private Condition condition = reentrantLock.newCondition();
// 单线程不建议这样创建 因为这样的线程个数太多会导致内存及其其他异常 具体可以参考阿里巴巴出的java编程规范
private ExecutorService executorService = Executors.newSingleThreadExecutor();
public String getImgBase64() throws InterruptedException {
final ReentrantLock lock = reentrantLock;
lock.lock();
try {
//首先需要一个锁
// 基于这个锁一个条件变量 base64==null
while (imgBase64 == null) {
condition.await();
}
return imgBase64;
} finally {
lock.unlock();
}
}
public void loadImage(Context context, String imagePath) {
executorService.execute(() -> {
// 需要放到线程里面处理 IO
//1.判断base64 是否为空 不为空 就不加载了
// 2、如果为空 就进行加载文件或者图片
//3、将图片转换成base64赋值给 base64
final ReentrantLock lock = reentrantLock;
lock.lock();
try {
// if (imgBase64 != null) {
// // 1.判断base64 是否为空 不为空 就不加载了
// return;
// }
// 2、如果为空 就进行加载文件或者图片
imgBase64 = assetFileToBase64(context, imagePath);
condition.signal();
} finally {
lock.unlock();
}
});
}
public String assetFileToBase64(Context context, String fileName) {
InputStream inputStream = null;
try {
inputStream = context.getAssets().open(fileName);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP);
} catch (IOException e) {
e.printStackTrace();
// Log.e("TAG", "assetFileToBase64:e=" + e.toString());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
上面这种方法通过锁和条件变量进行处理的 ReentrantLock 是 Java语法层面提供的API,是一种手动锁,需要手动获取锁lock()和释放锁unlock(),并要手动处理异常。一方面使用更加灵活,另一方面也需要特别注意,一定要记得释放锁,不然就会造成死锁。 Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒,它通常与Lock(锁)一起使用,用于在多个线程之间协调和同步访问共享资源。Condition接口提供了类似于Object.wait()和Object.notify()方法的机制,但是它更加灵活,可以在等待和通知的过程中更精细地控制线程
逻辑思路就是 现将图片转成base64放到子线程中处理(当然可以用Thread)然后进行 先将线程内进行锁住并且通过Condition先进行signal唤醒一个等待在该条件上的线程,然后我们从 getImgBase64()中通过锁进行while循环 如果imageBase64为空的话就等待 等待imageBase64不为空的时候就返回imageBase64的结果,当前方法也是一个耗时的方法,外面调用需要在子线程中调用
方案二 通过异步转同步方法(submit和 Future)
代码实现:
public class CommonConfig {
public static CommonConfig instance = new CommonConfig();
private volatile String imgBase64;
private ExecutorService executorService = Executors.newSingleThreadExecutor();
private Future<String> future;
public String getImgBase64() {
try {
return future.get();
} catch (Exception e) {
return null;
}
}
public void loadImage(Context context, String imagePath) {
future = executorService.submit(() -> {
return assetFileToBase64(context, imagePath);
});
}
public String assetFileToBase64(Context context, String fileName) {
InputStream inputStream = null;
try {
inputStream = context.getAssets().open(fileName);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP);
} catch (IOException e) {
e.printStackTrace();
// Log.e("TAG", "assetFileToBase64:e=" + e.toString());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
首先我们将图片转base64通过线程submit提交到线程中去处理,然后在调用getImageBase64()方法的时候调用future.get() 当然这个方法也是一个耗时操作 也同样需要在子线程处理
以上不是记录问题的关键,通过这个知识点延伸了锁,锁有很多种 有java层手动锁ReentrantLock 也有公平锁FairSync和不公平锁NonfairSync 及其不少锁,每个锁的作用都不同 如果想要初步了解锁可以看Android 源码中java.util.concurrent.ArrayBlockingQueue,里面有详细怎么调用及其怎么释放 后续将详细分析ArrayBlockingQueue里面的调用流程及其牵涉到的知识点