Android 8.0学习 (36)---Android 8.0 WebView 拍照、简易预览、二维码扫描 各种问题解决 Android 8.0 WebView 拍照、简易预览、二维码扫描 各种问题解决

Android 8.0 WebView 拍照、简易预览、二维码扫描 各种问题解决

项目用到了WebView包装HTML5做成app使用,其中有页面用到了二维码和拍照上传功能。本人从未做过android,短时间内完成,只能靠“热心网友”帮忙了,网上也铺天盖地各种demo和文章。

但是对于高版本,特别是android 8.0以上,网上的各种现成的Demo都不好用,各种问题。现在我成功了解决了这些问题,并汇总供初学者参考。


1. 权限问题

这个问题也是困扰最多的一个问题,各种Android版本对于权限的限制和提示不一样,特别是在我的Mate9(Android 8.0)上一度不提示,后台也不报错,但是浏览器就是在拍照后无法访问图片,搞了很久才知道是没有在代码里加动态权限检查。AndroidManifest.xml文件只是注册了需要用哪些权限并不是app获取到的权限,高版本android在安装app时忽略提示权限,所以一般app首次安装打开后,都会提示你需要什么权限。

由此,建议在首页的Activity的onCreate方法里,增加权限检查。

checkSelfPermission(Manifest.permission.CAMERA);

checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);

等等,如果只用到了拍照,那么这2个就够了,可存比可读,不用再赋读取权限。


对于权限检查,有很多问题需要考虑,比如禁止后,怎么办?如果用户勾选“禁止后不再询问”,怎么办?

我这边的处理是如下: 

Activity的回调方法onRequestPermissionsResult 

  1. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  2. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  3. HandleMainActivityResult.onRequestPermissionsResult( this,requestCode,permissions,grantResults);
  4. }
处理权限提示等,基本都在HandleMainActivityResult.onRequestPermissionsResult方法

  1. //权限回调方法(默认知道某个requestCode赋值几个权限,数组大小为几)
  2. public static void onRequestPermissionsResult(final MainActivity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  3. if (requestCode == ActivityResultConst.CODE_FOR_WRITE_PERMISSION) {
  4. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  5. boolean isShow = true;
  6. Log.e(TAG, "grantResults.length:"+grantResults.length);
  7. String tipTitle = "权限不可用";
  8. if(grantResults.length == 2){
  9. if (grantResults[ 0] != PackageManager.PERMISSION_GRANTED && grantResults[ 1] != PackageManager.PERMISSION_GRANTED) {
  10. tipTitle = "相机和存储权限不可用";
  11. } else if(grantResults[ 0] != PackageManager.PERMISSION_GRANTED){
  12. tipTitle = "相机权限不可用";
  13. } else if(grantResults[ 1] != PackageManager.PERMISSION_GRANTED){
  14. tipTitle = "存储权限不可用";
  15. } else {
  16. //权限已经全部赋值成功
  17. isShow = false;
  18. }
  19. // 判断用户是否 点击了不再提醒。(检测该权限是否还可以申请)
  20. boolean completeForbidden1 = activity.shouldShowRequestPermissionRationale(permissions[ 0]);
  21. boolean completeForbidden2 = activity.shouldShowRequestPermissionRationale(permissions[ 1]);
  22. Log.e(TAG, permissions[ 0] + " isCompleteForbidden: " + completeForbidden1 + ";" + permissions[ 1] + " isCompleteForbidden: " + completeForbidden2);
  23. if(!completeForbidden1 || !completeForbidden2){
  24. //用户点击了禁止后不再询问,建议直接提示并退出
  25. Toast.makeText(activity.getApplicationContext(), "以后可在-应用设置-权限管理-中,手动开启权限", Toast.LENGTH_SHORT).show();
  26. } else {
  27. if (isShow) {
  28. new AlertDialog.Builder(activity)
  29. .setTitle(tipTitle)
  30. .setMessage( "由于手机助手需要拍照上传和扫描二维码功能,请开启权限;\n否则,您将无法正常使用")
  31. .setPositiveButton( "立即开启", new DialogInterface.OnClickListener() {
  32. @Override
  33. public void onClick(DialogInterface dialog, int which) {
  34. PermissionHandler.checkPermissionForCameraAndWriteStorage(activity);
  35. }
  36. })
  37. .setNegativeButton( "取消", new DialogInterface.OnClickListener() {
  38. @Override
  39. public void onClick(DialogInterface dialog, int which) {
  40. //Toast.makeText(activity.getApplicationContext(), "以后可在-应用设置-权限-中,手动开启权限", Toast.LENGTH_SHORT).show();
  41. }
  42. }).setCancelable( false).show();
  43. }
  44. }
  45. } else {
  46. PermissionHandler.checkPermissionForCameraAndWriteStorage(activity);
  47. }
  48. }
  49. }
  50. }
