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();
    }
}
相关推荐
Estar.Lee32 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
找藉口是失败者的习惯1 小时前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey2 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!4 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟5 小时前
Android音频采集
android·音视频
小白也想学C6 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程6 小时前
初级数据结构——树
android·java·数据结构
闲暇部落8 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX10 小时前
Android 分区相关介绍
android
大白要努力!11 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle