快速COPY,请滑到最后
因为Intent只能传输数据容量很小的基本数据,当位图很大的时候,很有可能会出现传输错误
为什么不能传输很大的Bitmap呢?
Intent 传递大数据,会出现 TransactionTooLargeException 的场景,在 TransactionTooLargeException(https://developer.android.com/reference/android/os/TransactionTooLargeException.html) 的文档中,其实已经将触发原因详细说明了。
问题思路:
Intent 传输数据的机制中,用到了 Binder。Intent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。而这个 Binder 事务缓冲区具有一个有限的固定大小,当前为 1MB。可别以为传递 1MB 以下的数据就安全了,这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。也就是说 Intent 在 Activity 间传输数据,本身也不适合传递太大的数据。
细节说明1:
Intent 使用 Bundle 存储数据,到底是值传递(深拷贝)还是引用传递?
1、 Intent 传输的数据,都存放在一个 Bundle 类型的对象 mExtras 中,Bundle 要求所有存储的数据,都是可被序列化的。
2、在 Android 中,序列化数据需要实现 Serializable 或者 Parcelable。对于基础数据类型的包装类,本身就是实现了 Serializable,而我们自定义的对象,按需实现这两个序列化接口的其中一个即可。
细节说明2: 是不是只要通过 Bundle 传递数据,就会面临序列化的问题?
并不是,Activity 之间传递数据,首先要考虑跨进程的问题,而 Android 中又是通过 Binder 机制来解决跨进程通信的问题。涉及到跨进程,对于复杂数据就要涉及到序列化和反序列化的过程,这就注定是一次值传递(深拷贝)的过程。
这个问题用反证法也可以解释,如果是引用传递,那传递过去的只是对象的引用,指向了对象的存储地址,就只相当于一个 Int
的大小,也就根本不会出现 TransactionTooLargeException 异常。
细节说明3:传输数据序列化和 Bundle 没有关系,只与 Binder 的跨进程通信有关。
在 Android 中,使用 Bundle 传输数据,并非 Intent 独有的。例如使用弹窗时,DialogFragment 中也可以通过 setArguments(Bundle) 传递一个 Bundle 对象给对话框。
Fragment 本身是不涉及跨进程的,这里虽然使用了 Bundle 传输数据,但是并没有通过
Binder,也就是不存在序列化和反序列化。和 Fragment 数据传递相关的 Bundle,其实传递的是原对象的引用。
如何解决:
原因既然在于 Binder 传输限制了数据的大小,那我们不走 Binder 通信就好了。
可以从数据源上来考虑:
例如 Bitmap,本身就已经实现了 Parcelable 是可以支持序列化的。用 Intent 传输,稍微大一点的图一定会出现
TransactionTooLargeException。当然真是业务场景,肯定不存在传递 Bitmap 的情况。那就先看看这个图片的数据源。Drawable?本地文件?线上图片?无论数据源在哪里,我们只需要传递一个
drawable_id、路径、URL,就可以还原这张图片,无需将这个 Bitmap
对象传递过去。大数据总有数据源,从数据源还原数据,对我们而言只是调用一个方法而已。
源自《Android 开发者手册》
第一种:使用Intent,通过parcelable方式传递
//Activity One 【第一个活动ActivityA (传递)】
Intent intent = new Intent(this, Two.class);
intent.putExtra("key", bitmap);
//Activity Two 【第二个活动ActivityB (接收)】
//接收类型为 Bitmap ,通过getParcelableExtra接收key
Intent intent = new Intent();
Bitmap bitmap = (Bitmap) intent.getParcelableExtra("key");
注意:在第二个活动中,Bitmap一定要强转(Bitmap)不然拿不到
第二种:使用Intent,通过字节数组传递
//Activity One 【第一个活动ActivityA (传递)】
Intent intent = new Intent(this,Two.class);
ByteArrayOutputStream bs = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bs);
byte[] bitmapByte = bs.toByteArray();
intent.putExtra("bp", bitmapByte);
this.startActivity(intent);
//Activity Two 【第二个活动ActivityB (接收)】
byte[] byteTemp = intent.getByteArrayExtra("bitmap");
bitmap = BitmapFactory.decodeByteArray(byteTemp, 0, byteTemp.length);
第三种:以流的形式写入到文件到,通过Intent传文件路径到另一个Activity
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
public class BitmapUtil
{
/**
* 保存Bitmap为文件;可能报空指针是因为没有配置权限
*
* @param bmp
* @param filename
* @return
*/
public static void saveBitmap2file(Bitmap bmp, String filename){
CompressFormat format = Bitmap.CompressFormat.JPEG;
int quality = 100;
OutputStream stream = null;
try{
stream = new FileOutputStream(Environment.getExternalStorageDirectory().getPath()
+ "/"
+ filename
+ ".jpg");
} catch (FileNotFoundException e){
e.printStackTrace();
}
bmp.compress(format, quality, stream);
try{
stream.flush();
stream.close();
} catch (IOException e){
e.printStackTrace();
}
}
/**
* 读取文件为Bitmap
*
* @param filename
* @return
* @throws FileNotFoundException
*/
public static Bitmap getBitmapFromFile(String filename){
try{
return BitmapFactory.decodeStream(newFileInputStream(Environment.getExternalStorageDirectory().getPath()
+ "/"
+ filename
+ ".jpg"));
} catch (FileNotFoundException e){
e.printStackTrace();
}
return null;
}
}