Android: 激活相机后出现 nullpointerException

Android: nullponterException after activating camera

我正在为 android 开发简单的绘图应用程序。我的目标是:

  1. 从我的应用程序打开相机
  2. 拍照
  3. 把这张照片画成canvas
  4. 在这张照片上画画

激活相机后:

Intent takePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePhoto.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePhoto, CAMERA_INTENT_CALLED);
                }

当我尝试将照片绘制到 canvas 中时:

else if (requestCode == CAMERA_INTENT_CALLED) {
            if (resultCode == RESULT_OK) {
                Bundle extras = returnedIntent.getExtras();
                Bitmap imageBitmap = (Bitmap) extras.get("data");
                drawView.drawImage(imageBitmap);
            }
        }

我收到 NullPointerException:尝试调用虚方法说我的 canvas 为空。 将图库中的照片加载到 canvas 工作正常。可能的原因是我的 activity 在激活相机后被破坏所以我做了一个包装器使我的 canvas 可序列化:

public class SerializableCanvas extends Canvas implements Serializable {
    public SerializableCanvas(Bitmap canvasBitmap) {
        super(canvasBitmap);
    }
}

并试图保存我的 canvas:

   protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("drawCanvas", drawView.getDrawCanvas());
    }

并恢复它:

protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState != null) {
        if (savedInstanceState.containsKey("drawCanvas")) {
            drawView.setDrawCanvas((SerializableCanvas)savedInstanceState.getSerializable("drawCanvas"));
        }
    }
}

但现在我收到一条错误消息,指出此片段中的宽度和高度必须 >0:

public void drawImage(Bitmap image) {
    drawCanvas.drawBitmap(resize(image, drawCanvas.getWidth(), drawCanvas.getHeight()), 0, 0, canvasPaint);
    invalidate();
}

我也试过保存整个 DrawingView 对象,但它太大并且里面有位图。 我有 2 个 classes:MainActivity 和 DrawingView。如果同一个人知道如何克服这个问题,我将不胜感激。完整代码如下。

主要活动class:

public class MainActivity extends Activity implements View.OnClickListener {

    private DrawingView drawView;
    private ImageButton currPaint;
    private ImageButton drawBtn;
    private ImageButton eraseBtn;
    private ImageButton newBtn;
    private ImageButton saveBtn;
    private ImageButton openBtn;
    private ImageButton cameraBtn;
    private float smallBrush;
    private float mediumBrush;
    private float largeBrush;
    private Drawable image;

    private final int GALLERY_INTENT_CALLED = 1;
    private final int CAMERA_INTENT_CALLED = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        drawView = (DrawingView)findViewById(R.id.drawing);
        LinearLayout paintLayout = (LinearLayout)findViewById(R.id.paint_colors);
        currPaint = (ImageButton)paintLayout.getChildAt(0);
        currPaint.setImageDrawable(getResources().getDrawable(R.drawable.paint_pressed));
        smallBrush = getResources().getInteger(R.integer.small_size);
        mediumBrush = getResources().getInteger(R.integer.medium_size);
        largeBrush = getResources().getInteger(R.integer.large_size);

        drawBtn = (ImageButton)findViewById(R.id.draw_btn);
        drawBtn.setOnClickListener(this);
        eraseBtn = (ImageButton)findViewById(R.id.erase_btn);
        eraseBtn.setOnClickListener(this);
        newBtn = (ImageButton)findViewById(R.id.new_btn);
        newBtn.setOnClickListener(this);
        saveBtn = (ImageButton)findViewById(R.id.save_btn);
        saveBtn.setOnClickListener(this);
        openBtn = (ImageButton)findViewById(R.id.open_btn);
        openBtn.setOnClickListener(this);
        cameraBtn = (ImageButton)findViewById(R.id.camera_btn);
        cameraBtn.setOnClickListener(this);

