因获取固定图片而衍生的知识点

61 阅读4分钟

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里面的调用流程及其牵涉到的知识点