注:shouldShowRequestPermissionRationale方法返回true表示用户彻底禁止了询问

如果用户禁止了某个权限,而你程序又运行到此处怎么办?后台会直接报错,我的处理办法是try catch 然后进行权限提示(注意不是权限检查,因为这里不应该进行检查也不适合进行检查)。看我的webChromeClient子类代码:

  1. @Override
  2. public boolean onShowFileChooser(WebView webView,
  3. ValueCallback<Uri[]> filePathCallback,
  4. FileChooserParams fileChooserParams) {
  5. mainActivity.setmUploadCallbackAboveL(filePathCallback);
  6. try {
  7. PhotoUtil.take(mainActivity);
  8. } catch (java.lang.SecurityException e){
  9. Log.e(TAG,e.getMessage(),e);
  10. if (e.getMessage() != null && e.getMessage().indexOf( "Permission Denial") != - 1) {
  11. String tipTitle = "缺少权限";
  12. if (e.getMessage().indexOf( "android.permission.WRITE_EXTERNAL_STORAGE") != - 1) {
  13. tipTitle = "缺少存储权限";
  14. } else if(e.getMessage().indexOf( "android.permission.CAMERA") != - 1){
  15. tipTitle = "缺少相机权限";
  16. }
  17. new AlertDialog.Builder(mainActivity)
  18. .setTitle( "温馨提示")
  19. .setMessage(tipTitle + ",可在-应用设置-权限管理-中,手动开启")
  20. .setPositiveButton( "知道了", new DialogInterface.OnClickListener() {
  21. @Override
  22. public void onClick(DialogInterface dialog, int which) {
  23. //Do Nothing
  24. }
  25. }).setCancelable( false).show();
  26. }
  27. }
  28. return true;
  29. }

也许有更好的办法,也许有第三方框架可以解决这个问题,但是作为初学者的我,目前先这么处理吧。


这里有个问题需要讨论:能不能在使用时才增加权限检查,而不是(首次)打开app首页时检查?

告诉你,这个是不行的!在使用时,动态检查权限,虽然app会跳出权限提示的dialog提示,但是Activity的onRequestPermissionsResult方法回调会出问题,导致你页面调整异常。这也就是为什么大部分app都是安装后,首次打开,会进行一连串权限提醒的原因。别问我为什么知道,我被坑了很久。


2.调用相机问题

如果是android 低版本,随便调(单独调用相机,不浏览相册),几行代码即可:

  1. Intent intentCamera = new Intent();
  2. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  3. intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
  4. }
  5. intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
  6. //将拍照结果保存至photo_file的Uri中,不保留在相册中
  7. intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
  8. startActivityForResult(intentCamera, FILECHOOSER_RESULTCODE);
如果是android版本,比如 android 7.0以上,那么需要做判断

  1. //兼容android 7.0+版本的照相机调用代码
  2. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  3. if (intent.resolveActivity(mainActivity.getPackageManager()) != null) {
  4. /*获取当前系统的android版本号*/
  5. int currentapiVersion = android.os.Build.VERSION.SDK_INT;
  6. Log.e(TAG, "currentapiVersion====>"+currentapiVersion);
  7. if (currentapiVersion< 24){
  8. intent.putExtra(MediaStore.EXTRA_OUTPUT, mainActivity.getImageUri());
  9. mainActivity.startActivityForResult(intent, ActivityResultConst.CAMERA_RESULTCODE);
  10. } else {
  11. ContentValues contentValues = new ContentValues( 1);
  12. contentValues.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
  13. Uri uri = mainActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
  14. intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
  15. mainActivity.startActivityForResult(intent, ActivityResultConst.CAMERA_RESULTCODE);
  16. }
  17. } else {
  18. Toast.makeText(mainActivity, "照相机不存在", Toast.LENGTH_SHORT).show();
  19. }

android 7.0以上无法调用相机,网上有网友这么建议,在onCreate方法里加入如下代码:

  1. //android 7.0系统解决拍照的问题
  2. StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
  3. StrictMode.setVmPolicy(builder.build());
  4. builder.detectFileUriExposure();
