In my activity, I create a Bitmap
object and then I need to launch another Activity
,
How can I pass this Bitmap
object from the sub-activity (the one which is going to be launched)?
Bitmap
implements Parcelable
, so you could always pass it with the intent:
Intent intent = new Intent(this, NewActivity.class);
intent.putExtra("BitmapImage", bitmap);
and retrieve it on the other end:
Intent intent = getIntent();
Bitmap bitmap = (Bitmap) intent.getParcelableExtra("BitmapImage");
android.os.TransactionTooLargeException
thrown with this. –
Theodora Actually, passing a bitmap as a Parcelable will result in a "JAVA BINDER FAILURE" error. Try passing the bitmap as a byte array and building it for display in the next activity.
I shared my solution here:
how do you pass images (bitmaps) between android activities using bundles?
Passsing bitmap as parceable in bundle between activity is not a good idea because of size limitation of Parceable(1mb). You can store the bitmap in a file in internal storage and retrieve the stored bitmap in several activities. Here's some sample code.
To store bitmap in a file myImage in internal storage:
public String createImageFromBitmap(Bitmap bitmap) {
String fileName = "myImage";//no .png or .jpg needed
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
FileOutputStream fo = openFileOutput(fileName, Context.MODE_PRIVATE);
fo.write(bytes.toByteArray());
// remember close file output
fo.close();
} catch (Exception e) {
e.printStackTrace();
fileName = null;
}
return fileName;
}
Then in the next activity you can decode this file myImage to a bitmap using following code:
//here context can be anything like getActivity() for fragment, this or MainActivity.this
Bitmap bitmap = BitmapFactory.decodeStream(context.openFileInput("myImage"));
Note A lot of checking for null and scaling bitmap's is ommited.
openFileOutput
. –
Sextan If the image is too large and you can't save&load it to the storage, you should consider just using a global static reference to the bitmap (inside the receiving activity), which will be reset to null on onDestory, only if "isChangingConfigurations" returns true.
Compress and Send Bitmap
The accepted answer will crash when the Bitmap
is too large. I believe it's a 1MB limit. The Bitmap
must be compressed into a different file format such as a JPG represented by a ByteArray
, then it can be safely passed via an Intent
.
Implementation
The function is contained in a separate thread using Kotlin Coroutines because the Bitmap
compression is chained after the Bitmap
is created from an url String
. The Bitmap
creation requires a separate thread in order to avoid Application Not Responding (ANR) errors.
Concepts Used
- Kotlin Coroutines notes.
- The Loading, Content, Error (LCE) pattern is used below. If interested you can learn more about it in this talk and video.
- LiveData is used to return the data. I've compiled my favorite LiveData resource in these notes.
- In Step 3,
toBitmap()
is a Kotlin extension function requiring that library to be added to the app dependencies.
Code
1. Compress Bitmap
to JPG ByteArray
after it has been created.
Repository.kt
suspend fun bitmapToByteArray(url: String) = withContext(Dispatchers.IO) {
MutableLiveData<Lce<ContentResult.ContentBitmap>>().apply {
postValue(Lce.Loading())
postValue(Lce.Content(ContentResult.ContentBitmap(
ByteArrayOutputStream().apply {
try {
BitmapFactory.decodeStream(URL(url).openConnection().apply {
doInput = true
connect()
}.getInputStream())
} catch (e: IOException) {
postValue(Lce.Error(ContentResult.ContentBitmap(ByteArray(0), "bitmapToByteArray error or null - ${e.localizedMessage}")))
null
}?.compress(CompressFormat.JPEG, BITMAP_COMPRESSION_QUALITY, this)
}.toByteArray(), "")))
}
}
ViewModel.kt
//Calls bitmapToByteArray from the Repository
private fun bitmapToByteArray(url: String) = liveData {
emitSource(switchMap(repository.bitmapToByteArray(url)) { lce ->
when (lce) {
is Lce.Loading -> liveData {}
is Lce.Content -> liveData {
emit(Event(ContentResult.ContentBitmap(lce.packet.image, lce.packet.errorMessage)))
}
is Lce.Error -> liveData {
Crashlytics.log(Log.WARN, LOG_TAG,
"bitmapToByteArray error or null - ${lce.packet.errorMessage}")
}
}
})
}
2. Pass image as ByteArray
via an Intent
.
In this sample it's passed from a Fragment to a Service. It's the same concept if being shared between two Activities.
Fragment.kt
ContextCompat.startForegroundService(
context!!,
Intent(context, AudioService::class.java).apply {
action = CONTENT_SELECTED_ACTION
putExtra(CONTENT_SELECTED_BITMAP_KEY, contentPlayer.image)
})
3. Convert ByteArray
back to Bitmap
.
Utils.kt
fun ByteArray.byteArrayToBitmap(context: Context) =
run {
BitmapFactory.decodeByteArray(this, BITMAP_OFFSET, size).run {
if (this != null) this
// In case the Bitmap loaded was empty or there is an error I have a default Bitmap to return.
else AppCompatResources.getDrawable(context, ic_coinverse_48dp)?.toBitmap()
}
}
Because Intent has size limit . I use public static object to do pass bitmap from service to broadcast ....
public class ImageBox {
public static Queue<Bitmap> mQ = new LinkedBlockingQueue<Bitmap>();
}
pass in my service
private void downloadFile(final String url){
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap b = BitmapFromURL.getBitmapFromURL(url);
synchronized (this){
TaskCount--;
}
Intent i = new Intent(ACTION_ON_GET_IMAGE);
ImageBox.mQ.offer(b);
sendBroadcast(i);
if(TaskCount<=0)stopSelf();
}
});
}
My BroadcastReceiver
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
LOG.d(TAG, "BroadcastReceiver get broadcast");
String action = intent.getAction();
if (DownLoadImageService.ACTION_ON_GET_IMAGE.equals(action)) {
Bitmap b = ImageBox.mQ.poll();
if(b==null)return;
if(mListener!=null)mListener.OnGetImage(b);
}
}
};
It might be late but can help. On the first fragment or activity do declare a class...for example
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
description des = new description();
if (requestCode == PICK_IMAGE_REQUEST && data != null && data.getData() != null) {
filePath = data.getData();
try {
bitmap = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), filePath);
imageView.setImageBitmap(bitmap);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
constan.photoMap = bitmap;
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static class constan {
public static Bitmap photoMap = null;
public static String namePass = null;
}
Then on the second class/fragment do this..
Bitmap bm = postFragment.constan.photoMap;
final String itemName = postFragment.constan.namePass;
Hope it helps.
All of the above solutions doesn't work for me, Sending bitmap as parceableByteArray
also generates error android.os.TransactionTooLargeException: data parcel size
.
Solution
- Saved the bitmap in internal storage as:
public String saveBitmap(Bitmap bitmap) {
String fileName = "ImageName";//no .png or .jpg needed
try {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bytes);
FileOutputStream fo = openFileOutput(fileName, Context.MODE_PRIVATE);
fo.write(bytes.toByteArray());
// remember close file output
fo.close();
} catch (Exception e) {
e.printStackTrace();
fileName = null;
}
return fileName;
}
- and send in
putExtra(String)
as
Intent intent = new Intent(ActivitySketcher.this,ActivityEditor.class);
intent.putExtra("KEY", saveBitmap(bmp));
startActivity(intent);
- and Receive it in other activity as:
if(getIntent() != null){
try {
src = BitmapFactory.decodeStream(openFileInput("myImage"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
You can create a bitmap transfer. try this....
In the first class:
1) Create:
private static Bitmap bitmap_transfer;
2) Create getter and setter
public static Bitmap getBitmap_transfer() {
return bitmap_transfer;
}
public static void setBitmap_transfer(Bitmap bitmap_transfer_param) {
bitmap_transfer = bitmap_transfer_param;
}
3) Set the image:
ImageView image = (ImageView) view.findViewById(R.id.image);
image.buildDrawingCache();
setBitmap_transfer(image.getDrawingCache());
Then, in the second class:
ImageView image2 = (ImageView) view.findViewById(R.id.img2);
imagem2.setImageDrawable(new BitmapDrawable(getResources(), classe1.getBitmap_transfer()));
In my case, the way mentioned above didn't worked for me. Every time I put the bitmap in the intent, the 2nd activity didn't start. The same happened when I passed the bitmap as byte[].
I followed this link and it worked like a charme and very fast:
package your.packagename
import android.graphics.Bitmap;
public class CommonResources {
public static Bitmap photoFinishBitmap = null;
}
in my 1st acitiviy:
Constants.photoFinishBitmap = photoFinishBitmap;
Intent intent = new Intent(mContext, ImageViewerActivity.class);
startActivity(intent);
and here is the onCreate() of my 2nd Activity:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bitmap photo = Constants.photoFinishBitmap;
if (photo != null) {
mViewHolder.imageViewerImage.setImageDrawable(new BitmapDrawable(getResources(), photo));
}
}
CommonResources.photoFinishBitmap
instead of Constants.photoFinishBitmap
. –
Vanquish © 2022 - 2024 — McMap. All rights reserved.
URI
orResourceID
of the bitmap and not the bitmap itself. Passing the entire bitmap requires a lot of memory. Passing the URL requires very little memory and allows each activity to load and scale the bitmap as they need it. – Foliose