前言
在做上一个项目时深深受到了图片上传的苦恼。图片上传主要分为两个部分,首先要获取图片,而获取图片可以分为从文件获取或者拍照获取。第二个部分才是上传图片,两个部分都是走了不少弯路。由于Android系统的碎片化比较严重,我们可能出现在第一台机子上能获取图片,但是换一个机子就不能获取图片的问题,并且在Android6.0,7.0之后也要做一定的适配,这样对于开发者来说,无疑很蛋疼。由于也是初学者,很多东西没有考虑到,适配起来也是有点难度的。
这几天也是从github上找到了一个库(地址在这TakePhoto),经过简单的学习之后,发现用起来还是蛮简单的,并且在不同机型之间都能达到同样的效果。更重要的是可以根据不同配置达到不同的效果
接下来看下用法
获取图片
1) 获取TakePhoto对象
一) 通过继承的方式
继承TakePhotoActivity、TakePhotoFragmentActivity、TakePhotoFragment三者之一。
通过getTakePhoto()获取TakePhoto实例进行相关操作。
重写以下方法获取结果
void takeSuccess(TResult result); void takeFail(TResult result,String msg); void takeCancel();
这种方法使用起来虽然简单,但是感觉定制性不高,必须继承指定的Activity,而 有时我们已经封装好了BaseActivity,不想再改了。有时候通过继承无法满足实际项目的需求。
二) 通过组装的方式去使用
实现TakePhoto.TakeResultListener,InvokeListener接口。
在 onCreate,onActivityResult,onSaveInstanceState方法中调用TakePhoto对用的方法。
重写onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults),添加如下代码。
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); //以下代码为处理Android6.0、7.0动态权限所需 TPermissionType type=PermissionManager.onRequestPermissionsResult(requestCode,permissions,grantResults); PermissionManager.handlePermissionsResult(this,type,invokeParam,this); }
重写TPermissionType invoke(InvokeParam invokeParam)方法,添加如下代码:
@Override public TPermissionType invoke(InvokeParam invokeParam) { TPermissionType type=PermissionManager.checkPermission(TContextWrap.of(this),invokeParam.getMethod()); if(TPermissionType.WAIT.equals(type)){ this.invokeParam=invokeParam; } return type; }
添加如下代码获取TakePhoto实例:
/** * 获取TakePhoto实例 * @return */ public TakePhoto getTakePhoto(){ if (takePhoto==null){ takePhoto= (TakePhoto) TakePhotoInvocationHandler.of(this).bind(new TakePhotoImpl(this,this)); } return takePhoto; }
2)自定义UI
不仅可以对于参数自定义,也可以对于UI的自定义,比如自定义相册,自定义Toolbar, 自定义状态栏,自定义提示文字,自定义裁切工具(需要使用自带的TakePhoto裁剪才行)。
3)通过TakePhoto对象获取图片
支持从相册获取,也支持拍照,相关Api
* 从相机获取图片并裁剪 * @param outPutUri 图片裁剪之后保存的路径 * @param options 裁剪配置 */ void onPickFromCaptureWithCrop(Uri outPutUri, CropOptions options); /** * 从相册中获取图片并裁剪 * @param outPutUri 图片裁剪之后保存的路径 * @param options 裁剪配置 */ void onPickFromGalleryWithCrop(Uri outPutUri, CropOptions options); /** * 从文件中获取图片并裁剪 * @param outPutUri 图片裁剪之后保存的路径 * @param options 裁剪配置 */ void onPickFromDocumentsWithCrop(Uri outPutUri, CropOptions options); /** * 图片多选,并裁切 * @param limit 最多选择图片张数的限制 * @param options 裁剪配置 * */ void onPickMultipleWithCrop(int limit, CropOptions options);
4)裁剪配置
CropOptions 用于裁剪的配置类,可以对图片的裁剪比例,最大输出大小,以及是否使用TakePhoto自带的裁剪工具进行裁剪等,进行个性化配置。
压缩图片 onEnableCompress(CompressConfig config,boolean showCompressDialog)
指定压缩工具 takePhoto里面自带压缩算法,也可以通过第三方的Luban进行压缩
对于TakePhoto的二次封装
封装是对第二种方法的封装,主要参考了第一种的思想封装的。
关于TakePhoto的库代码全部封装到一个TakePhotoUtil工具类中,看代码:
public class TakePhotoUtil implements TakePhoto.TakeResultListener, InvokeListener { private static final String TAG = TakePhotoUtil.class.getName(); private TakePhoto takePhoto; private InvokeParam invokeParam; private Activity activity; private Fragment fragment; public TakePhotoUtil(Activity activity){ this.activity = activity; } public TakePhotoUtil(Fragment fragment){ this.fragment = fragment; } /** * 获取TakePhoto实例 * @return */ public TakePhoto getTakePhoto(){ if (takePhoto==null){ takePhoto= (TakePhoto) TakePhotoInvocationHandler.of(this).bind(new TakePhotoImpl(activity,this)); } return takePhoto; } public void onCreate(Bundle savedInstanceState){ getTakePhoto().onCreate(savedInstanceState); } public void onSaveInstanceState(Bundle outState){ getTakePhoto().onSaveInstanceState(outState); } public void onActivityResult(int requestCode, int resultCode, Intent data){ getTakePhoto().onActivityResult(requestCode, resultCode, data); } public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { PermissionManager.TPermissionType type=PermissionManager.onRequestPermissionsResult(requestCode,permissions,grantResults); PermissionManager.handlePermissionsResult(activity,type,invokeParam,this); } /** * * @param result */ @Override public void takeSuccess(TResult result) { if(listener != null){ listener.takeSuccess(result); } // deleteCachePic(); } @Override public void takeFail(TResult result, String msg) { if(listener != null){ listener.takeFail(result, msg); } // deleteCachePic(); } @Override public void takeCancel() { if(listener != null){ listener.takeCancel(); } } public void deleteCachePic(){ File file=new File(Environment.getExternalStorageDirectory(), "/takephoto/"); if(!file.exists()) return; File[] files = file.listFiles(); for (File f: files) { f.delete(); } } public interface TakePhotoListener{ void takeSuccess(TResult result); void takeFail(TResult result, String msg); void takeCancel(); } public TakePhotoListener listener; public void setTakePhotoListener(SimpleTakePhotoListener listener){ this.listener = listener; } public static class SimpleTakePhotoListener implements TakePhotoListener{ @Override public void takeSuccess(TResult result) { } @Override public void takeFail(TResult result, String msg) { } @Override public void takeCancel() { } } @Override public PermissionManager.TPermissionType invoke(InvokeParam invokeParam) { PermissionManager.TPermissionType type=PermissionManager.checkPermission(TContextWrap.of(activity),invokeParam.getMethod()); if(PermissionManager.TPermissionType.WAIT.equals(type)){ this.invokeParam=invokeParam; } return type; } /** * * @param select_type */ public void takePhoto(Select_type select_type, SimpleTakePhotoListener listener){ takePhoto(select_type, null, listener); } public void takePhoto(Select_type select_type, PhotoConfigOptions cropOptions, SimpleTakePhotoListener listener){ if (takePhoto == null){ Toast.makeText(activity, "请先开启照片功能", Toast.LENGTH_SHORT).show(); return; } setTakePhotoListener(listener); if(cropOptions == null){ cropOptions = new PhotoConfigOptions(); } cropOptions.configCompress(); //压缩配置 cropOptions.configTakePhoto(); //拍照配置 File file=new File(Environment.getExternalStorageDirectory(), "/takephoto/"+System.currentTimeMillis() + ".jpg"); if (!file.getParentFile().exists())file.getParentFile().mkdirs(); Uri imageUri = Uri.fromFile(file); switch (select_type){ case PICK_BY_SELECT: //从相册获取 if(cropOptions.limit > 1){ if(cropOptions.crop == true){ takePhoto.onPickMultipleWithCrop(cropOptions.limit, cropOptions.getCropOptions()); }else { takePhoto.onPickMultiple(cropOptions.limit); } } if(cropOptions.chooseFromFile){ if(cropOptions.crop == true){ takePhoto.onPickFromDocumentsWithCrop(imageUri, cropOptions.getCropOptions()); }else { takePhoto.onPickFromDocuments(); } }else { if(cropOptions.crop == true){ takePhoto.onPickFromGalleryWithCrop(imageUri, cropOptions.getCropOptions()); }else { takePhoto.onPickFromGallery(); } } break; case PICK_BY_TAKE: //拍照获取 if(cropOptions.crop == true){ takePhoto.onPickFromCaptureWithCrop(imageUri, cropOptions.getCropOptions()); }else { takePhoto.onPickFromCapture(imageUri); } break; default: break; } } /** * 图片的裁剪配置选项内部类 */ public class PhotoConfigOptions{ //裁剪配置 private boolean crop = true; //是否裁剪 private boolean withWonCrop = true; //是否采用自带的裁剪工具,默认选取第三方的裁剪工具 private boolean cropSize = true; //尺寸还是比例 //压缩配置 private boolean useOwnCompressTool = true; //使用自带的压缩工具 private boolean isCompress = true; //是否压缩 private boolean showProgressBar = true; //显示压缩进度条 // private private int maxSize = 102400; //选择图片配置 private boolean useOwnGallery = true; //选择使用自带的相册 private boolean chooseFromFile = false; //从文件获取图片 private int limit = 1; //选择最多图片的配置,选择多张图片会自动切换到TakePhoto自带相册 //其它配置 private boolean savePic = true; //选择完之后是否保存图片 private boolean correctTool = false; //纠正拍照的照片旋转角度 private int height = 800; private int width = 800; /** * 裁剪相关配置 * @return */ public CropOptions getCropOptions(){ if(crop == false) return null; CropOptions.Builder builder = new CropOptions.Builder(); if(cropSize){ builder.setOutputX(width).setOutputY(height); }else { builder.setAspectX(width).setAspectY(height); } builder.setWithOwnCrop(withWonCrop); //默认采用第三方配置 return builder.create(); } /** * 图片压缩相关配置 */ public void configCompress(){ if(isCompress == false) { takePhoto.onEnableCompress(null, false); return; } CompressConfig config; if(useOwnCompressTool){ config = new CompressConfig.Builder() .setMaxSize(maxSize) .setMaxPixel(width>height?width:height) .enableReserveRaw(savePic) .create(); }else { LubanOptions options = new LubanOptions.Builder() .setMaxHeight(height) .setMaxWidth(maxSize) .create(); config = CompressConfig.ofLuban(options); config.enableReserveRaw(savePic); } takePhoto.onEnableCompress(config, showProgressBar); } public void configTakePhoto(){ TakePhotoOptions.Builder builder = new TakePhotoOptions.Builder(); if(useOwnGallery){ builder.setWithOwnGallery(true); } if(correctTool){ builder.setCorrectImage(true); } takePhoto.setTakePhotoOptions(builder.create()); } public void setCrop(boolean crop) { this.crop = crop; } public void setWithWonCrop(boolean withWonCrop) { this.withWonCrop = withWonCrop; } public void setCropSize(boolean cropSize) { this.cropSize = cropSize; } public void setUseOwnCompressTool(boolean useOwnCompressTool) { this.useOwnCompressTool = useOwnCompressTool; } public void setCompress(boolean compress) { isCompress = compress; } public void setShowProgressBar(boolean showProgressBar) { this.showProgressBar = showProgressBar; } public void setMaxSize(int maxSize) { this.maxSize = maxSize; } public void setUseOwnGallery(boolean useOwnGallery) { this.useOwnGallery = useOwnGallery; } public void setChooseFromFile(boolean chooseFromFile) { this.chooseFromFile = chooseFromFile; } public void setLimit(int limit) { this.limit = limit; } public void setSavePic(boolean savePic) { this.savePic = savePic; } public void setCorrectTool(boolean correctTool) { this.correctTool = correctTool; } public void setHeight(int height) { this.height = height; } public void setWidth(int width) { this.width = width; } } /** * 照片获取方式, 从相册获取或拍照处理 */ public enum Select_type{ PICK_BY_SELECT, PICK_BY_TAKE } }
封装了一个BaseTakePhotoActivity,里面的代码如下:
protected TakePhotoUtil takePhotoUtil; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { takePhotoUtil = new TakePhotoUtil(this); if(useTakePhoto()){ takePhotoUtil.onCreate(savedInstanceState); } super.onCreate(savedInstanceState); } @Override protected void onSaveInstanceState(Bundle outState) { if(useTakePhoto()){ takePhotoUtil.onSaveInstanceState(outState); } super.onSaveInstanceState(outState); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(useTakePhoto()){ takePhotoUtil.onActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if(useTakePhoto()){ takePhotoUtil.onRequestPermissionsResult(requestCode, permissions, grantResults); } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } protected boolean useTakePhoto(){ return false; }
其他对于业务的封装,可以再封装一个BaseActivity,继承自BaseTakePhotoActivity,这样就可以不影响BaseActivity的使用,如果我们在主Activity中使用获取图片的功能需要两步
1)开启TakePhoto功能
@Override protected boolean useTakePhoto() { return true; }
2 ) 获取图片
takePhotoUtil.takePhoto(TakePhotoUtil.Select_type.PICK_BY_TAKE, new TakePhotoUtil.SimpleTakePhotoListener(){ @Override public void takeSuccess(TResult result) { String s = result.getImage().getCompressPath(); Bitmap bitmap = BitmapFactory.decodeFile(s); iv.setImageBitmap(bitmap); } });
takePhoto()的第一个参数是一个枚举类型的参数,分别为从相册获取和拍照获取,第二个参数为获取成功失败监听,有三个回调,由于有些回调不是必须的,所以对Listener做了一个适配,只需要回调想要的方法即可,获取成功之后就可以通过TResult封装的参数获取想要的图片以及图片地址。对于获取到的图片地址就可以做一些上传处理。
图片上传
可以借助okhttp3实现上传功能
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); RequestBody requestBody = RequestBody.create(MediaType.parse(MULTIPART_FORM_DATA), file); MultipartBody.Part part = MultipartBody.Part.createFormData("dir", file.getName(), requestBody); builder.addPart(part); Request.Builder builder1 = new Request.Builder().url(url).post(builder.build()); Request request = builder1.build(); HttpUtils.client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { } @Override public void onResponse(Call call, Response response) throws IOException { if(response.isSuccessful()){ final String s = response.body().string(); ((Activity)context).runOnUiThread(new Runnable() { @Override public void run() { } }); } } });
大致代码如上
最后
由于当时没有找到这个库,于是跑去问公司另一个做Android的,看了下他封装的代码,确实也是值得学习的,他的代码也是适配到了Android7.0,贴下它的代码,方便以后学习:
public class CameraUtil { private static final int REQUEST_CAPTURE_CAMERA = 1221; private static final int REQUEST_CROP = 1222; private static final int REQUEST_OPEN_ALBUM = 1223; private static final String TAG = "Camera"; private static Uri mCacheUri; private CameraUtil() { } @RequiresPermission(allOf = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}) public static void getImageFromCamera(Activity activity) { if (checkExternalStorageState(activity)) { activity.startActivityForResult(getImageFromCamera(activity.getApplicationContext()), REQUEST_CAPTURE_CAMERA); } } @RequiresPermission(allOf = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}) @Deprecated public static void getImageFromCamera(Fragment fragment) { if (checkExternalStorageState(fragment.getContext())) { fragment.startActivityForResult(getImageFromCamera(fragment.getContext()), REQUEST_CAPTURE_CAMERA); } } private static Intent getImageFromCamera(Context context) { Intent getImageByCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); mCacheUri = getCachePhotoUri(context.getApplicationContext()); getImageByCamera.putExtra(MediaStore.EXTRA_OUTPUT, mCacheUri); getImageByCamera.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); grantUriPermission(context, getImageByCamera, mCacheUri); return getImageByCamera; } private static boolean checkExternalStorageState(Context context) { if (TextUtils.equals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED)) { return true; } Toast.makeText(context.getApplicationContext(), "请确认已经插入SD卡", Toast.LENGTH_LONG).show(); return false; } @SuppressWarnings("ResultOfMethodCallIgnored") public static File getCachePhotoFile() { File file = new File(Environment.getExternalStorageDirectory(), "/lenso/cache/CameraTakePhoto" + System.currentTimeMillis() + ".jpg"); if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); return file; } private static Uri getCachePhotoUri(Context context) { return FileProvider.getUriForFile(context, getAuthority(context), getCachePhotoFile()); } private static Uri getCachePhotoUri(Context context, File file) { return FileProvider.getUriForFile(context, getAuthority(context), file); } public static void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data, OnActivityResultListener listener) { onActivityResult(activity, null, requestCode, resultCode, data, listener); } /** * getCachePhotoFile().getParentFile().getAbsolutePath() * @param dir * @return */ public static boolean deleteDir(File dir) { if (dir != null && dir.isDirectory()) { String[] children = dir.list(); for (int i = 0; i < children.length; i++) { boolean success = deleteDir(new File(dir, children[i])); if (!success) { return false; } } } return dir.delete(); } public static File saveBitmap(Bitmap bitmap) { File file = getCachePhotoFile(); if (bitmap == null || bitmap.isRecycled()) return file; FileOutputStream outputStream = null; try { outputStream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (outputStream != null) try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } bitmap.recycle(); } return file; } public static void copy(File file, File point) { if (!file.exists()) return; if (!point.getParentFile().exists()) point.getParentFile().mkdirs(); BufferedInputStream inputStream = null; BufferedOutputStream outputStream = null; try { inputStream = new BufferedInputStream(new FileInputStream(file)); outputStream = new BufferedOutputStream(new FileOutputStream(point)); byte[] buff = new byte[1024 * 1024 * 2]; int len; while ((len = inputStream.read(buff)) != -1) { outputStream.write(buff, 0, len); outputStream.flush(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { closeStream(inputStream); closeStream(outputStream); } } private static void closeStream(Closeable closeable) { if (closeable != null) try { closeable.close(); } catch (IOException e) { e.printStackTrace(); } } public static void onActivityResult(Activity activity, CropOption crop, int requestCode, int resultCode, Intent data, OnActivityResultListener listener) { if (resultCode == Activity.RESULT_CANCELED) return; Uri uri; switch (requestCode) { case REQUEST_OPEN_ALBUM: uri = data.getData(); if (uri != null) { mCacheUri = getCachePhotoUri(activity); copy(new File(getRealFilePath(activity, uri)), new File(getRealFilePath(activity, mCacheUri))); } else { Bitmap bitmap = data.getParcelableExtra("data"); File file = saveBitmap(bitmap); mCacheUri = getCachePhotoUri(activity, file); } case REQUEST_CAPTURE_CAMERA: uri = mCacheUri; if (listener != null) { listener.requestCaptureCamera(getRealFilePath(activity, uri), null); } if (crop == null) return; crop.setSource(uri); Intent intent = crop.create(); grantUriPermission(activity, intent, crop.getOutput()); activity.startActivityForResult(intent, REQUEST_CROP); break; case REQUEST_CROP: if (listener != null && data != null) { listener.requestCrop(getRealFilePath(activity, mCacheUri), (Bitmap) data.getParcelableExtra("data")); } break; } } @RequiresPermission(allOf = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}) public static void getImageFromAlbum(Activity activity) { Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*");//相片类型 activity.startActivityForResult(intent, REQUEST_OPEN_ALBUM); } @RequiresPermission(allOf = {Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}) @Deprecated public static void getImageFromAlbum(Fragment fragment) { Intent intent = new Intent(Intent.ACTION_PICK); intent.setType("image/*");//相片类型 fragment.startActivityForResult(intent, REQUEST_OPEN_ALBUM); } public interface OnActivityResultListener { void requestCaptureCamera(String path, Bitmap bitmap); void requestCrop(String path, Bitmap bitmap); } /** * Try to return the absolute file path from the given Uri * * @param context context * @param uri uri * @return the file path or null */ public static String getRealFilePath(final Context context, final Uri uri) { if (null == uri) return null; String path = uri.toString(); if (path.startsWith("content://" + getAuthority(context) + "/rc_external_path")) { return path.replace("content://" + getAuthority(context) + "/rc_external_path", Environment.getExternalStorageDirectory().getAbsolutePath()); } final String scheme = uri.getScheme(); String data = null; if (scheme == null) data = uri.getPath(); else if (ContentResolver.SCHEME_FILE.equals(scheme)) { data = uri.getPath(); } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null); if (null != cursor) { if (cursor.moveToFirst()) { int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); if (index > -1) { data = cursor.getString(index); } } cursor.close(); } } return data; } private static String getAuthority(Context context) { return context.getPackageName() + ".FileProvider"; } public static class CropOption { private int aspectX=1;//x比例 private int aspectY=1;//y比例 private boolean returnData = false;//是返回bitmap,否返回uri private String outputFormat;//输出流保存格式JPG PNG ... private int outputX=200;//返回的bitmap宽 private int outputY=200;//返回的bitmap高 private Uri output;//输出流保存路径 private Uri source;//需要截图的图片uri private boolean noFaceDetection = true;//是否关闭人脸识别功能 // get和set方法省略 private Intent create() { if (source == null) throw new NullPointerException("没有设置图片uri"); Intent intent = new Intent("com.android.camera.action.CROP"); intent.setDataAndType(source, "image/*"); intent.putExtra("crop", "true"); if (aspectX > 0) intent.putExtra("aspectX", aspectX); if (aspectY > 0) intent.putExtra("aspectY", aspectY); if (outputX > 0) intent.putExtra("outputX", outputX); if (outputY > 0) intent.putExtra("outputY", outputY); intent.putExtra("return-data", returnData); if (!returnData) { output = output == null ? source : output; outputFormat = outputFormat == null ? Bitmap.CompressFormat.JPEG.toString() : outputFormat; intent.putExtra(MediaStore.EXTRA_OUTPUT, output); intent.putExtra("outputFormat", outputFormat); intent.setType("image/*"); intent.putExtra("noFaceDetection", noFaceDetection); } return intent; } } private static void grantUriPermission(Context context, Intent intent, Uri uri) { ListresInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); for (ResolveInfo resolveInfo : resInfoList) { String packageName = resolveInfo.activityInfo.packageName; context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); } } } //xml文件部分 <?xml version="1.0" encoding="utf-8"?> //清单文件注册部分
也封装了从本地获取,以及拍照获取的相关功能,可以值得学习,毕竟不少坑。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。