我测试过,是可以的,虽然不知道暴力的原理是什么,建议还是不要这么用。

如果需要调用相机或者打开相册,那么可以这么搞:

  1. //调用照相机和浏览图片库代码
  2. Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
  3. captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mainActivity.getImageUri());
  4. Intent Photo = new Intent(Intent.ACTION_PICK,
  5. android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  6. Intent chooserIntent = Intent.createChooser(Photo, "Image Chooser");
  7. chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{captureIntent});
  8. mainActivity.startActivityForResult(chooserIntent, ActivityResultConst.CAMERA_RESULTCODE);
照相后,Activity里的onActivityResult回调方法里面,还需要广播通知一下,刷新图库,调用方法代码如下:

  1. private static void updatePhotos(MainActivity activity) {
  2. // 该广播即使多发(即选取照片成功时也发送)也没有关系,只是唤醒系统刷新媒体文件
  3. Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
  4. intent.setData(activity.getImageUri());
  5. activity.sendBroadcast(intent);
  6. }
如果是android 高版本,高配置手机的话,照相机拍得图分辨率非常高,图片就非常大,特别是进行多图预览或上传时,后台容易报,app变卡顿:(我在进行多个图片不压缩上传时,就出现过此问题)

I/Choreographer: Skipped 179 frames!  The application may be doing too much work on its main thread.

这种情况建议压缩图片,比如压缩成800*480,最多几百K,基本不会出问题。压缩代码见我上传的demo:

  1. @SuppressWarnings( "null")
  2. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  3. private static void onActivityResultAboveL(MainActivity activity, int requestCode, int resultCode, Intent data) {
  4. if (requestCode != ActivityResultConst.CAMERA_RESULTCODE
  5. || activity.getmUploadCallbackAboveL() == null) {
  6. return;
  7. }
  8. Uri[] results = null;
  9. if (resultCode == Activity.RESULT_OK) {
  10. if (data == null) {
  11. results = new Uri[]{activity.getImageUri()};
  12. } else {
  13. String dataString = data.getDataString();
  14. ClipData clipData = data.getClipData();
  15. if (clipData != null) {
  16. results = new Uri[clipData.getItemCount()];
  17. for ( int i = 0; i < clipData.getItemCount(); i++) {
  18. ClipData.Item item = clipData.getItemAt(i);
  19. results[i] = item.getUri();
  20. }
  21. }
  22. if (dataString != null)
  23. results = new Uri[]{Uri.parse(dataString)};
  24. }
  25. }
  26. if (results != null) {
  27. //压缩
  28. results = PhotoUtil.doCompressImageForActivityResult(activity,results);
  29. activity.getmUploadCallbackAboveL().onReceiveValue(results);
  30. activity.setmUploadCallbackAboveL( null);
  31. } else {
  32. //压缩
  33. results = new Uri[]{activity.getImageUri()};
  34. results = PhotoUtil.doCompressImageForActivityResult(activity,results);
  35. activity.getmUploadCallbackAboveL().onReceiveValue(results);
  36. activity.setmUploadCallbackAboveL( null);
  37. }
  38. return;
  39. }
如果你不需要压缩,那么注释掉的这一行,results传原值给getmUploadCallbackAboveL().onReceiveValue()即可
results = PhotoUtil.doCompressImageForActivityResult(activity,results);


最后还有几个小问题,需要注意一下:
a. 二维码扫描之后,如果要返回,需要考虑是不是goback到父的父页面,不然扫描完一点击手机的返回键,直接就打开了照相机。


b. 我在AndroidManifest.xml文件给webview加了一个背景图片。
代码如 android:roundIcon="@mipmap/ic_launcher_round"


c. 我对MainActivity进行了一些简答的封装,不至于让全部的代码,都写在MainActivity.java中。
封装得很没有水平,用j2ee的思维在弄android,实在没办法。


d.  MainActivity的onActivityResult回调方法,在处理不同的行为请求返回时,照理说都应该有个requestCode,比如相机处理化代码:
  1. ContentValues contentValues = new ContentValues( 1);
  2. contentValues.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
  3. Uri uri = mainActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
  4. intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
  5. mainActivity.startActivityForResult(intent, ActivityResultConst.CAMERA_RESULTCODE);

ActivityResultConst.CAMERA_RESULTCODE是我定义的常量类属性,不同的数字代表不同的请求码。
但是问题来了,扫描二维码,我用页面JavaScript调用BarcodeCallBack类实现的,好像没有进行Activity的Intent操作,无法设置或取得请求码,怎么办呢,我用了个非常挫的办法,在MainActivity里定义了一个Map,然后扫描二维码初始化时手动往这里面放一个常量,作为请求码,在onActivityResult方法里处理判断:

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  3. super.onActivityResult(requestCode, resultCode, data);
  4. //自定义Activity返回结果码
  5. Integer extraRequestCode = (Integer) extra.get(ActivityResultConst.REQUEST_CODE_KEY);
  6. if(extraRequestCode == null || extraRequestCode == 0){
  7. //如果没有自定义返回结果码,则使用onActivityResult的参数
  8. extraRequestCode = requestCode;
  9. }
  10. //根据不同的返回码,执行不同的结果处理(一般指回调操作)
  11. if (extraRequestCode == ActivityResultConst.BARCODE_RESULTCODE) {
  12. HandleMainActivityResult.onBarcodeResult( this, requestCode, resultCode, data);
  13. }
  14. if (extraRequestCode == ActivityResultConst.CAMERA_RESULTCODE) {
  15. HandleMainActivityResult.onCameraResult( this,requestCode,resultCode,data);
  16. }
  17. extra.clear();
  18. }


e. 发现了一个比较好玩的事情:如果在AndroidManifest.xml里未注册相机权限,则似乎不需要动态检查相机权限,在安装时,权限列表里也未发现有相机权限,但是事实上,相机却可以正常调用。
如果在AndroidManifest.xml里注册相机权限,那么对不起,需要你在onCreate方法里动态检查相机权限,否则后台报错,相机无法调起。
说明相机这个权限很弱,默认可以让app使用,估计用一下,也不会怎么样,把系统搞崩溃吧。


f.如果你已经打开了照相机,这时,你按手机的回退键取消操作,再重新进来时,你会发现相机已经无法被调起。
原因是:因为对于页面表单<input type=file >来说,调用照相机相当于选择图片。而选择图片这个事件必须要有一个返回值,不然程序会一直处于等待状态,当你没有选定的时候你要传回一个null,否则程序就一直阻塞,就不能进行其它操作。
处理代码在Activity的方法onActivityResult(int requestCode, int resultCode, Intent data),对于我demo代码,也就是方法onCameraResult(MainActivity activity, int requestCode, int resultCode, Intent data)里加上取消操作的判断

  1. public static void onCameraResult(MainActivity activity, int requestCode, int resultCode, Intent data) {
  2. if(resultCode == Activity.RESULT_CANCELED) {
  3. //打开相机,若取消,必须要设置一个null返回值,不然页面会一直处于等待状态,阻塞住无法响应
  4. if(activity.getmUploadCallbackAboveL() != null){
  5. activity.getmUploadCallbackAboveL().onReceiveValue( null);
  6. }
  7. if(activity.getmUploadMessage() != null){
  8. activity.getmUploadMessage().onReceiveValue( null);
  9. }
  10. return;
  11. }
  12. updatePhotos(activity);
  13. if ( null == activity.getmUploadMessage() && null == activity.getmUploadCallbackAboveL())
  14. return;
  15. Uri result = data == null || resultCode != Activity.RESULT_OK ? null : data.getData();
  16. if (activity.getmUploadCallbackAboveL() != null) {
  17. onActivityResultAboveL(activity, requestCode, resultCode, data);
  18. } else if (activity.getmUploadMessage() != null) {
  19. Log.e( "result", result + "");
  20. if (result == null) {
  21. //低版本,暂时不做压缩处理
  22. activity.getmUploadMessage().onReceiveValue(activity.getImageUri());
  23. activity.setmUploadMessage( null);
  24. Log.e( "imageUri", activity.getImageUri() + "");
  25. } else {
  26. //压缩
  27. result = PhotoUtil.doCompressImageForActivityResult(activity, data, null);
  28. activity.getmUploadMessage().onReceiveValue(result);
  29. activity.setmUploadMessage( null);
  30. }
  31. }
  32. }
这个代码可能在我上传的demo里未更新,请读者务必要留意。


上面就是我完成的demo遇到的各种问题的汇总介绍。

如果有人觉得繁琐,那么我再上传一个最简答的demo版本,拍照预览功能的。(上传自己用jquery弄下,图片都被你file标签获取到了)。  点击下载 最简易版本的demo

