介绍
NoHttp的底层默认使用HttpURLConnection
实现,但是NoHttp网络层接口允许在初始化的时候配置,所以它允许无缝替换底层框架,NoHttp作者也提供了一个基于OKHttp
的底层接口实现。
官方文档http://doc.nohttp.net/
依赖
- 如果仅仅使用HttpURLConnection作为网络层,在app的gralde中添加以下依赖即可:
implementation 'com.yanzhenjie.nohttp:nohttp:1.1.1'
- 如果要使用OkHttp作为网络层,请再依赖(注意两个lib的版本需要一致):
implementation 'com.yanzhenjie.nohttp:okhttp:1.1.1'
注意:不论使用基于HttpURLConnection还是OkHttp的版本,NoHttp的使用方法都不会变,这是NoHttp的优点之一。
初始化与配置
NoHttp初始化需要一个Context,最好在Application#onCreate()
中初始化,记得在manifest.xml
中注册Application
。
Application:
package com.yanzhenjie.simple;
public class MyApplication extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
...
}
}
manifest.xml:
...
<application
android:name="com.yanzhenjie.simple.MyApplication"
...
/>
默认初始化
如果使用默认始化后,一切采用默认设置。如果你需要配置全局超时时间、缓存、Cookie、底层为OkHttp的话,请看高级初始化。
...
public class MyApplication extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
NoHttp.initialize(this); // NoHttp默认初始化。
}
}
高级初始化
超时配置
如果不设置,默认全局超时时间是10s。
NoHttp.initialize(this, new NoHttp.Config()
.setConnectTimeout(30 * 1000) // 全局连接超时时间,单位毫秒。
.setReadTimeout(30 * 1000) // 全局服务器响应超时时间,单位毫秒。
);
配置缓存
默认是开启状态,且保存在数据库。
- 设置缓存到数据库、禁用缓存
NoHttp.initialize(this, new NoHttp.Config()
...
.setCacheStore(
new DBCacheStore(this) // 配置缓存到数据库。
.setEnable(true) // true启用缓存,fasle禁用缓存。
)
);
- 设置缓存到本地SD卡
NoHttp.initialize(this, new NoHttp.Config()
...
.setCacheStore(
new DiskCacheStore(this) // 配置缓存到SD卡。
)
);
配置网络层
NoHttp的网络层是通过NetworkExecutor
接口来配置的,内部提供了一个基于HttpURLConnection
的接口实现类URLConnectionNetworkExecutor
,在NoHttp
项目中用另一个module
提供了一个基于OkHttp
的接口实现类OkHttpNetworkExecutor
,二者选其一即可,关于二者该如何使用选择请看项目如何引入NoHttp。
**值得注意的是:**切换了NoHttp的网络底层后,NoHttp的上层代码不需要任何改动,你的应用层代码也不需要任何改动。默认采用HttpURLConnection
的实现做底层,既URLConnectionNetworkExecutor
。
NoHttp.initialize(this, new NoHttp.Config()
...
.setNetworkExecutor(new URLConnectionNetworkExecutor()) // 使用HttpURLConnection做网络层。
);
如果要使用OkHttp作为网络层,请在app的gradle中添加依赖:
implementation 'com.yanzhenjie.nohttp:okhttp:1.1.11'
然后在初始化的时候这么做:
NoHttp.initialize(this, new NoHttp.Config()
...
.setNetworkExecutor(new OkHttpNetworkExecutor()) // 使用OkHttp做网络层。
);
到底该用OKHttp还是URLConnection
HttpURLConnection
是Android系统自带的api,无需依赖其它任何第三方库。
- HttpURLConnection
- 不用依赖第三方底层框架,相应的apk的体积也不会增大。
- 在5.0以下的系统中
DELETE
请求方法不允许发送body
,因此会在http协议的实现上做一些妥协。 - 在
Android4.4
以后HttpURLConnection
的底层使用OkHttp2.7.5
来实现。 - OkHttp
square
开发的第三方框架(非系统集成),相对高效、稳定。- 写文档的时候OkHttp已经更新到
3.4.1
了。 - 使用OkHttp的好处是第三方框架有bug可以改代码,不像系统集成的api没办法改动。
我个人比较推荐使用OkHttp作为NoHttp的底层,我们公司的所有项目也是用nohttp的,全都是配置okhttp为底层的。
调试模式
NoHttp的调试模式,主要是提供一个合理的日志来供开发者查看和排查错误,默认的Log的TAG是“NoHttp”字符串。NoHttp的调试模式的控制NoHttp初始化与配置一样,最好在Application#onCreate()
中设置。
package com.yanzhenjie.simple;
public class MyApplication extends android.app.Application {
@Override
public void onCreate() {
super.onCreate();
Logger.setDebug(true); // 开启NoHttp调试模式。
Logger.setTag("NoHttpSample"); // 设置NoHttp打印Log的TAG。
...
}
}
权限
因为要请求网络、监听网络状态、从SD卡读写缓存、下载文件到SD卡等等,所以需要在manifest.xml
中配置以下几个权限,如果你已经配置过了这些权限,请不要重复配置:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
请求方法
这里的请求方法指的是Http的请求方法,例如GET、POST、DELETE等。
NoHttp支持以下8种请求方法,以RequestMethod
枚举的形式给出:
public enum RequestMethod {
GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, TRACE
}
所以我们调用的时候是通过:RequestMethod.GET
这样来引用其中一个请求方法的。
指定Request的Method
请求方法需要在构造的时候指定,下面以StringRequest
举例说明:
- GET
如果不传入第二个参数,默认为GET方法,当然你也可以选择传入:
Request<String> stringReq = NoHttp.createStringRequest(url);
// 或者
Request<String> stringReq = NoHttp.createStringRequest(url , RequestMethod.GET);
- POST
Request<String> stringReq = NoHttp.createStringRequest(url , RequestMethod.POST);
同步请求
Android同步网络请求就是在当前线程发起请求。当然同步请求不能直接在主线程使用,所以一般是在子线程使用这种方法。
NoHttp的核心还是同步请求,下一章要讲的异步请求也是基于同步请求的。
这里以StringRequest
为例:
// 创建请求。
Request<String> request = NoHttp.createStringRequest(url, RequestMethod.GET);
// 调用同步请求,直接拿到请求结果。
Response<String> response = NoHttp.startRequestSync(request);
异步请求
1、创建队列(队列特性讲解点我)
RequestQueue queue = NoHttp.newRequestQueue();
2、创建请求
Request<String> request = new StringRequest(url);
// 添加url?key=value形式的参数
request.add("enName", "yanzhenjie");
request.add("zhName", "严振杰");
request.add("website", "http://www.yanzhenjie.com");
3、添加请求到队列
...
queue.add(0, request, new OnResponseListener<String>(){
@Override
public void onSucceed(int what, Response<String> response) {
if(response.responseCode() == 200) {// 请求成功。
String result = response.get();
}
}
@Override
public void onFailed(int what, Response<String> response) {
Excepition exception = response.getException();
if(exception instanceof NetworkError) {// 网络不好。
}
// 这里还有很多错误类型,可以看demo:
https://github.com/yanzhenjie/NoHttp
...
}
@Override
public void onStart(int what) {
// 这里可以show()一个wait dialog。
}
@Override
public void onFinish(int what) {
// 这里可以dismiss()上面show()的wait dialog。
}
});
这里对其中queue.add(what, request, listener)
中的what
做个说明,任意添加多个请求到队列时,使用同一个Listener接受结果,listener的任何一个方法被回调时都会返回在添加请求到队列时写的相应what值,可以用这个what来区分是哪个请求的回调结果,你可以理解为它的作用和handler
的what
一样的作用,就是用来区分信号来源的。
这样做的好处是不像其它框架一样,每个请求都new listener()
来接受结果,这样及省了代码量,又让代码更加整洁。
当然如果你不想这么用,那么你可以每个请求都new listener()
。
NoHttp示例:
效果:
app/buil.gradle:
//nohttp网络请求,默认底层基于HttpUrlConnection
implementation 'com.yanzhenjie.nohttp:nohttp:1.1.1'
//nohttp底层基于okhttp
implementation 'com.yanzhenjie.nohttp:okhttp:1.1.1'
权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
Application:
public class MyApp extends Application {
private static RequestQueue mRequestQueue;
public static RequestQueue getRequestQueue() {
return mRequestQueue;
}
@Override
public void onCreate() {
super.onCreate();
//初始化nohttp请求
NoHttp.initialize(this, new NoHttp.Config()
.setConnectTimeout(30 * 1000) // 全局连接超时时间,单位毫秒。
.setReadTimeout(30 * 1000) // 全局服务器响应超时时间,单位毫秒。
.setNetworkExecutor(new OkHttpNetworkExecutor() // 使用OkHttp做网络层。
));
Logger.setDebug(true); // 开启NoHttp调试模式。
Logger.setTag("NoHttpSample"); // 设置NoHttp打印Log的TAG。
mRequestQueue = NoHttp.newRequestQueue();
}
}
NetUtils:
public class NetUtils {
private static NetUtils mNetUtils;
private RequestQueue requestQueue;
private NetResponseListener netResponseListener;
private JsonObjectRequest request;
private NetUtils() {
requestQueue = NoHttp.newRequestQueue(8);
}
public static NetUtils getInstance() {
if (mNetUtils == null) {
synchronized (NetUtils.class) {
if (mNetUtils == null) {
mNetUtils = new NetUtils();
}
}
}
return mNetUtils;
}
public void setNetResponseListener(NetResponseListener listener) {
this.netResponseListener = listener;
}
/**
* Get请求
*
* @param what
* @param params -参数
* @param url -绝对路径
*/
public void createGetStirngRequst(int what, Map<String, Object> params, String url) {
// 请求String:
request = new JsonObjectRequest(url, RequestMethod.GET);
request.setCancelSign(url + params);
if (params != null) {
try {
request.add(params);
} catch (Exception e) {
e.printStackTrace();
}
}
requestQueue.add(what, request, new JSONObjectResponseListener());
}
/**
* Post请求
*
* @param what
* @param params -参数
* @param url -绝对路径
*/
public void createPostStirngRequst(int what, Map<String, Object> params, String url) {
request = new JsonObjectRequest(url, RequestMethod.POST);
request.setCancelSign(url + params);
if (params != null) {
try {
request.add(params);
} catch (Exception e) {
e.printStackTrace();
}
}
requestQueue.add(what, request, new JSONObjectResponseListener());
}
/**
* POST 请求
*
* @param what
* @param jsonStr 请求体为JSON字符串
* @param url
*/
public void createPostStirngRequstJson(int what, String jsonStr, String url) {
Log.e("POST", "jsonStr" + jsonStr);
request = new JsonObjectRequest(url, RequestMethod.POST);
request.setCancelSign(url);
request.setDefineRequestBodyForJson(jsonStr);
requestQueue.add(what, request, new JSONObjectResponseListener());
}
protected class JSONObjectResponseListener implements OnResponseListener<JSONObject> {
@Override
public void onStart(int what) {
if (netResponseListener != null) {
netResponseListener.onStart(what);
}
}
@Override
public void onSucceed(int what, Response<JSONObject> response) {
if (netResponseListener != null) {
Log.e("NetUtils", "请求成功:" + response.toString());
int code;
String msg = "";
if (response != null) {
code = response.get().optInt("code");
msg = response.get().optString("msg");
if (code == 0) {
netResponseListener.onSuccess(what, response.get());
}else{
netResponseListener.onFailed(what, response.responseCode(), msg);
}
}else{
netResponseListener.onFailed(what, response.responseCode(), msg);
}
}
}
@Override
public void onFailed(int what, Response<JSONObject> response) {
Log.e("NetUtils", "请求失败:" + response.toString());
netResponseListener.onFailed(what, -1, "");
}
@Override
public void onFinish(int what) {
if (netResponseListener != null && requstFinish()) {
netResponseListener.onFinish(what);
}
}
}
/**
* 网络请求是否完成
*
* @return
*/
public boolean requstFinish() {
return requestQueue.unFinishSize() == 0 || requestQueue.unStartSize() == 0;
}
}
NoHttpActivity:
public class NoHttpActivity extends AppCompatActivity {
private TextView mTishiTv;
private TextView mContentTv;
private EditText mDateEdit;
private SharedPreferences mSp;
private SharedPreferences.Editor mEdit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_no_http);
mSp = getSharedPreferences("age_sp", MODE_PRIVATE);
mEdit = mSp.edit();
initView();
}
private void initView() {
mDateEdit = findViewById(R.id.date_edit);
mContentTv = findViewById(R.id.content_tv);
mTishiTv = findViewById(R.id.tishi_tv);
String age = mSp.getString("age", "01-01");
mDateEdit.setText(age);
findViewById(R.id.query_btn_tv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (TextUtils.isEmpty(mDateEdit.getText().toString().trim())) {
Toast.makeText(NoHttpActivity.this, "还没输入年龄呢,先输一下年龄叭.....", Toast.LENGTH_SHORT).show();
return;
}
String getAge = mDateEdit.getText().toString().trim();
query(getAge);
}
});
}
/**
* 生日密码接口API
* (http://apis.juhe.cn/fapig/birthdayPassword/query)
* @param age
*/
private void query(final String age) {
String url = "http://apis.juhe.cn/fapig/birthdayPassword/query";
NetUtils mInstance = NetUtils.getInstance();
Map<String, Object> map = new HashMap<>();
map.put("keyword", age);
map.put("key", "245af794c4107807ca8d001d5b0b0285");
mInstance.createGetStirngRequst(0, map, url);
mInstance.setNetResponseListener(new NetResponseListener() {
@Override
public void onSuccess(int what, JSONObject jsonObject) {
LogUtils.d("querytag", "succeed_jsonObject:" + jsonObject.toString());
String json = jsonObject.toString();
String str1 = json.replace("<p>", "");
String str2 = str1.replace("<\\/p>", "");//反斜杠\ 的转义字符\\表示
LogUtils.d("querytag", "str2:" + str2);
BirthdayCodeBean birthdayCodeBean = new Gson().fromJson(str2, BirthdayCodeBean.class);
BirthdayCodeBean.ResultDTO result = birthdayCodeBean.getResult();
mEdit.putString("age", age);
mEdit.commit();
mContentTv.setText(result.toString());
}
@Override
public void onFinish(int what) {
LogUtils.d("querytag", "start_what:" + what);
}
@Override
public void onFailed(int what, int code, String msg) {
LogUtils.d("querytag", "failed_response:" + msg + "__what:" + what);
}
@Override
public void onStart(int what) {
LogUtils.d("querytag", "start_what:" + what);
}
});
}
LogUtils:
public class LogUtils {
private final static String APP_TAG = "appName";
public static void v(String msg) {
if (Constant.IS_PRINT_LOG) {
Log.v(APP_TAG, getMsgFormat(msg));
}
}
public static void v(String tag, String msg) {
if (Constant.IS_PRINT_LOG) {
Log.v(tag, getMsgFormat(msg));
}
}
public static void d(String msg) {
if (Constant.IS_PRINT_LOG) {
Log.d(APP_TAG, getMsgFormat(msg));
}
}
public static void d(String tag, String msg) {
if (Constant.IS_PRINT_LOG) {
Log.d(tag, getMsgFormat(msg));
}
}
public static void i(String msg) {
if (Constant.IS_PRINT_LOG) {
Log.i(APP_TAG, getMsgFormat(msg));
}
}
public static void i(String tag, String msg) {
if (Constant.IS_PRINT_LOG) {
Log.i(tag, getMsgFormat(msg));
}
}
public static void w(String msg) {
if (Constant.IS_PRINT_LOG) {
Log.w(APP_TAG, getMsgFormat(msg));
}
}
public static void w(String tag, String msg) {
if (Constant.IS_PRINT_LOG) {
Log.w(tag, getMsgFormat(msg));
}
}
public static void e(String msg) {
if (Constant.IS_PRINT_LOG) {
Log.e(APP_TAG, getMsgFormat(msg));
}
}
public static void e(String tag, String msg) {
if (Constant.IS_PRINT_LOG) {
Log.e(tag, getMsgFormat(msg));
}
}
public static void e(String tag, String msg, Throwable e) {
if (Constant.IS_PRINT_LOG) {
Log.e(tag, getMsgFormat(msg), e);
}
}
public static void e(String msg, Throwable e) {
if (Constant.IS_PRINT_LOG) {
Log.e(APP_TAG, getMsgFormat(msg), e);
}
}
private static String getMsgFormat(String msg) {
return getFunctionName() + " : " + msg;
}
/**
* 格式 Thread:线程名 类名.方法名
*
* @return
*/
private static String getFunctionName() {
StackTraceElement[] sts = Thread.currentThread().getStackTrace();
if (sts != null) {
int index = -1;
String className = "";
for (StackTraceElement st : sts) {
if (st.isNativeMethod()) {
continue;
}
if (st.getClassName().equals(Thread.class.getName())) {
continue;
}
if (st.getClassName().equals(LogUtils.class.getName())) {
continue;
}
//com.aa.bb.类名->类名
if ((index = st.getClassName().lastIndexOf(".")) != -1) {
className = st.getClassName().substring(index + 1);
}
return "Thread:" + Thread.currentThread().getName()
+ " " + className
+ "." + st.getMethodName();
}
}
return null;
}
}
Constant:
public class Constant {
//是否输出log日志
public static boolean IS_PRINT_LOG = true;
}
activity_no_http.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:orientation="vertical"
tools:context=".NoHttpActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="80dp"
android:text="输入你的生日叭,我来帮你查查你的生日密码...."
android:textColor="#03DAC6" />
<EditText
android:id="@+id/date_edit"
android:layout_width="300dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="15dp"
android:background="#6200EE"
android:hint="例如:01-01" />
<TextView
android:id="@+id/query_btn_tv"
android:layout_width="50dp"
android:layout_height="30dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:background="#EC70FA"
android:gravity="center"
android:text="查询"
android:textColor="#ffffff" />
<TextView
android:id="@+id/tishi_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="生日密码会出现在这里→"
android:textColor="#7459D3" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="none">
<TextView
android:id="@+id/content_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text=""
android:textSize="16sp"
android:textColor="#7459D3" />
</ScrollView>
</LinearLayout>
</LinearLayout>