Android Camera - litonghui/TechBlog GitHub Wiki

   在社交软件中,文字传送已近不能满足用户需求,以图片、语音、视频为主的流媒体传送开始主导社交,以Android 图片上传为例,实现从相册选照片和拍照传照片的dome

===================

申请权限,在AndroidManifest 申明权限,包括获取照相机权限,文件(图片)存储读写权限。
    <!-- 照相机权限 -->
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 用于读取手机当前的状态-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <!-- 写入扩展存储,向扩展卡写入数据,用于写入离线定位数据-->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 往SDCard写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
AlertDialog 弹窗选择:照相和相册
    final AlertDialog dlg = new AlertDialog.Builder(this).create();
    dlg.show();

    Window window = dlg.getWindow();
    // 设置窗口的内容页面,shrew_exit_dialog.xml文件中定义view内容
    window.setContentView(R.layout.alertdialog);

    TextView tv_paizhao = (TextView) window.findViewById(R.id.tv_content1);
    tv_paizhao.setText("拍照");

    TextView tv_xiangce = (TextView) window.findViewById(R.id.tv_content2);
    tv_xiangce.setText("相册");
拍照功能
1. 判断照相机:
/**
 * 判断系统是否可以启动相机
 * @return true 表示有照相机,false 表示没有照相机
 */
public static boolean hasCamera(Activity activity) {
    if (null != activity && !activity.isFinishing()) {
        PackageManager packageManager = activity.getPackageManager();
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        List<ResolveInfo> infos = packageManager.queryIntentActivities(
                intent, PackageManager.MATCH_DEFAULT_ONLY);
        return infos != null ? infos.size() > 0 : false;
    } else
        return false;
}
2. 创建存储文件 和 以时间命名文件名
/**
 *  创建照片存储绝对路径
 * @param filePath 文件路径
 * @param fileName 照片名字
 * @return 照片绝对路径
 */
public static File createOrOpen(String filePath,String fileName) {
    File dir = new File(filePath);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    File destFile = new File(dir, fileName);
    return destFile;
}
/**
 * 获取当前时间
 * @return 
 */
@SuppressLint("SimpleDateFormat")
public static  String getNowTime() {
    Date date = new Date(System.currentTimeMillis());
    SimpleDateFormat dateFormat = new SimpleDateFormat("MMddHHmmssSS");
    return dateFormat.format(date);
}
3. 调起照相机
    tv_paizhao.setOnClickListener(new View.OnClickListener() {
        @SuppressLint("SdCardPath")
        public void onClick(View v) {
            if (Utils.hasCamera(mActivity)) {
                mImageName = Utils.getNowTime() + ".png";
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                // 指定调用相机拍照后照片的储存路径
                intent.putExtra(MediaStore.EXTRA_OUTPUT,
                        Uri.fromFile(Utils.createOrOpen(ImageAvatarPath, mImageName)));
                startActivityForResult(intent, PHOTO_REQUEST_TAKEPHOTO);
            } else {
                Toast.makeText(mActivity, "No Find Camera !", Toast.LENGTH_SHORT).show();
            }
            dlg.cancel();
        }
    });
4. 回调处理数据
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK) {
        switch (requestCode) {
            case PHOTO_REQUEST_TAKEPHOTO:                  
                  startPhotoSave(Uri.fromFile(Utils.createOrOpen(ImageAvatarPath, mImageName)));			
                break;
			default:
                break;
        }
    }
    super.onActivityResult(requestCode, resultCode, data);		
}
/**
 * 照片存储并显示
 * @return 
 */
  private void  startPhotoSave(Uri uri){
    ContentResolver resolver = getContentResolver();
    //照片的原始资源地址
    try {
        //使用ContentProvider通过URI获取原始图片
        Bitmap photo = MediaStore.Images.Media.getBitmap(resolver, uri);
        if (photo != null) {
            Bitmap smallBitmap = Utils.zoomBitmap(photo, photo.getWidth() / SCALE, photo.getHeight() / SCALE);
            photo.recycle();  //释放原始图片占用的内存,防止out of memory异常发生
            Utils.save(smallBitmap, Bitmap.CompressFormat.PNG, 1, ImageAvatarPath,mImageName);
            mImageView.setImageBitmap(smallBitmap);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
  在图片存储时候用到两个方法:zoomBitmap(),save(),分析原因,目前流行照相机拍照产生原始照片相对较大,如果不做相应压缩,很容易造成OOM,所以需要对照片压缩。
/**
 * 压缩图片
 * @param bitmap 原始图片
 * @param width 宽度
 * @param height 高度
 * @return 压缩结果
 */
public static Bitmap zoomBitmap(Bitmap bitmap, int width, int height) {
    int w = bitmap.getWidth();
    int h = bitmap.getHeight();
    Matrix matrix = new Matrix();
    float scaleWidth = ((float) width / w);
    float scaleHeight = ((float) height / h);
    matrix.postScale(scaleWidth, scaleHeight);
    Bitmap newbmp = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
    if (null != bitmap && !bitmap.isRecycled()) {
        bitmap.recycle();
    }
    return newbmp;
}

public static String save(
        Bitmap bitmap,
        Bitmap.CompressFormat format,
        int quality, File destFile) {

    try {
        FileOutputStream out = new FileOutputStream(destFile);

        if (bitmap.compress(format, quality, out)) {
            out.flush();
            out.close();
        }
        return destFile.getAbsolutePath();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}
相册选择
1 ,创建存储文件 和 以时间命名文件名 同上
2 ,调起相册选择图片
    tv_xiangce.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            mImageName = Utils.getNowTime() + ".png";
            Intent intent = new Intent(Intent.ACTION_PICK, null);
            intent.setDataAndType(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
            startActivityForResult(intent, PHOTO_REQUEST_GALLERY);

            dlg.cancel();
        }
    });
3,接收图片选择回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK) {
        switch (requestCode) {
            case PHOTO_REQUEST_GALLERY:
                if(data != null) {
                        startPhotoSave(data.getData());// 方法同上
                }
                break; 
			default:
                break;
        }
    }
    super.onActivityResult(requestCode, resultCode, data);		
}
  通过上面方法可以完成简单的图片选择功能,但是细节优化还值得注意,比如,通过照相和相册选择图片之后,可以对图片做裁剪;因为调用照相机相当于启动新的Activity,个别手会出现销毁栈顶Activity,也就意味着照片选择完成之后无法预览显示,需要借助onSaveInstanceState、onRestoreInstanceState 方法做保存。
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if (mImageName != null) {
        outState.putSerializable(EXTRA_RESTORE_PHOTO, mImageName);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    mImageName = (String) savedInstanceState.getSerializable(EXTRA_RESTORE_PHOTO);
}
   如果想测试不用上门两个方法是否可以造成影响,可以在如图一,选择不保留当前活动,测试是否显示正常。

图一