项目用到了WebView包装HTML5做成app使用,其中有页面用到了二维码和拍照上传功能。本人从未做过android,短时间内完成,只能靠“热心网友”帮忙了,网上也铺天盖地各种demo和文章。

但是对于高版本,特别是android 8.0以上,网上的各种现成的Demo都不好用,各种问题。现在我成功了解决了这些问题,并汇总供初学者参考。


1. 权限问题

这个问题也是困扰最多的一个问题,各种Android版本对于权限的限制和提示不一样,特别是在我的Mate9(Android 8.0)上一度不提示,后台也不报错,但是浏览器就是在拍照后无法访问图片,搞了很久才知道是没有在代码里加动态权限检查。AndroidManifest.xml文件只是注册了需要用哪些权限并不是app获取到的权限,高版本android在安装app时忽略提示权限,所以一般app首次安装打开后,都会提示你需要什么权限。

由此,建议在首页的Activity的onCreate方法里,增加权限检查。

checkSelfPermission(Manifest.permission.CAMERA);

checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);

等等,如果只用到了拍照,那么这2个就够了,可存比可读,不用再赋读取权限。


对于权限检查,有很多问题需要考虑,比如禁止后,怎么办?如果用户勾选“禁止后不再询问”,怎么办?

我这边的处理是如下: 

Activity的回调方法onRequestPermissionsResult 

  1. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  2. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  3. HandleMainActivityResult.onRequestPermissionsResult( this,requestCode,permissions,grantResults);
  4. }
处理权限提示等,基本都在HandleMainActivityResult.onRequestPermissionsResult方法

  1. //权限回调方法(默认知道某个requestCode赋值几个权限,数组大小为几)
  2. public static void onRequestPermissionsResult(final MainActivity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  3. if (requestCode == ActivityResultConst.CODE_FOR_WRITE_PERMISSION) {
  4. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  5. boolean isShow = true;
  6. Log.e(TAG, "grantResults.length:"+grantResults.length);
  7. String tipTitle = "权限不可用";
  8. if(grantResults.length == 2){
  9. if (grantResults[ 0] != PackageManager.PERMISSION_GRANTED && grantResults[ 1] != PackageManager.PERMISSION_GRANTED) {
  10. tipTitle = "相机和存储权限不可用";
  11. } else if(grantResults[ 0] != PackageManager.PERMISSION_GRANTED){
  12. tipTitle = "相机权限不可用";
  13. } else if(grantResults[ 1] != PackageManager.PERMISSION_GRANTED){
  14. tipTitle = "存储权限不可用";
  15. } else {
  16. //权限已经全部赋值成功
  17. isShow = false;
  18. }
  19. // 判断用户是否 点击了不再提醒。(检测该权限是否还可以申请)
  20. boolean completeForbidden1 = activity.shouldShowRequestPermissionRationale(permissions[ 0]);
  21. boolean completeForbidden2 = activity.shouldShowRequestPermissionRationale(permissions[ 1]);
  22. Log.e(TAG, permissions[ 0] + " isCompleteForbidden: " + completeForbidden1 + ";" + permissions[ 1] + " isCompleteForbidden: " + completeForbidden2);
  23. if(!completeForbidden1 || !completeForbidden2){
  24. //用户点击了禁止后不再询问,建议直接提示并退出
  25. Toast.makeText(activity.getApplicationContext(), "以后可在-应用设置-权限管理-中,手动开启权限", Toast.LENGTH_SHORT).show();
  26. } else {
  27. if (isShow) {
  28. new AlertDialog.Builder(activity)
  29. .setTitle(tipTitle)
  30. .setMessage( "由于手机助手需要拍照上传和扫描二维码功能,请开启权限;\n否则,您将无法正常使用")
  31. .setPositiveButton( "立即开启", new DialogInterface.OnClickListener() {
  32. @Override
  33. public void onClick(DialogInterface dialog, int which) {
  34. PermissionHandler.checkPermissionForCameraAndWriteStorage(activity);
  35. }
  36. })
  37. .setNegativeButton( "取消", new DialogInterface.OnClickListener() {
  38. @Override
  39. public void onClick(DialogInterface dialog, int which) {
  40. //Toast.makeText(activity.getApplicationContext(), "以后可在-应用设置-权限-中,手动开启权限", Toast.LENGTH_SHORT).show();
  41. }
  42. }).setCancelable( false).show();
  43. }
  44. }
  45. } else {
  46. PermissionHandler.checkPermissionForCameraAndWriteStorage(activity);
  47. }
  48. }
  49. }
  50. }
注:shouldShowRequestPermissionRationale方法返回true表示用户彻底禁止了询问

如果用户禁止了某个权限,而你程序又运行到此处怎么办?后台会直接报错,我的处理办法是try catch 然后进行权限提示(注意不是权限检查,因为这里不应该进行检查也不适合进行检查)。看我的webChromeClient子类代码:

  1. @Override
  2. public boolean onShowFileChooser(WebView webView,
  3. ValueCallback<Uri[]> filePathCallback,
  4. FileChooserParams fileChooserParams) {
  5. mainActivity.setmUploadCallbackAboveL(filePathCallback);
  6. try {
  7. PhotoUtil.take(mainActivity);
  8. } catch (java.lang.SecurityException e){
  9. Log.e(TAG,e.getMessage(),e);
  10. if (e.getMessage() != null && e.getMessage().indexOf( "Permission Denial") != - 1) {
  11. String tipTitle = "缺少权限";
  12. if (e.getMessage().indexOf( "android.permission.WRITE_EXTERNAL_STORAGE") != - 1) {
  13. tipTitle = "缺少存储权限";
  14. } else if(e.getMessage().indexOf( "android.permission.CAMERA") != - 1){
  15. tipTitle = "缺少相机权限";
  16. }
  17. new AlertDialog.Builder(mainActivity)
  18. .setTitle( "温馨提示")
  19. .setMessage(tipTitle + ",可在-应用设置-权限管理-中,手动开启")
  20. .setPositiveButton( "知道了", new DialogInterface.OnClickListener() {
  21. @Override
  22. public void onClick(DialogInterface dialog, int which) {
  23. //Do Nothing
  24. }
  25. }).setCancelable( false).show();
  26. }
  27. }
  28. return true;
  29. }

也许有更好的办法,也许有第三方框架可以解决这个问题,但是作为初学者的我,目前先这么处理吧。


这里有个问题需要讨论:能不能在使用时才增加权限检查,而不是(首次)打开app首页时检查?

告诉你,这个是不行的!在使用时,动态检查权限,虽然app会跳出权限提示的dialog提示,但是Activity的onRequestPermissionsResult方法回调会出问题,导致你页面调整异常。这也就是为什么大部分app都是安装后,首次打开,会进行一连串权限提醒的原因。别问我为什么知道,我被坑了很久。


2.调用相机问题

如果是android 低版本,随便调(单独调用相机,不浏览相册),几行代码即可:

  1. Intent intentCamera = new Intent();
  2. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  3. intentCamera.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //添加这一句表示对目标应用临时授权该Uri所代表的文件
  4. }
  5. intentCamera.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
  6. //将拍照结果保存至photo_file的Uri中,不保留在相册中
  7. intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
  8. startActivityForResult(intentCamera, FILECHOOSER_RESULTCODE);
如果是android版本,比如 android 7.0以上,那么需要做判断

  1. //兼容android 7.0+版本的照相机调用代码
  2. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  3. if (intent.resolveActivity(mainActivity.getPackageManager()) != null) {
  4. /*获取当前系统的android版本号*/
  5. int currentapiVersion = android.os.Build.VERSION.SDK_INT;
  6. Log.e(TAG, "currentapiVersion====>"+currentapiVersion);
  7. if (currentapiVersion< 24){
  8. intent.putExtra(MediaStore.EXTRA_OUTPUT, mainActivity.getImageUri());
  9. mainActivity.startActivityForResult(intent, ActivityResultConst.CAMERA_RESULTCODE);
  10. } else {
  11. ContentValues contentValues = new ContentValues( 1);
  12. contentValues.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
  13. Uri uri = mainActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
  14. intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
  15. mainActivity.startActivityForResult(intent, ActivityResultConst.CAMERA_RESULTCODE);
  16. }
  17. } else {
  18. Toast.makeText(mainActivity, "照相机不存在", Toast.LENGTH_SHORT).show();
  19. }

android 7.0以上无法调用相机,网上有网友这么建议,在onCreate方法里加入如下代码:

  1. //android 7.0系统解决拍照的问题
  2. StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
  3. StrictMode.setVmPolicy(builder.build());
  4. builder.detectFileUriExposure();
我测试过,是可以的,虽然不知道暴力的原理是什么,建议还是不要这么用。

