首页 > 解决方案 > Android:激活相机后出现 nullponterException

问题描述

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

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

激活相机后:

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

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

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

我得到 NullPointerException:尝试调用虚拟方法说我的画布为空。将照片从画廊加载到画布上效果很好。可能原因是激活相机后我的活动被破坏了,所以我制作了一个包装器以使我的画布可序列化:

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

并试图保存我的画布:

   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 个类:MainActivity 和 DrawingView。如果相同的人知道如何克服这个问题,我将不胜感激。完整代码如下。

MainActivity 类:

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());
    }

}

绘图视图类:

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;
    }

}

标签: androiddrawingphotoandroid-camera-intent

解决方案


你是绝对正确的; NPE 的根本原因是您的活动已关闭。最有可能的是,Camera 应用程序会强制横向放置,并且您的不支持配置更改的 Activity 会重新启动(可能不止一次)以解决这种“旋转”问题。另一个常见的情况是系统决定关闭您的应用程序,因为相机应用程序需要更多 RAM。

无论如何,您无法在onSaveInstanceState()包中保存/恢复 Canvas 或 View 。活动恢复后,您必须连接到新的DrawingView及其drawingCanvas

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

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 进行序列化。


推荐阅读