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中,就像这样:

java 复制代码
public class MemoryBigData<T> implements Serializable {
    private static final ConcurrentHashMap<String, Object> CACHE_MAP = new ConcurrentHashMap<>();

    //......
}

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

java 复制代码
Intent intent = new Intent(this, TestActivity.class);
intent.putExtra("bigIntData", MemoryBigData.of(new int[1024 * 1024]));
startActivity(intent);

读取参数的时候,就是:

java 复制代码
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());

MemoryBigData类的完整实现如下:

java 复制代码
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);
    }
}
FileBigData

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

java 复制代码
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);
        });

而读取的代码就变成了:

java 复制代码
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. 我使用了JDK自带的CompletableFuture类来完成异步操作,这部分可以换成其他的工具。
  2. 因为数据涉及序列化,但是序列化方案比较多,因此定义了DataFactory来完成相应的序列化和反序列化。

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

java 复制代码
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);
            }
        }
    }

    //其他序列化方案请自行扩充
}

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

java 复制代码
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();
    }
}
相关推荐
潜龙95274 分钟前
第3.2.3节 Android动态调用链路的获取
android·调用链路
追随远方40 分钟前
Android平台FFmpeg音视频开发深度指南
android·ffmpeg·音视频
撰卢2 小时前
MySQL 1366 - Incorrect string value:错误
android·数据库·mysql
恋猫de小郭2 小时前
Flutter 合并 ‘dot-shorthands‘ 语法糖,Dart 开始支持交叉编译
android·flutter·ios
牛马程序小猿猴2 小时前
15.thinkphp的上传功能
android
林家凌宇3 小时前
Flutter 3.29.3 花屏问题记录
android·flutter·skia
时丶光3 小时前
Android 查看 Logcat (可纯手机方式 无需电脑)
android·logcat
血手人屠喵帕斯3 小时前
事务连接池
android·adb
恋猫de小郭4 小时前
React Native 前瞻式重大更新 Skia & WebGPU & ThreeJS,未来可期
android·javascript·flutter·react native·react.js·ios
一人一萧十只猫�5 小时前
MySQL 从入门到精通(三):日志管理详解 —— 从排错到恢复的核心利器
android·mysql·adb