1. 程式人生 > >Android 呼叫系統相機,拍照,並上傳圖片所注意的一些問題

Android 呼叫系統相機,拍照,並上傳圖片所注意的一些問題

其實android拍照這個地方還是有很多注意事項的,我在上個專案中就遇到一些坑,因此我想把它記錄下來,希望能幫助一些跟我遇到的同樣的問題的人

如果你在專案中遇到以下問題:

  • 通過系統路徑,拍出來的圖片不清楚
  • 在某些情況下,onActivityResult(int requestCode, int resultCode, Intent data) 回撥方法中,data為null
  • 有些時候,在某些手機拍照之後會閃退,報空指標錯誤,等等
  • 有些時候在拍完照片之後,點選確認按鈕之後,無法返回到前一個Activity

可以繼續往下看,否則的話,就跳過吧,不能耽誤你的時間

先說第一種情況,如果直接呼叫系統相機,一般系統會把圖片放在預設的路徑,然後通過回撥函式onActivityResult(int requestCode, int resultCode, Intent data)中的data拿值,Bitmap bitmap =(Bitmap) data.getExtras().get("data")

,這樣就拿到bitmap了。值得注意的是,返回的這個圖片是縮圖並不是原圖。如果是需要拍身份證照片,錄入資訊的,不能用考慮這種。當然這種呼叫相機也會有人用到,先貼一下程式碼:

 public class MainActivity extends AppCompatActivity {

     public static final int TAKE_PHOTO = 111;

     private Button mButton;
     private ImageView mImageView;

     @Override
     protected void onCreate
(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mButton = (Button) findViewById(R.id.takePhoto); mImageView = (ImageView) findViewById(R.id.img); mButton.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View view) { takePhoto(); } }); } public void takePhoto() { Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); startActivityForResult(captureIntent, TAKE_PHOTO); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == TAKE_PHOTO) { Bitmap bitmap = (Bitmap) data.getExtras().get("data"); //拿到bitmap,做喜歡做的事情把 ---> 顯示 or上傳? mImageView.setImageBitmap(bitmap); //upload } } } }

第二種情況,onActivityResult(int requestCode, int resultCode, Intent data) 回撥方法中,data為null。這種情況,如果程式碼沒錯誤,那麼產生這種原因是什麼呢? 其實當你自定義拍照路徑之後,回撥函式中,返回的data就為null了,那麼你拿圖片資源就只能通過file轉化成bitmap的了。下面我會把Bitmap工具類提供出來

public class MainActivity extends AppCompatActivity {

    public static final int TAKE_PHOTO = 111;

    private Button mButton;
    private ImageView mImageView;


    //儲存 照片的目錄
    private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mms";
    private File photo_file = new File(path);
    private String photoPath ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.takePhoto);
        mImageView = (ImageView) findViewById(R.id.img);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                takePhoto();
            }
        });
    }

    public void takePhoto() {
        Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        if (!photo_file.exists()) {
            photo_file.mkdirs();
        }
        photo_file = new File(path, "/temp.jpg");
        photoPath = path + "/temp.jpg";
        if (photo_file != null) {
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo_file));
            startActivityForResult(captureIntent, TAKE_PHOTO);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {

            if (requestCode == TAKE_PHOTO) {
                //這種情況下 data是為null的,因為自定義了路徑

            }
        }


    }

}

這種情況下,因為data為null,所以正確做法應該是從 photoPath中來獲取bitmap。在後面會寫出全部程式碼的,這裡只是說明為啥data會為null

在某些手機拍照之後會閃退,報空指標錯誤。這個錯誤主要是因為 當點選拍照時,某些情況下,系統會認為這個是耗費資源的操作,所以會未經你允許的情況下把之前的Activity給銷燬了,所以當再次回到上個介面中,生命週期會重新來一遍,所以當執行到onActivityResult(int requestCode, int resultCode, Intent data)中,photoPath還是為空,所以報了空指標異常。解決辦法如下:

   @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("photoPath", photoPath);
        Log.d(TAG, "onSaveInstanceState");
    }
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (TextUtils.isEmpty(photoPath)) {
            photoPath = savedInstanceState.getString("photoPath");
        }
        Log.d(TAG, "onRestoreInstanceState");
    }

