• Andorid小技巧:TransactionTooLargeException的简洁处理


    示例代码:https://gitee.com/spectre1225/big-data-demo.git

    问题场景

    在Activity之间跳转时,可能因为传递大型数据导致抛出TransactionTooLargeException异常,或许是传递一个Bitmap,或许是一个长度非常长的List或数组,甚至可能是一个长得离谱的String

    碰到这类问题的时候,我们通常会采用:

    • 通过某个全局可见的第三者(比如某个单例类)在内存中共享这个大型数据。

    缺点:1. 类似全局变量,不安全;2. 无法用于闹钟或通知中的Bundle,因为触发时应用可能已经重启过了,内存信息被清理了。

    • 写到数据库/文件中,读取的时候访问数据库/文件。

    缺点:1. 操作和维护麻烦。

    且上面方法有一个共同的问题:我需要一个参数,却得到了一个环境变量/全局上下文变量。

    因此,我需要一种方案,可以:

    1. 避免TransactionTooLargeException异常
    2. 在使用上和Intent/Bundle兼容

    实现思路

    其实不管是写文件/数据库,还是通过内存变量来共享,都解决了TransactionTooLargeException异常,主要问题还是没法像其他数据那样通过Intent/Bundle来传递,达不到“参数”的效果。

    那么我们可以封装一种数据结构,可以通过Intent/Bundle来传递和使用,但内部使用文件/数据库/全局变量就可以了。

    MemoryBigData

    首先是通过内存变量的方式,我们只要定义一个类,使之实现SerializableParcelable接口,用于序列化传输,然后数据放在内部定义的静态Map中,就像这样:

    public class MemoryBigData<T> implements Serializable {
        private static final ConcurrentHashMap<String, Object> CACHE_MAP = new ConcurrentHashMap<>();
    
        //......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    设置参数的代码,可以写成:

    Intent intent = new Intent(this, TestActivity.class);
    intent.putExtra("bigIntData", MemoryBigData.of(new int[1024 * 1024]));
    startActivity(intent);
    
    • 1
    • 2
    • 3

    读取参数的时候,就是:

    MemoryBigData<int[]> intsData = (MemoryBigData<int[]>) getIntent().getSerializableExtra("bigIntData");
    int[] ints1 = intsData.get();//直接读取
    Log.d("TestActivity", "onCreate => data:" + ints1);
    //或
    int[] ints2 = intsData.getAndRemove();//读取并删除,不删除可能会导致内存溢出
    Log.d("TestActivity", "onCreate => remove: " + ints2 + " - " + intsData.isPresent());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    MemoryBigData类的完整实现如下:

    public class MemoryBigData<T> implements Serializable {
        private static final ConcurrentHashMap<String, Object> CACHE_MAP = new ConcurrentHashMap<>();
    
        public static <T> MemoryBigData<T> of(T value) {
            return of(UUID.randomUUID().toString(), value);
        }
    
        public static <T> MemoryBigData<T> of(String tag, T value) {
            MemoryBigData<T> bigData = new MemoryBigData<>(tag);
            bigData.set(value);
            return bigData;
        }
    
        private final String tag;
    
        private MemoryBigData(String tag) {
            this.tag = tag;
        }
    
        public void set(T t) {
            CACHE_MAP.put(tag, t);
        }
    
        public T get() {
            return (T) CACHE_MAP.get(tag);
        }
    
        public T getAndRemove() {
            return (T) CACHE_MAP.remove(tag);
        }
    
        public boolean isPresent() {
            return CACHE_MAP.containsKey(tag);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    FileBigData

    如果要考虑应用被杀/重启的情况,通过内存在传递显然满足不了,于是就得考虑用文件或数据库这样的持久化方式了。不过这类方式有个问题,就是IO延时,比如现在有个4MB的图片,我们就得考虑不能在主线程同步写文件。因此,我们的设置参数代码变成了:

    Intent intent = new Intent(this, TestActivity.class);
    FileBigData.create(bigString, new DataFactory.StringDataFactory())
            .whenCompleteAsync((FileBigData<String> data, Throwable throwable) -> {
                intent.putExtra("data", data);
                startActivity(intent);
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    而读取的代码就变成了:

    FileBigData<String> strData = (FileBigData<String>) getIntent().getSerializableExtra("data");
    strData.get().whenCompleteAsync((data, t) -> { //只读取
        Log.d("TestActivity", "onCreate => data1:" + data);
    });
    //或
    strData.getAndRemove().whenCompleteAsync((data2, t1) -> { //读取并移除
            Log.d("TestActivity", "onCreate => remove1: " + data2 + " - " + strData.isPresent());
        });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里有两个注意点:

    1. 我使用了JDK自带的CompletableFuture类来完成异步操作,这部分可以换成其他的工具。
    2. 因为数据涉及序列化,但是序列化方案比较多,因此定义了DataFactory来完成相应的序列化和反序列化。

    DataFactory包含两个方法,为具体数据类型和byte数组之间的相互转化,定义和实现如下:

    public interface DataFactory<T> extends Serializable {
        T toObject(byte[] data);
    
        byte[] toData(T t);
    
        class StringDataFactory implements DataFactory<String> {
            @Override
            public String toObject(byte[] data) {
                return data == null ? "" : new String(data);
            }
    
            @Override
            public byte[] toData(String s) {
                return s == null ? new byte[0] : s.getBytes();
            }
        }
    
        class SerializableDataFactory implements DataFactory<Serializable> {
    
            @Override
            public Serializable toObject(byte[] data) {
                try (ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(data))) {
                    return (Serializable) inputStream.readObject();
                } catch (IOException | ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
    
            @Override
            public byte[] toData(Serializable serializable) {
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                try (ObjectOutputStream outputStream = new ObjectOutputStream(bos)) {
                    outputStream.writeObject(serializable);
                    return bos.toByteArray();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
        //其他序列化方案请自行扩充
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    FileBigData的完整实现如下(其中UtilsFileIOUtils来自 AndroidUtilCode 库):

    public class FileBigData<T> implements Serializable {
    
        public static <T> CompletableFuture<FileBigData<T>> create(T value, DataFactory<T> factory) {
            return create(UUID.randomUUID().toString(), value, factory);
        }
    
        public static <T> CompletableFuture<FileBigData<T>> create(String tag, T value, DataFactory<T> factory) {
            FileBigData<T> bigData = new FileBigData<>(tag, factory);
            return bigData.set(value).thenApply((v) -> bigData);
        }
    
        private final String tag;
        private final DataFactory<T> factory;
    
        public FileBigData(String tag, DataFactory<T> factory) {
            this.tag = tag;
            this.factory = factory;
        }
    
        public CompletableFuture<Void> set(T t) {
            return CompletableFuture.runAsync(() -> {
                File file = new File(Utils.getApp().getCacheDir(), tag);
                boolean success = FileIOUtils.writeFileFromBytesByChannel(file, factory.toData(t), true);
                if (!success) {
                    throw new IllegalStateException("set failed!");
                }
            });
        }
    
        public CompletableFuture<T> get() {
            return CompletableFuture.supplyAsync(() -> {
                File file = new File(Utils.getApp().getCacheDir(), tag);
                byte[] bytes = FileIOUtils.readFile2BytesByMap(file);
                return factory.toObject(bytes);
            });
        }
    
        public CompletableFuture<T> getAndRemove() {
            return CompletableFuture.supplyAsync(() -> {
                File file = new File(Utils.getApp().getCacheDir(), tag);
                byte[] bytes = FileIOUtils.readFile2BytesByMap(file);
                file.delete();
                return factory.toObject(bytes);
            });
        }
    
        public boolean isPresent() {
            return new File(Utils.getApp().getCacheDir(), tag).exists();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
  • 相关阅读:
    SSM项目源码基于SSM实现的小说网站含前后台
    CCAA 认证通用基础卷之(一)合格评定基础 第一章合格评定基础知识
    C#实现的曲线方法
    Python3,我用这种方式讲解python模块,80岁的奶奶都说能理解。建议收藏 ~ ~
    软件测试 - Linux命令作用及案例
    CentOS部署FastDFS+Nginx并实现远程访问本地服务器中文件
    定时器浅析
    C++ Reference: Standard C++ Library reference: Containers: deque: deque: resize
    Check Point:企业部署零信任网络(ZTNA)的核心要素
    python教程:列表[list]和元组(tuple)
  • 原文地址:https://blog.csdn.net/zssrxt/article/details/133976256