如果需要调用相机或者打开相册,那么可以这么搞:

  1. //调用照相机和浏览图片库代码
  2. Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
  3. captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mainActivity.getImageUri());
  4. Intent Photo = new Intent(Intent.ACTION_PICK,
  5. android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
  6. Intent chooserIntent = Intent.createChooser(Photo, "Image Chooser");
  7. chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{captureIntent});
  8. mainActivity.startActivityForResult(chooserIntent, ActivityResultConst.CAMERA_RESULTCODE);
照相后,Activity里的onActivityResult回调方法里面,还需要广播通知一下,刷新图库,调用方法代码如下:

  1. private static void updatePhotos(MainActivity activity) {
  2. // 该广播即使多发(即选取照片成功时也发送)也没有关系,只是唤醒系统刷新媒体文件
  3. Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
  4. intent.setData(activity.getImageUri());
  5. activity.sendBroadcast(intent);
  6. }
如果是android 高版本,高配置手机的话,照相机拍得图分辨率非常高,图片就非常大,特别是进行多图预览或上传时,后台容易报,app变卡顿:(我在进行多个图片不压缩上传时,就出现过此问题)

I/Choreographer: Skipped 179 frames!  The application may be doing too much work on its main thread.

这种情况建议压缩图片,比如压缩成800*480,最多几百K,基本不会出问题。压缩代码见我上传的demo:

  1. @SuppressWarnings( "null")
  2. @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  3. private static void onActivityResultAboveL(MainActivity activity, int requestCode, int resultCode, Intent data) {
  4. if (requestCode != ActivityResultConst.CAMERA_RESULTCODE
  5. || activity.getmUploadCallbackAboveL() == null) {
  6. return;
  7. }
  8. Uri[] results = null;
  9. if (resultCode == Activity.RESULT_OK) {
  10. if (data == null) {
  11. results = new Uri[]{activity.getImageUri()};
  12. } else {
  13. String dataString = data.getDataString();
  14. ClipData clipData = data.getClipData();
  15. if (clipData != null) {
  16. results = new Uri[clipData.getItemCount()];
  17. for ( int i = 0; i < clipData.getItemCount(); i++) {
  18. ClipData.Item item = clipData.getItemAt(i);
  19. results[i] = item.getUri();
  20. }
  21. }
  22. if (dataString != null)
  23. results = new Uri[]{Uri.parse(dataString)};
  24. }
  25. }
  26. if (results != null) {
  27. //压缩
  28. results = PhotoUtil.doCompressImageForActivityResult(activity,results);
  29. activity.getmUploadCallbackAboveL().onReceiveValue(results);
  30. activity.setmUploadCallbackAboveL( null);
  31. } else {
  32. //压缩
  33. results = new Uri[]{activity.getImageUri()};
  34. results = PhotoUtil.doCompressImageForActivityResult(activity,results);
  35. activity.getmUploadCallbackAboveL().onReceiveValue(results);
  36. activity.setmUploadCallbackAboveL( null);
  37. }
  38. return;
  39. }
如果你不需要压缩,那么注释掉的这一行,results传原值给getmUploadCallbackAboveL().onReceiveValue()即可
results = PhotoUtil.doCompressImageForActivityResult(activity,results);


最后还有几个小问题,需要注意一下:
a. 二维码扫描之后,如果要返回,需要考虑是不是goback到父的父页面,不然扫描完一点击手机的返回键,直接就打开了照相机。


b. 我在AndroidManifest.xml文件给webview加了一个背景图片。
代码如 android:roundIcon="@mipmap/ic_launcher_round"


c. 我对MainActivity进行了一些简答的封装,不至于让全部的代码,都写在MainActivity.java中。
封装得很没有水平,用j2ee的思维在弄android,实在没办法。


d.  MainActivity的onActivityResult回调方法,在处理不同的行为请求返回时,照理说都应该有个requestCode,比如相机处理化代码:
  1. ContentValues contentValues = new ContentValues( 1);
  2. contentValues.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
  3. Uri uri = mainActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
  4. intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
  5. mainActivity.startActivityForResult(intent, ActivityResultConst.CAMERA_RESULTCODE);

