Background
Since Android API 21, it's possible for apps to take screenshots globally and record the screen.
The problem
I've made a sample code out of all I've found on the Internet, but it has a few issues:
It's quite slow. Maybe it's possible to avoid it, at least for multiple screenshots, by avoiding the notification of it being removed till really not needed.
It has black margins on left and right, meaning something might be wrong with the calculations :
What I've tried
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ID = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.checkIfPossibleToRecordButton).setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.requestScreenshotPermission(MainActivity.this, REQUEST_ID);
}
});
findViewById(R.id.takeScreenshotButton).setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.takeScreenshot(MainActivity.this);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ID)
ScreenshotManager.INSTANCE.onActivityResult(resultCode, data);
}
}
layout/activity_main.xml
<LinearLayout
android:id="#+id/rootView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="#+id/checkIfPossibleToRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="request if possible"/>
<Button
android:id="#+id/takeScreenshotButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="take screenshot"/>
</LinearLayout>
ScreenshotManager
public class ScreenshotManager {
private static final String SCREENCAP_NAME = "screencap";
private static final int VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
public static final ScreenshotManager INSTANCE = new ScreenshotManager();
private Intent mIntent;
private ScreenshotManager() {
}
public void requestScreenshotPermission(#NonNull Activity activity, int requestId) {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
activity.startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), requestId);
}
public void onActivityResult(int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK && data != null)
mIntent = data;
else mIntent = null;
}
#UiThread
public boolean takeScreenshot(#NonNull Context context) {
if (mIntent == null)
return false;
final MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mIntent);
if (mediaProjection == null)
return false;
final int density = context.getResources().getDisplayMetrics().densityDpi;
final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
final Point size = new Point();
display.getSize(size);
final int width = size.x, height = size.y;
// start capture reader
final ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
final VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(SCREENCAP_NAME, width, height, density, VIRTUAL_DISPLAY_FLAGS, imageReader.getSurface(), null, null);
imageReader.setOnImageAvailableListener(new OnImageAvailableListener() {
#Override
public void onImageAvailable(final ImageReader reader) {
Log.d("AppLog", "onImageAvailable");
mediaProjection.stop();
new AsyncTask<Void, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(final Void... params) {
Image image = null;
Bitmap bitmap = null;
try {
image = reader.acquireLatestImage();
if (image != null) {
Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride(), rowStride = planes[0].getRowStride(), rowPadding = rowStride - pixelStride * width;
bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
return bitmap;
}
} catch (Exception e) {
if (bitmap != null)
bitmap.recycle();
e.printStackTrace();
}
if (image != null)
image.close();
reader.close();
return null;
}
#Override
protected void onPostExecute(final Bitmap bitmap) {
super.onPostExecute(bitmap);
Log.d("AppLog", "Got bitmap?" + (bitmap != null));
}
}.execute();
}
}, null);
mediaProjection.registerCallback(new Callback() {
#Override
public void onStop() {
super.onStop();
if (virtualDisplay != null)
virtualDisplay.release();
imageReader.setOnImageAvailableListener(null, null);
mediaProjection.unregisterCallback(this);
}
}, null);
return true;
}
}
The questions
Well it's about the problems:
Why is it so slow? Is there a way to improve it?
How can I avoid, between taking screenshots, the removal of the notification of them? When can I remove the notification? Does the notification mean it constantly takes screenshots?
Why does the output bitmap (currently I don't do anything with it, because it's still POC) have black margins in it? What's wrong with the code in this matter?
NOTE: I don't want to take a screenshot only of the current app. I want to know how to use it globally, for all apps, which is possible officially only by using this API, as far as I know.
EDIT: I've noticed that on CommonsWare website (here), it is said that the output bitmap is larger for some reason, but as opposed to what I've noticed (black margin in beginning AND end), it says it's supposed to be in the end:
For inexplicable reasons, it will be a bit larger, with excess unused
pixels on each row on the end.
I've tried what was offered there, but it crashes with the exception "java.lang.RuntimeException: Buffer not large enough for pixels" .
Why does the output bitmap (currently I don't do anything with it, because it's still POC) have black margins in it? What's wrong with the code in this matter?
You have black margins around your screenshot because you are not using realSize of the window you're in. To solve this:
Get the real size of the window:
final Point windowSize = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getRealSize(windowSize);
Use that to create your image reader:
imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);
This third step may not be required but I have seen otherwise in my app's production code (which runs on a variety of android devices out there). When you acquire an image for ImageReader and create a bitmap out of it. Crop that bitmap using the window size using below code.
// fix the extra width from Image
Bitmap croppedBitmap;
try {
croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, windowSize.x, windowSize.y);
} catch (OutOfMemoryError e) {
Timber.d(e, "Out of memory when cropping bitmap of screen size");
croppedBitmap = bitmap;
}
if (croppedBitmap != bitmap) {
bitmap.recycle();
}
I don't want to take a screenshot only of the current app. I want to know how to use it globally, for all apps, which is possible officially only by using this API, as far as I know.
To capture screen/take screenshot you need an object of MediaProjection. To create such object, you need pair of resultCode (int) and Intent. You already know how these objects are acquired and cache those in your ScreenshotManager class.
Coming back to taking screenshots of any app, you need to follow the same procedure of getting these variables resultCode and Intent but instead of caching it locally in your class variables, start a background service and pass these variables to the same like any other normal parameters. Take a look at how Telecine does it here. When this background service is started it can provide a trigger (a notification button) to the user which when clicked, will perform the same operations of capturing screen/taking screenshot as you are doing in your ScreenshotManager class.
Why is it so slow? Is there a way to improve it?
How much slow is it to your expectations? My use case for Media projection API is to take a screenshot and present it to the user for editing. For me the speed is decent enough. One thing I feel worth mentioning is that the ImageReader class can accept a Handler to a thread in setOnImageAvailableListener. If you provide a handler there, onImageAvailable callback will be triggered on the handler thread instead of the one that created the ImageReader. This will help you in NOT creating a AsyncTask (and starting it) when an image is available instead the callback itself will happen on a background thread. This is how I create my ImageReader:
private void createImageReader() {
startBackgroundThread();
imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);
ImageHandler imageHandler = new ImageHandler(context, domainModel, windowSize, this, notificationManager, analytics);
imageReader.setOnImageAvailableListener(imageHandler, backgroundHandler);
}
private void startBackgroundThread() {
backgroundThread = new HandlerThread(NAME_VIRTUAL_DISPLAY);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
Related
I am using the MediaRecorder and MediaProjection Api for recording the screen in android app. I am not able to get each frame that I can send over a rtmp stream. For publishing over an RTMP stream I am using JAVACV Android library.
For eg - In case of live streaming through camera, we can get each frame in onPreviewFrame() callback. After getting each frame I simply use the FFmpegFrameRecorder of JAVACV library for streaming each frame on to the rtmp url.
How can I achieve the same with screen recording?
Any help would be appreciated here. Thanks in advance!
Firstly there is no callback interface with MediaProjection which can give you buffer of screen captured frame, But it is quite possible with SurfaceTexture. Here is one implementation ScreenCapturerAndroid of screen capturing with SurfaceTexture.
An implementation of VideoCapturer to capture the screen content as a video stream.
Capturing is done by MediaProjection on a SurfaceTexture. We interact with this
SurfaceTexture using a SurfaceTextureHelper.
The SurfaceTextureHelper is created by the native code and passed to this capturer in
VideoCapturer.initialize(). On receiving a new frame, this capturer passes it
as a texture to the native code via CapturerObserver.onFrameCaptured(). This takes
place on the HandlerThread of the given SurfaceTextureHelper. When done with each frame,
the native code returns the buffer to the SurfaceTextureHelper
But if you want to send stream of your app view then following screen capturer will help you.
public class ScreenCapturer {
private boolean capturing = false;
private int fps=15;
private int width,height;
private Context context;
private int[] frame;
private Bitmap bmp;
private Canvas canvas;
private View contentView;
private Handler mHandler = new Handler();
public ScreenCapturer(Context context, View view) {
this.context = context;
this.contentView = view;
}
public void startCapturing(){
capturing = true;
mHandler.postDelayed(newFrame, 1000 / fps);
}
public void stopCapturing(){
capturing = false;
mHandler.removeCallbacks(newFrame);
}
private Runnable newFrame = new Runnable() {
#Override
public void run() {
if (capturing) {
int width = contentView.getWidth();
int height = contentView.getHeight();
if (frame == null ||
ScreenCapturer. this.width != width ||
ScreenCapturer.this.height != height) {
ScreenCapturer.this.width = width;
ScreenCapturer.this.height = height;
if (bmp != null) {
bmp.recycle();
bmp = null;
}
bmp = Bitmap.createBitmap(width,
height, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bmp);
frame = new int[width * height];
}
canvas.saveLayer(0, 0, width, height, null);
canvas.translate(-contentView.getScrollX(), - contentView.getScrollY());
contentView.draw(canvas);
bmp.getPixels(frame, 0, width, 0, 0, width, height);
//frame[] is a rgb pixel array compress it to YUV if want and send over RTMP
canvas.restore();
mHandler.postDelayed(newFrame, 1000 / fps);
}
}
};
}
Usage
...
//Use this in your activity
private View parentView;
parentView = findViewById(R.id.parentView);
capturer = new ScreenCapturer(this,parentView);
//To start capturing
capturer.startCapturing();
//To Stop capturer
capturer.stopCapturing();
Using this you can send a view contents to RTMP stream, You may use parent view of your activity to capture all contents of an activity.
I wrote test application for capturing the images with MediaProjection class.
imageReader = ImageReader.newInstance(currentWidth, currentHeight, PixelFormat.RGBA_8888, 2);
imageReader.setOnImageAvailableListener(this, null);
virtualDisplay = mediaProjection.createVirtualDisplay("captureDisplay",
currentWidth, currentHeight, DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC|
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR |
DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE |
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, Screen.getDensity(mContext), imageReader.getSurface(), null, null);
// DisplayManager flags are trails only
and in onImageAvailable(ImageReader reader) method
i tried to get the image as follows:
Bitmap bitmap;
Image mImage = null;
try {
mImage = mImageReader .acquireLatestImage();
if (img != null) {
final Image.Plane[] planes = mImage.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mImage.getWidth();
bitmap = Bitmap.createBitmap(mImage.getWidth() + rowPadding/pixelStride, mImage.getHeight(), Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] rawData = lastImageAcquiredRaw = stream.toByteArray();
if (rawData != null) {
Bitmap fullScreen = BitmapFactory.decodeByteArray(rawData, 0, rawData.length);
savebitmap(fullScreen,"image",i); //Saving bitmap in storage
}
}
Till now I am OK and I am getting correct Image when my app is in landscape orientation. Problem facing is on orientation change, i am not getting proper images. Some times again on changing back to landscape also not capturing properly.
I gone through ImageReader.java class. Nothing has mentioned like need to handle in orientation changes.
Tried using acquireNextImageNoThrowISE(), acquireNextImage() but no use.
Did any one tried or having possibility to get proper image in orientation?
Please help me in getting proper image.
I know this post is a bit old, but the last couple of days I am strangling with the same thing. I am also using MediaProjection API, VirtualDisplay and ImageReader's surface. The solution I am going to demonstrate is not 100% full-proof, so if anyone has any useful suggestions is more than welcome.
Step one: add an OrientationChangeCallback after you create Virtual Display
OrientationChangeCallback orientationChangeCallback = new OrientationChangeCallback(context);
if (orientationChangeCallback.canDetectOrientation()) {
orientationChangeCallback.enable();
}
Step two: define the orientation callback, which in essence restarts the capture
private class OrientationChangeCallback extends OrientationEventListener {
OrientationChangeCallback(Context context) {
super(context);
}
#Override
public void onOrientationChanged(int orientation) {
final int rotation = mDisplay.getRotation();
if(rotation != mRotation) {
mRotation = rotation;
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
}
if (mImageReader != null) {
mImageReader.setOnImageAvailableListener(null, null);
}
if (!mProjectionStopped) {
createVirtualDisplay();
}
}
}
}
Note that I am holding references to ImageReader and VirtualDisplay, and also have a volatile boolean mRotation to check if the screen actually rotated.
The problem I have is that on some devices (Nexus 5X included) the image can become corrupt after X number or rotations (usually 10-20). If I rotate the device one more time the image is captured correctly. On quick devices (Google Pixel) I cannot replicate the corrupt image issue, so I am guessing it might be a synchronisation issue.
I am using NetworkImageView to show some covers downloaded from a remote URL and I successfully manage to cache and show them, but I want to let users set their own cover images if they want.
I tried to use setImageUrl method with Uri.fromFile(mCoverFile).toString() as arguments, but it doesn't work. Since it is a mix of remote and local images I can't switch to regular ImageViews, so I was wondering if there's any way to enable loading of local images.
I am of course aware of the ImageView's setImageBitmap method, but NetworkImageView automatically resizes the created Bitmap and also prevents View recycling in GridViews and ListViews.
UPDATE: njzk2's answer did the trick. To autoresize the Bitmap according to your View size, then just copy the ImageRequest.doParse method from Volley's source.
NetworkImageView uses ImageLoader, which in turn uses an ImageCache.
You can provide a custom ImageCache with your images, provided you use the same mechanism for keys:
return new StringBuilder(url.length() + 12).append("#W").append(maxWidth)
.append("#H").append(maxHeight).append(url).toString();
url is not tested before the actual request would be done, so no issue here.
Typically, your 'cache' could look like :
public class MyCache implements ImageLoader.ImageCache {
#Override
public Bitmap getBitmap(String key) {
if (key.contains("file://")) {
return BitmapFactory.decodeFile(key.substring(key.indexOf("file://") + 7));
} else {
// Here you can add an actual cache
return null;
}
}
#Override
public void putBitmap(String key, Bitmap bitmap) {
// Here you can add an actual cache
}
}
You use it like :
imageView.setImageUrl(Uri.fromFile(mCoverFile).toString(), new MyCache());
(This has not been actually tested and there may be some adjustments to do)
Thank you for your answer. I wrote some code based on your help.
usage: just use LocalImageCache.class as Cache. No more code to change.
private ImageLoader mLocalImageLoader;
mLocalImageLoader = new ImageLoader(mRequestQueue,
new LocalImageCache(mCtx));
NetworkImageView test = (NetworkImageView) findViewById(R.id.iv_test);
test.setImageUrl("/storage/emulated/0/DCIM/Philm/2017_03_24_01_.png", MySingleton.getInstance(this.getApplicationContext()).getLocalImageLoader());
public class LocalImageCache extends LruCache<String, Bitmap> implements ImageLoader.ImageCache {
public LocalImageCache(int maxSize) {
super(maxSize);
}
public LocalImageCache(Context ctx) {
this(getCacheSize(ctx));
}
#Override
public Bitmap getBitmap(String key) {
key = key.substring(key.indexOf("/"));
Bitmap result = get(key);
Log.d("TAG", key);
if (result == null) {
Bitmap temp = BitmapFactory.decodeFile(key);
put(key, temp);
return temp;
} else {
return result;
}
}
#Override
public void putBitmap(String key, Bitmap bitmap) {
// Here you can add an actual cache
// Never touch here
}
// 默认屏幕5倍的图片缓存
// Returns a cache size equal to approximately three screens worth of images.
public static int getCacheSize(Context ctx) {
final DisplayMetrics displayMetrics = ctx.getResources().
getDisplayMetrics();
final int screenWidth = displayMetrics.widthPixels;
final int screenHeight = displayMetrics.heightPixels;
// 4 bytes per pixel
final int screenBytes = screenWidth * screenHeight * 4;
return screenBytes * 5;
}
#Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
}
NetworkImageView extends ImageView. You should be able to use the same methods as a regular ImageView
image.setImageResource(R.drawable.my_image);
or
imageView.setImageBitmap(BitmapFactory.decodeFile(imagePath));
I have quite a horrible performance when trying to get the pixels of the Camera preview.
The image format is around 600x900.
The preview rate is quite stable 30fps on my HTC one.
As soon as I try to get the pixels of the image the framerate drops to below 5!
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
Bitmap bmp = mTextureView.getBitmap();
int width = bmp.getWidth();
int height = bmp.getHeight();
int[] pixels = new int[bmp.getHeight() * bmp.getWidth()];
bmp.getPixels(pixels, 0, width, 0, 0, width, height);
}
The performance is so slow, it's not really bearable.
Now my only 'easy' solution is to skip frames to at least keep some visual performance.
But I'd actually like to have that code perform faster.
I would appreciate any ideas and suggestions, maybe someone solved this already ?
UPDATE
getbitmap: 188.341ms
array: 122ms
getPixels: 12.330ms
recycle: 152ms
It takes 190 milliseconds just to get the bitmap !! That's the problem
I digged into this for several hours.
So short answer: I found no way to avoid getBitmap() and increase performance.
The function is known to be slow, I found many similar questions and no results.
However I found another solution which is about 3 times faster and solves the problem for me.
I keep using the TextureView approach, I use it because it gives more freedom on how to display the Camera preview (for example I can display the camera live preview in a small window of my own aspect ratio without distortion)
But to work with the image data I do not use onSurefaceTextureUpdated() anymore.
I registered a callback of the cameraPreviewFrame which gives me the pixel data I need.
So no getBitmap anymore, and a lot more speed.
Fast, new code:
myCamera.setPreviewCallback(preview);
Camera.PreviewCallback preview = new Camera.PreviewCallback()
{
public void onPreviewFrame(byte[] data, Camera camera)
{
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
Image img = new Image(size.width, size.height, "Y800");
}
};
Slow:
private int[] surface_pixels=null;
private int surface_width=0;
private int surface_height=0;
#Override
public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture)
{
int width,height;
Bitmap bmp= mTextureView.getBitmap();
height=barcodeBmp.getHeight();
width=barcodeBmp.getWidth();
if (surface_pixels == null)
{
surface_pixels = new int[height * width];
} else
{
if ((width != surface_width) || (height != surface_height))
{
surface_pixels = null;
surface_pixels = new int[height * width];
}
}
if ((width != surface_width) || (height != surface_height))
{
surface_height = barcodeBmp.getHeight();
surface_width = barcodeBmp.getWidth();
}
bmp.getPixels(surface_pixels, 0, width, 0, 0, width, height);
bmp.recycle();
Image img = new Image(width, height, "RGB4");
}
I hope this helps some people with the same problem.
If someone should find a way to create a bitmap in a fast manner within onSurfaceTextureUpdated please respond with a code sample.
Please try:
public void OnSurfaceTextureUpdated(SurfaceTexture surface) {
if (IsBusy) {
return;
}
IsBusy = true;
DoBigWork();
IsBusy = false;
}
I was a frequent guest at stackoverflow until I ran into a problem that I really couldn't find anything existing about. So here is my first question:
I am building a camera app in which the user can take several pictures before proceeding to the next step. I want to give the user the possibility to review and delete pictures while stying in the camera stage, so I have written a custom View to show Thumbnails of the already captured images with a delete button. These "Thumbviews" are contained in a LinearLayout that is located on top of the camerapreview-SurfaceView and has a default visibility of "GONE". The user can toggle the visibility with a button.
It all works fine, but I have one problem:
When I take more than about 10 pictures, I get an OutOfMemoryError. The thumbnails are really small and don't take a lot of memory and also I recycle the original Bitmaps and perform a System.gc() after creating the thumbs.
The weird thing is, when I press the button that sets the visibility of the containing LinearLayout to "VISIBLE" and again to "GONE", apparently all the memory gets freed and I can take many more pictures than 10.
I've tried switching the visibility in code but that doesn't work, and also destroying the drawing cache.
There has to be another way to free that memory besides pushing my visibility button 2 times ;-)
Here's the code for the ThumbView:
public class ThumbView extends View {
private Bitmap mBitmap;
private Bitmap mScaledBitmap;
private int mWidth, mHeight, mPosX, mPosY;
static private Bitmap mDeleteBitmap;
private File mPreviewFile;
private File mFinalFile;
private Orientation mOrientation;
private boolean mRed;
public ThumbView(Context context, Bitmap bitmap, File previewFile, File finalFile, Orientation orientation) {
super(context);
mBitmap = bitmap;
mPreviewFile = previewFile;
mFinalFile = finalFile;
mOrientation = orientation;
if(mDeleteBitmap != null)
return;
mDeleteBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.deletebutton);
}
public void deleteFile()
{
if(mPreviewFile != null && mPreviewFile.exists())
{
mPreviewFile.delete();
}
if(mFinalFile != null && mFinalFile.exists())
{
mFinalFile.delete();
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec);
setMeasuredDimension(mWidth, mWidth);
if(mBitmap == null)
return;
mHeight = mWidth;
float bitmapRatio = mBitmap.getWidth() / (float) mBitmap.getHeight();
if(bitmapRatio > 1)
{
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, mWidth,
(int)(mWidth/bitmapRatio), true);
mPosY = (mWidth-mScaledBitmap.getHeight())/2;
}
else
{
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, (int)(mHeight*bitmapRatio),
mHeight, true);
mPosX = (mHeight-mScaledBitmap.getWidth())/2;
}
Matrix mtx = new Matrix();
mtx.postRotate(-90);
Bitmap b = Bitmap.createBitmap(mScaledBitmap, 0, 0, mScaledBitmap.getWidth(), mScaledBitmap.getHeight(), mtx, true);
mScaledBitmap = b;
b = null;
mBitmap.recycle();
mBitmap = null;
System.gc();
}
public boolean deleteButtonPressed(float x, float y)
{
Rect r = new Rect(mPosY, mPosX, mPosY+mDeleteBitmap.getWidth(),
mPosX+mDeleteBitmap.getHeight());
if(r.contains((int)x, (int)y))
{
return true;
}
return false;
}
public void setRed(boolean red)
{
mRed = red;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(mScaledBitmap, mPosY, mPosX, new Paint());
canvas.drawBitmap(mDeleteBitmap, mPosY, mPosX, new Paint());
if(mRed)
canvas.drawColor(0x55FF0000);
}
}
The "why does it not break" answer's easy. When the visibility of a child view (or container) is set to GONE, the parent layout will (generally) skip it and not even bother rendering it. It's not "hidden", it's not there at all.
If your thumbnails are really thumbnails you shouldn't be running out of memory, however, I think you're not downsampling them (I could be wrong). How are you showing them? You should share that piece of code. (New Photo -> Thumbnail Image -> Image View)
I am so stupid. Obviously my onMeasure() won't be called while the View stays GONE and therefore the original bitmap stays in memory. I changed visibility to INVISIBLE and everything works fine now.