通過onSaveInstanceState(Bundle outState)把資料儲存起來,然後介面確實被系統銷燬了,那麼就會執行onRestoreInstanceState(Bundle savedInstanceState)方法,在這個方法中就可以取到資料了,從而恢復介面。

有些時候在拍完照片之後,點選確認按鈕之後,無法返回到前一個Activity,這個問題主要是檔案建的不對,匯入圖片資源無法寫進去,點選確定後也無法回退到上個介面。這裡就按照我這種建立的方式把。

完整拍照上傳圖片的程式碼如下:
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    public static final int TAKE_PHOTO = 111;

    private Button mButton;
    private ImageView mImageView;

    //儲存 照片的目錄
    private String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mms";
    private File photo_file = new File(path);
    private String photoPath;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.takePhoto);
        mImageView = (ImageView) findViewById(R.id.img);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                takePhoto();
            }
        });
    }

    public void takePhoto() {
        Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        if (!photo_file.exists()) {
            photo_file.mkdirs();
        }
        photo_file = new File(path, "/temp.jpg");
        photoPath = path + "/temp.jpg";
        if (photo_file != null) {
            captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photo_file));
            startActivityForResult(captureIntent, TAKE_PHOTO);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {

            if (requestCode == TAKE_PHOTO) {
                //這中情況下 data是為null的,因為自定義了路徑 所以通過這個路徑來獲取
                Bitmap smallBitmap = BitmapUtil.getSmallBitmap(photoPath);

                // ok 拿到圖片的base64 上傳
                String base64 = BitmapToBase64Util.bitmapToBase64(smallBitmap);
            }
        }

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("photoPath", photoPath);
        Log.d(TAG, "onSaveInstanceState");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (TextUtils.isEmpty(photoPath)) {
            photoPath = savedInstanceState.getString("photoPath");
        }
        Log.d(TAG, "onRestoreInstanceState");
    }

}

BitmapUtil

@SuppressLint("NewApi")
public class BitmapUtil {
    // 計算圖片的縮放值
    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int heightRatio = Math.round((float) height
                    / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        return inSampleSize;
    }

