示例代码:https://gitee.com/spectre1225/big-data-demo.git
问题场景
在Activity之间跳转时,可能因为传递大型数据导致抛出TransactionTooLargeException
异常,或许是传递一个Bitmap
,或许是一个长度非常长的List
或数组,甚至可能是一个长得离谱的String
。
碰到这类问题的时候,我们通常会采用:
- 通过某个全局可见的第三者(比如某个单例类)在内存中共享这个大型数据。
缺点:1. 类似全局变量,不安全;2. 无法用于闹钟或通知中的Bundle,因为触发时应用可能已经重启过了,内存信息被清理了。
- 写到数据库/文件中,读取的时候访问数据库/文件。
缺点:1. 操作和维护麻烦。
且上面方法有一个共同的问题:我需要一个参数,却得到了一个环境变量/全局上下文变量。
因此,我需要一种方案,可以:
- 避免
TransactionTooLargeException
异常 - 在使用上和
Intent
/Bundle
兼容
实现思路
其实不管是写文件/数据库,还是通过内存变量来共享,都解决了TransactionTooLargeException
异常,主要问题还是没法像其他数据那样通过Intent
/Bundle
来传递,达不到"参数"的效果。
那么我们可以封装一种数据结构,可以通过Intent
/Bundle
来传递和使用,但内部使用文件/数据库/全局变量就可以了。
MemoryBigData
首先是通过内存变量的方式,我们只要定义一个类,使之实现Serializable
或Parcelable
接口,用于序列化传输,然后数据放在内部定义的静态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());
});
这里有两个注意点:
- 我使用了JDK自带的
CompletableFuture
类来完成异步操作,这部分可以换成其他的工具。 - 因为数据涉及序列化,但是序列化方案比较多,因此定义了
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
的完整实现如下(其中Utils
和FileIOUtils
来自 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();
}
}