ActivityResultConst.CAMERA_RESULTCODE是我定义的常量类属性,不同的数字代表不同的请求码。
但是问题来了,扫描二维码,我用页面JavaScript调用BarcodeCallBack类实现的,好像没有进行Activity的Intent操作,无法设置或取得请求码,怎么办呢,我用了个非常挫的办法,在MainActivity里定义了一个Map,然后扫描二维码初始化时手动往这里面放一个常量,作为请求码,在onActivityResult方法里处理判断:

  1. @Override
  2. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  3. super.onActivityResult(requestCode, resultCode, data);
  4. //自定义Activity返回结果码
  5. Integer extraRequestCode = (Integer) extra.get(ActivityResultConst.REQUEST_CODE_KEY);
  6. if(extraRequestCode == null || extraRequestCode == 0){
  7. //如果没有自定义返回结果码,则使用onActivityResult的参数
  8. extraRequestCode = requestCode;
  9. }
  10. //根据不同的返回码,执行不同的结果处理(一般指回调操作)
  11. if (extraRequestCode == ActivityResultConst.BARCODE_RESULTCODE) {
  12. HandleMainActivityResult.onBarcodeResult( this, requestCode, resultCode, data);
  13. }
  14. if (extraRequestCode == ActivityResultConst.CAMERA_RESULTCODE) {
  15. HandleMainActivityResult.onCameraResult( this,requestCode,resultCode,data);
  16. }
  17. extra.clear();
  18. }


e. 发现了一个比较好玩的事情:如果在AndroidManifest.xml里未注册相机权限,则似乎不需要动态检查相机权限,在安装时,权限列表里也未发现有相机权限,但是事实上,相机却可以正常调用。
如果在AndroidManifest.xml里注册相机权限,那么对不起,需要你在onCreate方法里动态检查相机权限,否则后台报错,相机无法调起。
说明相机这个权限很弱,默认可以让app使用,估计用一下,也不会怎么样,把系统搞崩溃吧。


f.如果你已经打开了照相机,这时,你按手机的回退键取消操作,再重新进来时,你会发现相机已经无法被调起。
原因是:因为对于页面表单<input type=file >来说,调用照相机相当于选择图片。而选择图片这个事件必须要有一个返回值,不然程序会一直处于等待状态,当你没有选定的时候你要传回一个null,否则程序就一直阻塞,就不能进行其它操作。
处理代码在Activity的方法onActivityResult(int requestCode, int resultCode, Intent data),对于我demo代码,也就是方法onCameraResult(MainActivity activity, int requestCode, int resultCode, Intent data)里加上取消操作的判断

  1. public static void onCameraResult(MainActivity activity, int requestCode, int resultCode, Intent data) {
  2. if(resultCode == Activity.RESULT_CANCELED) {
  3. //打开相机,若取消,必须要设置一个null返回值,不然页面会一直处于等待状态,阻塞住无法响应
  4. if(activity.getmUploadCallbackAboveL() != null){
  5. activity.getmUploadCallbackAboveL().onReceiveValue( null);
  6. }
  7. if(activity.getmUploadMessage() != null){
  8. activity.getmUploadMessage().onReceiveValue( null);
  9. }
  10. return;
  11. }
  12. updatePhotos(activity);
  13. if ( null == activity.getmUploadMessage() && null == activity.getmUploadCallbackAboveL())
  14. return;
  15. Uri result = data == null || resultCode != Activity.RESULT_OK ? null : data.getData();
  16. if (activity.getmUploadCallbackAboveL() != null) {
  17. onActivityResultAboveL(activity, requestCode, resultCode, data);
  18. } else if (activity.getmUploadMessage() != null) {
  19. Log.e( "result", result + "");
  20. if (result == null) {
  21. //低版本,暂时不做压缩处理
  22. activity.getmUploadMessage().onReceiveValue(activity.getImageUri());
  23. activity.setmUploadMessage( null);
  24. Log.e( "imageUri", activity.getImageUri() + "");
  25. } else {
  26. //压缩
  27. result = PhotoUtil.doCompressImageForActivityResult(activity, data, null);
  28. activity.getmUploadMessage().onReceiveValue(result);
  29. activity.setmUploadMessage( null);
  30. }
  31. }
  32. }
这个代码可能在我上传的demo里未更新,请读者务必要留意。


上面就是我完成的demo遇到的各种问题的汇总介绍。

如果有人觉得繁琐,那么我再上传一个最简答的demo版本,拍照预览功能的。(上传自己用jquery弄下,图片都被你file标签获取到了)。  点击下载 最简易版本的demo

猜你喜欢

转载自blog.csdn.net/zhangbijun1230/article/details/80991013
今日推荐