在我最近一年接触的项目中,一直都会接触到一个功能就是图片的拍摄与选取,对于Android系统,各个厂家对于手机系统都会有不同的功能定制,导致碎片化一直很严重,写出的同一套程序,也许两个不同的手机上都会有不同的效果,也许有的手机上根本无反应,这一直是需要解决的问题。
TakePhoto简介
我在做这一功能时候,一直都是用的github上的一框架TakePhoto,这框架对于android的拍照和图片的选取都做了一层封装,并且兼容低版本系统,6.0和7.0的系统,可以实现图片的拍摄,单选,多选,压缩和裁剪功能。官网上的最新功能是4.0.3,下图是它的支持功能
TakePhoto的使用
基本使用就不多说了,官网上有使用,针对这一功能,重新封装了一个BaseTakePhotoActivity
, 用户一般只需要将其继承自自己的BaseActivity
就可以了。
public abstract class BaseTakePhotoActivity extends BaseActivity implements TakePhoto.TakeResultListener,
InvokeListener {
protected TakePhoto takePhoto;
protected InvokeParam invokeParam;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getTakePhoto().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
getTakePhoto().onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
getTakePhoto().onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public PermissionManager.TPermissionType invoke(InvokeParam invokeParam) {
PermissionManager.TPermissionType type= PermissionManager.checkPermission(TContextWrap.of(this),invokeParam.getMethod());
if(PermissionManager.TPermissionType.WAIT.equals(type)){
this.invokeParam=invokeParam;
}
return type;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionManager.TPermissionType type= PermissionManager.onRequestPermissionsResult(requestCode,permissions,grantResults);
PermissionManager.handlePermissionsResult(this,type,invokeParam,this);
}
/**
* 获取TakePhoto实例
* @return
*/
public TakePhoto getTakePhoto(){
if (takePhoto==null){
takePhoto= (TakePhoto) TakePhotoInvocationHandler.of(this).bind(new TakePhotoImpl(this,this));
}
return takePhoto;
}
protected void configTakePhotoOption(TakePhoto takePhoto) {
TakePhotoOptions.Builder builder = new TakePhotoOptions.Builder();
builder.setWithOwnGallery(false);
builder.setCorrectImage(true);
takePhoto.setTakePhotoOptions(builder.create());
}
protected void configCompress(TakePhoto takePhoto){
int maxSize = 1048576;
boolean showProgressBar = false;
boolean enableRawFile = true;
CompressConfig config;
LubanOptions option = new LubanOptions.Builder()
.setMaxSize(maxSize)
.create();
config = CompressConfig.ofLuban(option);
config.enableReserveRaw(enableRawFile);
takePhoto.onEnableCompress(config, showProgressBar);
}
@Override
public void takeSuccess(TResult result) {
}
@Override
public void takeFail(TResult result, String msg) {
ToastUtils.showShort(msg);
}
@Override
public void takeCancel() {
ToastUtils.showShort("用户取消");
}
}
源码调用过程
针对源码的调用过程,画了一个简易流程的调用过程
这是针对onPickMultiple
画的一个流程图,接下来详细介绍下其内部调用过程。
先从takephoto
说起,我们在调用它的api时候首先需要实例化takephoto
,看BaseTakePhotoActivity
的
/**
* 获取TakePhoto实例
* @return
*/
public TakePhoto getTakePhoto(){
if (takePhoto==null){
takePhoto= (TakePhoto) TakePhotoInvocationHandler.of(this).bind(new TakePhotoImpl(this,this));
}
return takePhoto;
}
takephoto
的实例化是通过TakePhotoInvocationHandler
获取,在TakePhotoInvocationHandler
中绑定一个TakePhotoImpl
/**
* 绑定委托对象并返回一个代理类
* @param delegate
* @return
*/
public Object bind(TakePhoto delegate) {
this.delegate = delegate;
return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this);
}
从这里可以看出我们获取的takephoto
只是一个代理对象,所有我们调用的takephoto
的api都会走TakePhotoInvocationHandler
的invoke
方法,看下其实现
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
PermissionManager.TPermissionType type=listener.invoke(new InvokeParam(proxy,method,args));
if(proxy instanceof TakePhoto){
if(!PermissionManager.TPermissionType.NOT_NEED.equals(type)){
((TakePhoto)proxy).permissionNotify(type);
}
}
return method.invoke(delegate, args);
}
当在走这个方法的时候,首先会调listener.invoke(new InvokeParam(proxy,method,args))
方法,并且将参数封装到InvokeParam
里面,并传给listener.invoke
方法,其中listener.invoke
方法,我们在BaseTakePhotoActivity
,中已经实现,看下实现
@Override
public PermissionManager.TPermissionType invoke(InvokeParam invokeParam) {
PermissionManager.TPermissionType type= PermissionManager.checkPermission(TContextWrap.of(this),invokeParam.getMethod());
if(PermissionManager.TPermissionType.WAIT.equals(type)){
this.invokeParam=invokeParam;
}
return type;
}
在该方法中首先调用PermissionManager
的checkPermission
方法
public static TPermissionType checkPermission(@NonNull TContextWrap contextWrap, @NonNull Method method) {
String methodName = method.getName();
boolean contain=false;
for(int i=0,j=methodNames.length;i<j;i++){
if(TextUtils.equals(methodName,methodNames[i])){
contain=true;
break;
}
}
if(!contain)return TPermissionType.NOT_NEED;
boolean cameraGranted = true, storageGranted = ContextCompat.checkSelfPermission(contextWrap.getActivity(), TPermission.STORAGE.stringValue()) == PackageManager.PERMISSION_GRANTED ? true : false;
if (TextUtils.equals(methodName, "onPickFromCapture") || TextUtils.equals(methodName, "onPickFromCaptureWithCrop")) {
cameraGranted = ContextCompat.checkSelfPermission(contextWrap.getActivity(), TPermission.CAMERA.stringValue()) == PackageManager.PERMISSION_GRANTED ? true : false;
}
boolean granted = storageGranted && cameraGranted;
if (!granted) {
ArrayList<String> permissions = new ArrayList<>();
if (!storageGranted) permissions.add(TPermission.STORAGE.stringValue());
if (!cameraGranted) permissions.add(TPermission.CAMERA.stringValue());
requestPermission(contextWrap,permissions.toArray(new String[permissions.size()]));
}
return granted ? TPermissionType.GRANTED : TPermissionType.WAIT;
}
在该方法中首先会去检测存储权限,接着如果是onPickFromCapture
或者onPickFromCaptureWithCrop
,回去检测是否有相机权限,如果那个没有就去申请那个的权限,最后根据是否有权限将,TPermissionType
的状态改为GRANTED
或者WAIT
状态,如果已经有权限直接调TakePhotoInvocationHandler.invoke
的最后method.invoke
方法,也就是调takephoto
的api方法。如果在申请权限,就会回调BaseTakePhotoActivity.onRequestPermissionsResult
方法,该方法中会根据用户是否同意或拒绝权限来是否调用的TakePhoto
的相应方法。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionManager.TPermissionType type= PermissionManager.onRequestPermissionsResult(requestCode,permissions,grantResults);
PermissionManager.handlePermissionsResult(this,type,invokeParam,this);
}
在该方法中根据用户是否同意权限,将结果封装到TPermissionType
中,然后调用PermissionManager
的handlePermissionsResult
方法
public static void handlePermissionsResult(Activity activity, TPermissionType type, InvokeParam invokeParam, TakePhoto.TakeResultListener listener){
String tip=null;
switch (type){
case DENIED:
listener.takeFail(null,tip=activity.getResources().getString(R.string.tip_permission_camera_storage));
break;
case ONLY_CAMERA_DENIED:
listener.takeFail(null,tip=activity.getResources().getString(R.string.tip_permission_camera));
break;
case ONLY_STORAGE_DENIED:
listener.takeFail(null,tip=activity.getResources().getString(R.string.tip_permission_storage));
break;
case GRANTED:
try {
invokeParam.getMethod().invoke(invokeParam.getProxy(),invokeParam.getArgs());
} catch (Exception e) {
e.printStackTrace();
listener.takeFail(null,tip=activity.getResources().getString(R.string.tip_permission_camera_storage));
}
break;
default:
break;
}
if(tip!=null)Toast.makeText(activity,tip,Toast.LENGTH_LONG).show();
}
在该方法中,通过TPermissionType
方法回调处理相应的结果,如果是同意,通过反射去调用takephoto
的方法,有一点是需要提示一下的,就是我们在调用takephoto
的方法时候,
this.fromType = TImage.FromType.CAMERA;
if (PermissionManager.TPermissionType.WAIT.equals(permissionType)) return;
都会有这样的判断,这个判断是给6.0运行时权限设计的,如果在申请权限的时候,对应的方法不是由takephoto
调用,而是由PermissionManager
的handlePermissionsResult
方法反射调用,这是一种非常值得借鉴的思想。
总结
基本流程就这多了,看了它的源码过程,自己也有了一定的架构思路,对于不同模块最好规范一个接口,模块之间可以通过动态代理连接,这样可以自己干自己的事,互不干扰。
这一篇介绍了流程,下一篇打算根据各个详细的调用过程来写几篇文章。