        drawView.setBrushSize(mediumBrush);
    }

    public void paintClicked(View view){
        drawView.setErase(false);
        drawView.setBrushSize(drawView.getLastBrushSize());
        if(view!=currPaint){
            ImageButton imgView = (ImageButton)view;
            String color = view.getTag().toString();
            drawView.setColor(color);
            imgView.setImageDrawable(getResources().getDrawable(R.drawable.paint_pressed));
            currPaint.setImageDrawable(getResources().getDrawable(R.drawable.paint));
            currPaint=(ImageButton)view;
        }
    }

    @Override
    public void onClick(View view) {
        if(view.getId()==R.id.draw_btn){
            proceedDrawButtonAction();
        }else if(view.getId()==R.id.erase_btn){
            proceedEraseButtonAction();
        }else if(view.getId()==R.id.new_btn){
            proceedNewButtonAction();
        }else if(view.getId()==R.id.save_btn){
            proceedSaveButtonAction();
        }else if(view.getId()==R.id.open_btn){
            proceedOpenButtonAction();
        }else if(view.getId()==R.id.camera_btn){
            proceedCameraButtonAction();
        }
    }

    public void proceedDrawButtonAction(){
        final Dialog brushDialog = new Dialog(this);
        brushDialog.setTitle("Brush size:");
        brushDialog.setContentView(R.layout.brush_chooser);
        brushDialog.show();

        ImageButton smallBtn = (ImageButton)brushDialog.findViewById(R.id.small_brush);
        smallBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                drawView.setErase(false);
                drawView.setBrushSize(smallBrush);
                drawView.setLastBrushSize(smallBrush);
                brushDialog.dismiss();
            }
        });
        ImageButton mediumBtn = (ImageButton)brushDialog.findViewById(R.id.medium_brush);
        mediumBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                drawView.setErase(false);
                drawView.setBrushSize(mediumBrush);
                drawView.setLastBrushSize(mediumBrush);
                brushDialog.dismiss();
            }
        });
        ImageButton largeBtn = (ImageButton)brushDialog.findViewById(R.id.large_brush);
        largeBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                drawView.setErase(false);
                drawView.setBrushSize(largeBrush);
                drawView.setLastBrushSize(largeBrush);
                brushDialog.dismiss();
            }
        });
    }

    public void proceedEraseButtonAction(){
        final Dialog brushDialog = new Dialog(this);
        brushDialog.setTitle("Eraser size:");
        brushDialog.setContentView(R.layout.brush_chooser);
        brushDialog.show();

        ImageButton smallBtn = (ImageButton)brushDialog.findViewById(R.id.small_brush);
        smallBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                drawView.setErase(true);
                drawView.setBrushSize(smallBrush);
                brushDialog.dismiss();
            }
        });
        ImageButton mediumBtn = (ImageButton)brushDialog.findViewById(R.id.medium_brush);
        mediumBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                drawView.setErase(true);
                drawView.setBrushSize(mediumBrush);
                brushDialog.dismiss();
            }
        });
        ImageButton largeBtn = (ImageButton)brushDialog.findViewById(R.id.large_brush);
        largeBtn.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v) {
                drawView.setErase(true);
                drawView.setBrushSize(largeBrush);
                brushDialog.dismiss();
            }
        });
    }

    public void proceedNewButtonAction(){
        AlertDialog.Builder yesNoDialog = new AlertDialog.Builder(this);
        yesNoDialog.setTitle("New drawing");
        yesNoDialog.setMessage("Start new drawing (you will lose the current drawing)?");
        yesNoDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                drawView.startNew();
                dialog.dismiss();
            }
        });
        yesNoDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                dialog.cancel();
            }
        });
        yesNoDialog.show();
    }

    public void proceedSaveButtonAction(){
        AlertDialog.Builder saveDialog = new AlertDialog.Builder(this);
        saveDialog.setTitle("Save drawing");
        saveDialog.setMessage("Save drawing to device Gallery?");
        saveDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                drawView.setDrawingCacheEnabled(true);
                String imgSaved = MediaStore.Images.Media.insertImage(
                        getContentResolver(), drawView.getDrawingCache(),
                        UUID.randomUUID().toString()+".png", "drawing");
                if(imgSaved!=null){
                    Toast savedToast = Toast.makeText(getApplicationContext(),
                            "Drawing saved to Gallery!", Toast.LENGTH_SHORT);
                    savedToast.show();
                }
                else{
                    Toast unsavedToast = Toast.makeText(getApplicationContext(),
                            "Image could not be saved.", Toast.LENGTH_SHORT);
                    unsavedToast.show();
                }
                drawView.destroyDrawingCache();
            }
        });
        saveDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                dialog.cancel();
            }
        });
        saveDialog.show();
    }

    public void proceedOpenButtonAction(){
        AlertDialog.Builder yesNoDialog = new AlertDialog.Builder(this);
        yesNoDialog.setTitle("Load image");
        yesNoDialog.setMessage("Load image from gallery (you will lose the current drawing)?");
        yesNoDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                Intent pickPhoto = new Intent(
                        Intent.ACTION_PICK,
                        android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                startActivityForResult(pickPhoto, GALLERY_INTENT_CALLED);
            }
        });
        yesNoDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                dialog.cancel();
            }
        });
        yesNoDialog.show();
    }

    public void proceedCameraButtonAction(){
        AlertDialog.Builder yesNoDialog = new AlertDialog.Builder(this);
        yesNoDialog.setTitle("Take a photo");
        yesNoDialog.setMessage("Take a photo and start drawing (you will lose the current drawing)?");
        yesNoDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                Intent takePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if (takePhoto.resolveActivity(getPackageManager()) != null) {
                    startActivityForResult(takePhoto, CAMERA_INTENT_CALLED);
                }
            }
        });
        yesNoDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
            public void onClick(DialogInterface dialog, int which){
                dialog.cancel();
            }
        });
        yesNoDialog.show();
    }

    protected void onActivityResult(int requestCode, int resultCode, Intent returnedIntent) {
        super.onActivityResult(requestCode, resultCode, returnedIntent);
        if (requestCode == GALLERY_INTENT_CALLED) {
            if (resultCode == RESULT_OK) {
                try {
                    Uri selectedImage = returnedIntent.getData();
                    InputStream inputStream = getContentResolver().openInputStream(selectedImage);
                    image = Drawable.createFromStream(inputStream, selectedImage.toString());
                } catch (FileNotFoundException e) {}
                drawView.drawImage(drawableToBitmap(image));
            }
        }else if (requestCode == CAMERA_INTENT_CALLED) {
            if (resultCode == RESULT_OK) {
                Bundle extras = returnedIntent.getExtras();
                Bitmap imageBitmap = (Bitmap) extras.get("data");
                drawView.drawImage(imageBitmap);
            }
        }
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (savedInstanceState != null) {
            if (savedInstanceState.containsKey("drawCanvas")) {
                drawView.setDrawCanvas((SerializableCanvas)savedInstanceState.getSerializable("drawCanvas"));
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable("drawCanvas", drawView.getDrawCanvas());
    }

}

绘图视图class:

public class DrawingView extends View implements Serializable{

    private Path drawPath;
    private Paint drawPaint;
    private Paint canvasPaint;
    private int paintColor = 0xFFFF0000;
    private SerializableCanvas drawCanvas;
    private Bitmap canvasBitmap;
    private float brushSize;
    private float lastBrushSize;
    private boolean erase = false;

    public DrawingView(Context context, AttributeSet attrs){
        super(context, attrs);
        setupDrawing();
    }

    private void setupDrawing(){
        brushSize = getResources().getInteger(R.integer.medium_size);
        lastBrushSize = brushSize;
        drawPath = new Path();
        drawPaint = new Paint();
        drawPaint.setColor(paintColor);
        drawPaint.setAntiAlias(true);
        drawPaint.setStrokeWidth(brushSize);
        drawPaint.setStyle(Paint.Style.STROKE);
        drawPaint.setStrokeJoin(Paint.Join.ROUND);
        drawPaint.setStrokeCap(Paint.Cap.ROUND);
        canvasPaint = new Paint(Paint.DITHER_FLAG);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float touchX = event.getX();
        float touchY = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                drawPath.moveTo(touchX, touchY);
                break;
            case MotionEvent.ACTION_MOVE:
                drawPath.lineTo(touchX, touchY);
                if(erase){
                    drawCanvas.drawPath(drawPath, drawPaint);
                }
                break;
            case MotionEvent.ACTION_UP:
                drawCanvas.drawPath(drawPath, drawPaint);
                drawPath.reset();
                break;
            default:
                return false;
        }

        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
        if(erase){ return;}
        canvas.drawPath(drawPath, drawPaint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        drawCanvas = new SerializableCanvas(canvasBitmap);
    }

    public void setColor(String newColor){
        invalidate();
        paintColor = Color.parseColor(newColor);
        drawPaint.setColor(paintColor);
    }

    public void setBrushSize(float newSize){
        float pixelAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                newSize, getResources().getDisplayMetrics());
        brushSize = pixelAmount;
        drawPaint.setStrokeWidth(brushSize);
    }

    public void setLastBrushSize(float lastSize){
        lastBrushSize=lastSize;
    }
    public float getLastBrushSize(){
        return lastBrushSize;
    }

    public void setErase(boolean erase){
        this.erase = erase;
        if(erase) {
            drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        } else {
            drawPaint.setXfermode(null);
        }
    }

    public void startNew(){
        drawCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
        invalidate();
    }

    public void drawImage(Bitmap image) {
        drawCanvas.drawBitmap(resize(image, drawCanvas.getWidth(), drawCanvas.getHeight()), 0, 0, canvasPaint);
        invalidate();
    }

    public static Bitmap drawableToBitmap (Drawable drawable) {
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable)drawable).getBitmap();
        }
        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

    private static Bitmap resize(Bitmap image, int maxWidth, int maxHeight) {
        image = rotateImage(image, 90);
        image = Bitmap.createScaledBitmap(image, maxWidth, maxHeight, true);
        return image;
    }

    public static Bitmap rotateImage(Bitmap source, float angle) {
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(),
                matrix, true);
    }

    public SerializableCanvas getDrawCanvas(){
        return drawCanvas;
    }

    public void setDrawCanvas(SerializableCanvas drawCanvas){
        this.drawCanvas = drawCanvas;
    }

    public Bitmap getCanvasBitmap(){
        return canvasBitmap;
    }

    public void setCanvasBitmap(Bitmap canvasBitmap){
        this.canvasBitmap = canvasBitmap;
    }

    public Paint getCanvasPaint(){
        return canvasPaint;
    }

    public void setCanvasPaint(Paint canvasPaint){
        this.canvasPaint = canvasPaint;
    }

}

