如何将位图从 Activity 传递给服务
Posted
技术标签:
【中文标题】如何将位图从 Activity 传递给服务【英文标题】:How to pass Bitmap from Activity to a Service 【发布时间】:2018-12-14 00:18:34 【问题描述】:我正在 android Studio 中编写一个小型 Android 程序,该程序使用 MediaProjection 抓取整个屏幕的屏幕截图,然后我想将该屏幕截图作为位图传递给我的系统叠加层(基于聊天头示例)。当 MediaProjection 运行并且 Imagereader 创建位图时,我将位图传递到一个类全局变量中,这样我就可以将它传递到我的 System Overlay 服务并显示它。我遇到了一个问题,当服务内的 Imageview 尝试读取 bmp 时,我收到如下错误:
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@347a4d5
据我了解,垃圾收集器很快就会吃掉传递的位图,当系统覆盖层试图抓取它时,数据就消失了。有人可以为我指出如何保留位图的正确方向吗?
主要活动
package com.example.chatheads;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.media.ImageReader;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.Image;
import android.media.ImageReader;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
public class MainActivity extends Activity
Button startService,stopService;
private static final String TAG = MainActivity.class.getName();
private static final int REQUEST_CODE = 100;
private static String STORE_DIRECTORY;
private static int IMAGES_PRODUCED;
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;
private static MediaProjection sMediaProjection;
private MediaProjectionManager mProjectionManager;
private ImageReader mImageReader;
private Handler mHandler;
private Display mDisplay;
private VirtualDisplay mVirtualDisplay;
private int mDensity;
private int mWidth;
private int mHeight;
private int mRotation;
private OrientationChangeCallback mOrientationChangeCallback;
private Image image = null;
private Bitmap bitmap = null;
Globals sharedData = Globals.getInstance();
public Bitmap getObjectContainer()
return bitmap;
private class ImageAvailableListener implements ImageReader.OnImageAvailableListener
@Override
public void onImageAvailable(ImageReader reader)
FileOutputStream fos = null;
try
image = mImageReader.acquireLatestImage();
if (image != null)
Image.Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
// create bitmap
bitmap = Bitmap.createBitmap(mWidth + rowPadding / pixelStride, mHeight, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
sharedData.setScreenshot(bitmap);
// write bitmap to a file
// fos = new FileOutputStream(STORE_DIRECTORY + "/myscreen_" + IMAGES_PRODUCED + ".png");
//bitmap.compress(CompressFormat.JPEG, 100, fos);
IMAGES_PRODUCED++;
//Log.e(TAG, "captured image: " + IMAGES_PRODUCED);
String s = ("captured image: " + String.valueOf(IMAGES_PRODUCED));
Toast toast1 = Toast.makeText(getBaseContext(),s, Toast.LENGTH_SHORT);
toast1.show();
catch (Exception e)
e.printStackTrace();
finally
if (fos!=null)
try
fos.close();
catch (IOException ioe)
ioe.printStackTrace();
if (bitmap!=null)
bitmap.recycle();
if (image!=null)
image.close();
private class OrientationChangeCallback extends OrientationEventListener
public OrientationChangeCallback(Context context)
super(context);
@Override
public void onOrientationChanged(int orientation)
synchronized (this)
final int rotation = mDisplay.getRotation();
if (rotation != mRotation)
mRotation = rotation;
try
// clean up
if(mVirtualDisplay != null) mVirtualDisplay.release();
if(mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
// re-create virtual display depending on device width / height
createVirtualDisplay();
catch (Exception e)
e.printStackTrace();
private class MediaProjectionStopCallback extends MediaProjection.Callback
@Override
public void onStop()
Log.e("ScreenCapture", "stopping projection.");
mHandler.post(new Runnable()
@Override
public void run()
if(mVirtualDisplay != null) mVirtualDisplay.release();
if(mImageReader != null) mImageReader.setOnImageAvailableListener(null, null);
if(mOrientationChangeCallback != null) mOrientationChangeCallback.disable();
sMediaProjection.unregisterCallback(MediaProjectionStopCallback.this);
);
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sharedData.setValue(1);
// call for the projection manager
mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
// start projection
Button startButton = (Button)findViewById(R.id.startButton);
startButton.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v)
startProjection();
);
// stop projection
Button stopButton = (Button)findViewById(R.id.stopButton);
stopButton.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v)
stopProjection();
);
// start capture handling thread
new Thread()
@Override
public void run()
Looper.prepare();
mHandler = new Handler();
Looper.loop();
.start();
startService=(Button)findViewById(R.id.startService);
stopService=(Button)findViewById(R.id.stopService);
startService.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v)
startService(new Intent(getApplication(), ChatHeadService.class));
);
stopService.setOnClickListener(new OnClickListener()
@Override
public void onClick(View v)
stopService(new Intent(getApplication(), ChatHeadService.class));
);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
if (requestCode == REQUEST_CODE)
sMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
if (sMediaProjection != null)
File externalFilesDir = getExternalFilesDir(null);
if (externalFilesDir != null)
STORE_DIRECTORY = externalFilesDir.getAbsolutePath() + "/screenshots/";
File storeDirectory = new File(STORE_DIRECTORY);
if (!storeDirectory.exists())
boolean success = storeDirectory.mkdirs();
if (!success)
Log.e(TAG, "failed to create file storage directory.");
return;
else
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
return;
// display metrics
DisplayMetrics metrics = getResources().getDisplayMetrics();
mDensity = metrics.densityDpi;
mDisplay = getWindowManager().getDefaultDisplay();
// create virtual display depending on device width / height
createVirtualDisplay();
// register orientation change callback
mOrientationChangeCallback = new OrientationChangeCallback(this);
if (mOrientationChangeCallback.canDetectOrientation())
mOrientationChangeCallback.enable();
// register media projection stop callback
sMediaProjection.registerCallback(new MediaProjectionStopCallback(), mHandler);
/****************************************** UI Widget Callbacks *******************************/
private void startProjection()
startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
private void stopProjection()
mHandler.post(new Runnable()
@Override
public void run()
if (sMediaProjection != null)
sMediaProjection.stop();
);
/****************************************** Factoring Virtual Display creation ****************/
private void createVirtualDisplay()
// get width and height
Point size = new Point();
mDisplay.getSize(size);
mWidth = size.x;
mHeight = size.y;
// start capture reader
mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat.RGBA_8888, 2);
mVirtualDisplay = sMediaProjection.createVirtualDisplay(SCREENCAP_NAME, mWidth, mHeight, mDensity, VIRTUAL_DISPLAY_FLAGS, mImageReader.getSurface(), null, mHandler);
mImageReader.setOnImageAvailableListener(new ImageAvailableListener(), mHandler);
服务
package com.example.chatheads;
import android.app.Service;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.view.View;
import android.widget.Toast;
public class ChatHeadService extends Service
private WindowManager windowManager;
private ImageView chatHead;
WindowManager.LayoutParams params;
Globals sharedData = Globals.getInstance();
@Override
public void onCreate()
super.onCreate();
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(displayMetrics);
int h = displayMetrics.heightPixels, w = displayMetrics.widthPixels;
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // see other conf types
final Bitmap bmp = Bitmap.createBitmap(w, h, conf); // this creates a MUTABLE bitmap
Canvas canvas = new Canvas(bmp);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStrokeWidth(5);
paint.setAlpha(60);
Rect rect=new Rect(0, 0, bmp.getWidth(), bmp.getHeight());
canvas.drawRect(rect,paint);
paint.setColor(Color.GREEN);
canvas.drawLine(0, 0, 0, h, paint);
canvas.drawLine(0, 0, w, 0, paint);
canvas.drawLine(w, 0, h, w, paint);
canvas.drawLine(0, 0, 0, w, paint);
chatHead = new ImageView(this);
chatHead.setImageResource(R.drawable.face1);
params= new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = 0;
params.y = 1000;
//this code is for dragging the chat head
chatHead.setOnTouchListener(new View.OnTouchListener()
private int initialX;
private int initialY;
private float initialTouchX;
private float initialTouchY;
boolean isExpanded = false;
@Override
public boolean onTouch(View v, MotionEvent event)
switch (event.getAction())
case MotionEvent.ACTION_DOWN:
initialX = params.x;
initialY = params.y;
initialTouchX = event.getRawX();
initialTouchY = event.getRawY();
int n = sharedData.getValue();
//Toast toast1 = Toast.makeText(getBaseContext(),String.valueOf(n), Toast.LENGTH_SHORT);
//toast1.show();
if (isExpanded == true)
chatHead.setImageResource(R.drawable.face1);
isExpanded=false;
params.x = 0;
params.y = 1000;
windowManager.updateViewLayout(chatHead, params);
else
//chatHead.setImageResource(R.drawable.clear);
Bitmap bmp2 = sharedData.getScreenshot();
chatHead.setImageBitmap(bmp2);
isExpanded = true;
params.x = 0;
params.y = 0;
windowManager.updateViewLayout(chatHead, params);
return true;
case MotionEvent.ACTION_UP:
//chatHead.setImageResource(R.drawable.test);
return true;
/*case MotionEvent.ACTION_MOVE:
params.x = initialX
+ (int) (event.getRawX() - initialTouchX);
params.y = initialY
+ (int) (event.getRawY() - initialTouchY);
windowManager.updateViewLayout(chatHead, params);
return true;*/
return false;
);
windowManager.addView(chatHead, params);
@Override
public void onDestroy()
super.onDestroy();
if (chatHead != null)
windowManager.removeView(chatHead);
@Override
public IBinder onBind(Intent intent)
// TODO Auto-generated method stub
return null;
全局
package com.example.chatheads;
import android.graphics.Bitmap;
public class Globals
private static Globals instance = new Globals();
// Getter-Setters
public static Globals getInstance()
return instance;
public static void setInstance(Globals instance)
Globals.instance = instance;
private Globals()
private int testi;
private Bitmap bmpscreenshot;
public Bitmap getScreenshot()
return bmpscreenshot;
public void setScreenshot(Bitmap bmp)
this.bmpscreenshot = bmp;
public int getValue()
return testi;
public void setValue(int testi)
this.testi = testi;
【问题讨论】:
最干净和安全的解决方案是将您的图像存储到应用数据或缓存目录中的临时文件中,然后读取它。 我同意@Pawel。您可以传递给活动的字节数也有限制。 【参考方案1】:如果你想将一个 Bitmap 从 Activity 传递给 service 方法,将 bitmap 转换为 ByteArray
Intent i = new Intent(this, YourService.class);
Bitmap b; // your bitmap
ByteArrayOutputStream bs = new ByteArrayOutputStream();
b.compress(Bitmap.CompressFormat.PNG, 50, bs);
i.putExtra("byteArray", bs.toByteArray());
startService(i);
在您的服务中覆盖 onStartCommand
public int onStartCommand (Intent intent, int flags, int startId)
Bitmap b = BitmapFactory.decodeByteArray(
getIntent().getByteArrayExtra("byteArray"),0,getIntent()
.getByteArrayExtra("byteArray").length);
return null;
【讨论】:
【参考方案2】:您似乎调用 bitmap.recycle() 太快了。在服务实际处理完数据后调用它。
【讨论】:
我做了一个快速测试并注释掉了回收线,效果很好。我将创建一个全局布尔值,让活动知道何时可以回收。感谢您的帮助。以上是关于如何将位图从 Activity 传递给服务的主要内容,如果未能解决你的问题,请参考以下文章
Android:将参数从Activity传递给Service
将 View 从 setContentView 传递给非 Activity 类
如何将 AzureDatafactory 中的 DataPath PipelineParameter 传递给 AzureMachineLearningExecutePipeline Activity?