    // 根據路徑獲得圖片並壓縮,返回bitmap用於顯示
    public static Bitmap getSmallBitmap(String filePath) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, 200, 380);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(filePath, options);
    }

    // 根據路徑獲得圖片並壓縮,返回bitmap用於顯示
    public static Bitmap getSmallBitmap(InputStream is) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is, null, options);
        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, 480, 800);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeStream(is, null, options);
    }

    public static Bitmap comp(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        if (baos.toByteArray().length / 1024 > 1024) {// 判斷如果圖片大於1M,進行壓縮避免在生成圖片(BitmapFactory.decodeStream)時溢位
            baos.reset();// 重置baos即清空baos
            image.compress(Bitmap.CompressFormat.JPEG, 40, baos);// 這裡壓縮50%,把壓縮後的資料存放到baos中
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
        BitmapFactory.Options newOpts = new BitmapFactory.Options();
        // 開始讀入圖片,此時把options.inJustDecodeBounds 設回true了
        newOpts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        newOpts.inJustDecodeBounds = false;
        int w = newOpts.outWidth;
        int h = newOpts.outHeight;
        // 現在主流手機比較多是800*480解析度,所以高和寬我們設定為
        float hh = 800f;// 這裡設定高度為800f
        float ww = 480f;// 這裡設定寬度為480f
        // 縮放比。由於是固定比例縮放,只用高或者寬其中一個數據進行計算即可
        int be = 1;// be=1表示不縮放
        if (w > h && w > ww) {// 如果寬度大的話根據寬度固定大小縮放
            be = (int) (newOpts.outWidth / ww);
        } else if (w < h && h > hh) {// 如果高度高的話根據寬度固定大小縮放
            be = (int) (newOpts.outHeight / hh);
        }
        if (be <= 0)
            be = 1;
        newOpts.inSampleSize = be;// 設定縮放比例
        // 重新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了
        isBm = new ByteArrayInputStream(baos.toByteArray());
        bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);
        return compressImage(bitmap);// 壓縮好比例大小後再進行質量壓縮
    }

    // 把bitmap轉換成String
    public static String bitmapToString(String filePath) {
        Bitmap bm = getSmallBitmap(filePath);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bm.compress(Bitmap.CompressFormat.JPEG, 40, baos);
        byte[] b = baos.toByteArray();
        return Base64.encodeToString(b, Base64.DEFAULT);
    }

    /**
     *  質量壓縮
     * @param image
     * @return
     */
    public static Bitmap compressImage(Bitmap image) {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 50, baos);// 質量壓縮方法,這裡100表示不壓縮,把壓縮後的資料存放到baos中
        int options = 50;
        while (baos.toByteArray().length / 1024 > 100) { // 迴圈判斷如果壓縮後圖片是否大於100kb,大於繼續壓縮
            baos.reset();// 重置baos即清空baos
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 這裡壓縮options%,把壓縮後的資料存放到baos中
            options -= 10;// 每次都減少10
        }
        ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把壓縮後的資料baos存放到ByteArrayInputStream中
        Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream資料生成圖片
        return bitmap;
    }

    /**
     * 根據Uri獲取圖片絕對路徑,解決Android4.4以上版本Uri轉換
     * @param context
     * @param imageUri
     * @author yaoxing
     * @date 2014-10-12
     */
    public static String getImageAbsolutePath(Activity context, Uri imageUri) {
        if (context == null || imageUri == null)
            return null;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, imageUri)) {
            if (isExternalStorageDocument(imageUri)) {
                String docId = DocumentsContract.getDocumentId(imageUri);
                String[] split = docId.split(":");
                String type = split[0];
                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
            } else if (isDownloadsDocument(imageUri)) {
                String id = DocumentsContract.getDocumentId(imageUri);
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                return getDataColumn(context, contentUri, null, null);
            } else if (isMediaDocument(imageUri)) {
                String docId = DocumentsContract.getDocumentId(imageUri);
                String[] split = docId.split(":");
                String type = split[0];
                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
                String selection = MediaStore.Images.Media._ID + "=?";
                String[] selectionArgs = new String[] { split[1] };
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        } // MediaStore (and general)
        else if ("content".equalsIgnoreCase(imageUri.getScheme())) {
            // Return the remote address
            if (isGooglePhotosUri(imageUri))
                return imageUri.getLastPathSegment();
            return getDataColumn(context, imageUri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(imageUri.getScheme())) {
            return imageUri.getPath();
        }
        return null;
    }

    public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        String column = MediaStore.Images.Media.DATA;
        String[] projection = { column };
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    /***
     * 圖片的縮放方法
     *
     * @param bgimage
     *            :源圖片資源
     * @param newWidth
     *            :縮放後寬度
     * @param newHeight
     *            :縮放後高度
     * @return
     */
    public static Bitmap zoomImage(Bitmap bgimage, double newWidth,
                                   double newHeight) {
        // 獲取這個圖片的寬和高
        float width = bgimage.getWidth();
        float height = bgimage.getHeight();
        // 建立操作圖片用的matrix物件
        Matrix matrix = new Matrix();
        // 計算寬高縮放率
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // 縮放圖片動作
        matrix.postScale(scaleWidth, scaleHeight);
        Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
                (int) height, matrix, true);
        return bitmap;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    public static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }
}

BitmapUtil工具類主要包含了獲取bitmap、質量壓縮等一些方法。當然拍照之後一般會獲取圖片的base64,然後上傳給服務端,或者前端js。 BitmapToBase64Util程式碼如下;

BitmapToBase64Util

public class BitmapToBase64Util {
    /**
     * bitmapתΪbase64
     *
     * @param bitmap
     * @return
     */
    public static String bitmapToBase64(Bitmap bitmap) {

        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 50, baos);  //這裡50表示壓縮50%
                baos.flush();
                baos.close();
                byte[] bitmapBytes = baos.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    /**
     * base64תΪbitmap
     *
     * @param base64Data
     * @return
     */
    public static Bitmap base64ToBitmap(String base64Data) {
        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
}
關於呼叫系統相機、拍照上傳就到處為止,希望能幫助到一些需要幫助的人