你完全正确; NPE 的根本原因是您的 Activity 已关闭。最有可能的是,相机应用程序强制横向,并且不支持配置更改的 Activity 重新启动(可能不止一次)以解决此问题 'rotation'。另一种常见情况是系统决定关闭您的应用程序,因为相机应用程序需要更多内存。

无论如何,您不能 save/restore Canvas 或 onSaveInstanceState() 中的视图。在 activity 恢复后,您必须连接到新的 DrawingView 及其 drawingCanvas

要注意的是 onActivityResult() 可能会在 Activity 恢复之前调用,这意味着您可能无法绘制位图你收到它。在 DrawingView.drawBitmap() 中,如果 drawingCanvas == null,将位图保存到局部变量,并在 canvas 可用时立即绘制此位图。

PS 请注意,对于 CAMERA_INTENT_CALLED,您的应用将绘制相机拍摄照片的低分辨率缩略图位图。如果您想要完整质量的图像,您必须从相机应用程序创建的文件中检索它。

下面是DrawingView实现延迟绘制的一段代码:

private Bitmap savedBitmap;

public void drawImage(Bitmap image) {
    if (drawCanvas == null) {
        savedBitmap = image;
    }
    else {
        drawCanvas.drawBitmap(resize(image, drawCanvas.getWidth(), drawCanvas.getHeight()), 0, 0, canvasPaint);
        invalidate();
    }
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    drawCanvas = new SerializableCanvas(canvasBitmap);
    if (savedBitmap != null) {
        drawBitmap(savedBitmap);
        savedBitmap = null;
    }
}

请注意,您不需要为 Canvas 或 DrawingView 进行序列化。