Android第三方网络请求(Retrofit)、权限请求(Permission4M)框架
1.网络请求框架
Volley、OkHttp或xUtils?
Retrofit或者Volley+OkHttp
参考意见:
公司项目一直在使用xutils发现在网络请求时存在一些bug,调研发下xutils这种聚合型框架一定要放弃,可读性差,耦合高,难扩展,不利于以后代码的迭代和错误纠正,现在重新调研选择了发现,Retrofit或者Volley+OkHttp。
网络请求框架总结
1.xutils
此框架庞大而周全,这个框架可以网络请求,同时可以图片加载,又可以数据存储,又可以 View 注解,使用这种框架很方便,这样会使得你整个项目对它依赖性太强,万一以后这个库不维护了,或者中间某个模块出问题了,这个影响非常大,所以在项目开发时,一般会更喜欢选择专注某一领域的框架。
2.OkHttp
Android 开发中是可以直接使用现成的api进行网络请求的,就是使用HttpClient、HttpUrlConnection 进行操作,目前HttpClient 已经被废弃,而 android-async-http 是基于HttpClient的,可能也是因为这个原因作者放弃维护。 而OkHttp是Square公司开源的针对Java和Android程序,封装的一个高性能http请求库,它的职责跟HttpUrlConnection 是一样的,支持 spdy、http 2.0、websocket ,支持同步、异步,而且 OkHttp 又封装了线程池,封装了数据转换,封装了参数使用、错误处理等,api使用起来更加方便。可以把它理解成是一个封装之后的类似HttpUrlConnection的东西,但是在使用的时候仍然需要自己再做一层封装,这样才能像使用一个框架一样更加顺手。
3.Volley
Volley是Google官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持HttpClient、HttpUrlConnection, 甚至支持OkHttp,而且Volley里面也封装了ImageLoader,所以如果你愿意你甚至不需要使用图片加载框架,不过这块功能没有一些专门的图片加载框架强大,对于简单的需求可以使用,稍复杂点的需求还是需要用到专门的图片加载框架。Volley也有缺陷,比如不支持post大数据,所以不适合上传文件。不过Volley设计的初衷本身也就是为频繁的、数据量小的网络请求而生。
4.Retrofit
Retrofit是Square公司出品的默认基于OkHttp封装的一套RESTful网络请求框架,RESTful是目前流行的一套api设计的风格, 并不是标准。Retrofit的封装可以说是很强大,里面涉及到一堆的设计模式,可以通过注解直接配置请求,可以使用不同的http客户端,虽然默认是用http ,可以使用不同Json Converter 来序列化数据,同时提供对RxJava的支持,使用Retrofit + OkHttp + RxJava + Dagger2 可以说是目前比较潮的一套框架,但是需要有比较高的门槛。
5.Volley VS OkHttp
Volley的优势在于封装的更好,而使用OkHttp你需要有足够的能力再进行一次封装。而OkHttp的优势在于性能更高,因为 OkHttp基于NIO和Okio ,所以性能上要比 Volley更快。IO 和 NIO这两个都是Java中的概念,如果我从硬盘读取数据,第一种方式就是程序一直等,数据读完后才能继续操作这种是最简单的也叫阻塞式IO,还有一种是你读你的,程序接着往下执行,等数据处理完你再来通知我,然后再处理回调。而第二种就是 NIO 的方式,非阻塞式, 所以NIO当然要比IO的性能要好了,而 Okio是 Square 公司基于IO和NIO基础上做的一个更简单、高效处理数据流的一个库。理论上如果Volley和OkHttp对比的话,更倾向于使用 Volley,因为Volley内部同样支持使用OkHttp,这点OkHttp的性能优势就没了, 而且 Volley 本身封装的也更易用,扩展性更好些。
6.OkHttp VS Retrofit
毫无疑问,Retrofit 默认是基于 OkHttp 而做的封装,这点来说没有可比性,肯定首选 Retrofit。
7.Volley VS Retrofit
这两个库都做了不错的封装,但Retrofit解耦的更彻底,尤其Retrofit2.0出来,Jake对之前1.0设计不合理的地方做了大量重构, 职责更细分,而且Retrofit默认使用OkHttp,性能上也要比Volley占优势,再有如果你的项目如果采用了RxJava ,那更该使用 Retrofit 。所以这两个库相比,Retrofit更有优势,在能掌握两个框架的前提下该优先使用 Retrofit。但是Retrofit门槛要比Volley稍高些, 要理解他的原理,各种用法,想彻底搞明白还是需要花些功夫的,如果你对它一知半解,那还是建议在商业项目使用Volley吧。
8.总结
综上,如果以上三种网络库你都能熟练掌握,那么优先推荐使用Retrofit,前提是最好你们的后台api也能遵循RESTful的风格, 其次如果不想使用或者没能力掌握Retrofit ,那么推荐使用Volley ,毕竟Volley不需要做过多的封装,如果需要上传大数据, 那么不建议使用 Volley,该采用 OkHttp 。
2.权限请求框架Permission4M使用
Gradle 依赖
project 中的 build.gradle:
allprojects {
repositories {
// 请添加如下一行
maven { url 'https://jitpack.io' }
}
}
app 中的 build.gradle:
dependencies {
compile 'com.github.jokermonn:permissions4m:2.1.2-lib'
annotationProcessor 'com.github.jokermonn:permissions4m:2.1.2-processor'
}
Activity、Fragment 中使用
Activity、Fragment中注册回调,并实现回调接口的三个方法permissionGranted(int i)、permissionDenied(int i)、permissionRationale(int i)
Permissions4M.get(QhVoiceRecorder.this)
// 是否强制弹出权限申请对话框,建议设置为 true,默认为 true
// .requestForce(true)
// 是否支持 5.0 权限申请,默认为 false
// .requestUnderM(false)
// 权限,单权限申请仅只能填入一个
.requestPermissions(Manifest.permission.RECORD_AUDIO)
// 权限码
.requestCodes(CODE_AUDIO)
// 如果需要使用 @PermissionNonRationale 注解的话,建议添加如下一行
// 返回的 intent 是跳转至**系统设置页面**
// .requestPageType(Permissions4M.PageType.MANAGER_PAGE)
// 返回的 intent 是跳转至**手机管家页面**
// .requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
.requestListener(this) //注册回调
.request();
@Override
public void permissionGranted(int i)
{
hasAudioPermission = true;
UIUtils.ShowToast(getActivity(), "权限申请成功!");
isRequesting = false;
}
@Override
public void permissionDenied(int i)
{
hasAudioPermission = false;
AlertPermissionDenied();
isRequesting = false;
}
@Override
public void permissionRationale(int i)
{
UIUtils.ShowToast(getActivity(), "请开启录音权限!");
}
必需的二次权限申请回调
在 Activity 或 Fragment 中,需要手动添加 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) 方法以支持权限申请回调
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
grantResults) {
Permissions4M.onRequestPermissionsResult(MainFragment.this, requestCode, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
附:多个权限同步申请
Permissions4M.get(NormalFragment.this)
.requestPermissions(Manifest.permission.BODY_SENSORS, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CALENDAR)
.requestCodes(SENSORS_CODE, LOCATION_CODE, CALENDAR_CODE)
.requestListener(new Wrapper.PermissionRequestListener() {
@Override
public void permissionGranted(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("地理位置权限授权成功 in fragment with annotation");
break;
case SENSORS_CODE:
ToastUtil.show("传感器权限授权成功 in fragment with annotation");
break;
case CALENDAR_CODE:
ToastUtil.show("读取日历权限授权成功 in fragment with annotation");
break;
default:
break;
}
}
@Override
public void permissionDenied(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("地理位置权限授权失败 in fragment with annotation");
break;
case SENSORS_CODE:
ToastUtil.show("传感器权限授权失败 in fragment with annotation");
break;
case CALENDAR_CODE:
ToastUtil.show("读取日历权限授权失败 in fragment with annotation");
break;
default:
break;
}
}
@Override
public void permissionRationale(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("请开启地理位置权限 in fragment with annotation");
break;
case SENSORS_CODE:
ToastUtil.show("请开启传感器权限 in fragment with annotation");
break;
case CALENDAR_CODE:
ToastUtil.show("请开启读取日历权限 in fragment with annotation");
break;
default:
break;
}
}
})
.requestCustomRationaleListener(new Wrapper.PermissionCustomRationaleListener() {
@Override
public void permissionCustomRationale(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("请开启地理位置权限 in fragment with annotation");
Log.e("TAG", "permissionRationale: 请开启地理位置权限 ");
new AlertDialog.Builder(getActivity())
.setMessage("地理位置权限权限申请:\n我们需要您开启地理位置权限(in fragment with " +
"annotation)")
.setPositiveButton("确定", new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(NormalFragment.this)
.requestOnRationale()
.requestPermissions(Manifest.permission
.ACCESS_FINE_LOCATION)
.requestCodes(LOCATION_CODE)
.request();
}
})
.show();
break;
case SENSORS_CODE:
ToastUtil.show("请开启传感器权限 in fragment with annotation");
Log.e("TAG", "permissionRationale: 请开启传感器权限 ");
new AlertDialog.Builder(getActivity())
.setMessage("传感器权限申请:\n我们需要您开启传感器权限(in fragment with " +
"annotation)")
.setPositiveButton("确定", new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(NormalFragment.this)
.requestOnRationale()
.requestPermissions(Manifest.permission
.BODY_SENSORS)
.requestCodes(SENSORS_CODE)
.request();
}
})
.show();
break;
case CALENDAR_CODE:
ToastUtil.show("请开启读取日历权限 in fragment with annotation");
Log.e("TAG", "permissionRationale: 请开启读取日历权限 ");
new AlertDialog.Builder(getActivity())
.setMessage("日历权限申请:\n我们需要您开启日历权限(in fragment with " +
"annotation)")
.setPositiveButton("确定", new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(NormalFragment.this)
.requestOnRationale()
.requestPermissions(Manifest.permission
.READ_CALENDAR)
.requestCodes(CALENDAR_CODE)
.request();
}
})
.show();
break;
default:
break;
}
}
})
.request();