Android 呼叫系統相機,拍照,並上傳圖片所注意的一些問題
阿新 • • 發佈:2019-01-07
其實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);
}
}