如何将位图从 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

使用 jni 将位图从 android 相机传递到 C++

将 View 从 setContentView 传递给非 Activity 类

如何将 AzureDatafactory 中的 DataPath PipelineParameter 传递给 AzureMachineLearningExecutePipeline Activity?

Android中Activity之间的数据传递ngti