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();
    }
}
相关推荐
无极程序员38 分钟前
PHP常量
android·ide·android studio
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农2 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶2 小时前
Android——多线程、线程通信、handler机制
android
L72562 小时前
Android的Handler
android
清风徐来辽2 小时前
Android HandlerThread 基础
android
HerayChen3 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野3 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing11233 小时前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件3 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio