編輯:關於Android編程
最開始使用AndroidStudio的時候,各種不適應,各種懷戀Eclipse,寫了幾千行代碼勉強熟悉了AndroidStudio後,感覺AndroidStudio不要太棒了。學習Retrofit也是這樣,遇到麻煩就想去用以前用過的框架,熟悉了過後我現在連吃個湯圓都喜歡串著吃,囧….
當然實際的項目開發中,不可能給我們時間慢慢去適應、去學習,要考慮技術風險和學習成本、團隊共用。所以我才覺得Retrofit這一點好貼心,基於OkHttp實現,也就是說我當我遇到我不知道該怎麼實現的功能的時候,可以無縫切換到OkHttp上面實現需求。
看看下面的代碼,同樣申明一個請求call
,同樣的異步call.enqueue
,同樣的回調Callback
,甚至連mDatas = new Gson().fromJson(res, new TypeToken
Gson解析都是從前的味道。但是我們已經開始在使用Retrofit了,不知不覺,潛移默化中已經前進了一小步。>
private void initBtnBasic() {
mBtnBasic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Retrofit build = new Retrofit.Builder().baseUrl("http://zhuangbi.info/").build();
Call call = build.create(SearchApi.class).basicSearch("牛逼");
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
try {
String res = response.body().string();
mDatas = new Gson().fromJson(res, new TypeToken>() {
}.getType());
for (SearchImage searchImage : mDatas) {
Log.d(TAG, "onResponse: " + searchImage.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Call call, Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
});
}
});
}
public interface SearchApi {
@GET("search")
Call basicSearch(@Query("q") String qTitle);
}
當然,100%的雷同也不好嘛,來了一趟總得學點東西,一下接觸陌生知識太多,跨步前進容易扯到蛋不可取,原地踏步也不爽,小步慢行。
這裡我們構建請求URL的事後使用了一個接口SearchApi
,
Retrofit
Retrofit.Builder().baseUrl("http://zhuangbi.info/").build().create(SearchApi.class).basicSearch("牛逼");
OkHttp
new Request.Builder()
.url("http://zhuangbi.info/search?q=" + getString("牛逼"))
.build();
聰明的我們一看就知道
@GET("search")
Call basicSearch(@Query("q") String qTitle);
這裡的@GET("search")
表示使用get請求,同時search是get命令路徑的一部分
當然,你也可以把他寫到參數裡面,Like This
@GET("{path}")
Call basicSearch(@Path("path") String path, @Query("q") String qTitle);
這樣用就行了:
Retrofit.Builder().baseUrl("http://zhuangbi.info/").build().create(SearchApi.class).basicSearch("search","牛逼");
@Query("q") String qTitle
Query是查詢參數,就相當於http://zhuangbi.info/search?q=牛逼
的
?q=牛逼
部分
也許你會想如果要查詢很多參數,豈不是也要配置很多函數參數裡面,然後讓函數變得巨長
當然不用@QueryMap
為你排憂解難
@GET("search")
Call basicSearch(@QueryMap Map map);
就這麼簡單,輕松進入到Retrofit的地盤
private void initTrans() {
final Retrofit build = new Retrofit.Builder()
.baseUrl("http://zhuangbi.info/")
.addConverterFactory(GsonConverterFactory.create())//添加轉換器
.build();
mBtnTrans.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Call> call = build.create(SearchApi.class).transSearch("牛逼");
call.enqueue(new Callback>() {
@Override
public void onResponse(Call> call, Response> response) {
mDatas.clear();
//使用轉換器,內部已經幫我們實現轉換,省去了new Gson().form(jsonStr,new TypeToken<>())的過程
mDatas = response.body();
for (SearchImage searchImage : mDatas) {
Log.d(TAG, "onResponse: " + searchImage.toString());
}
}
@Override
public void onFailure(Call> call, Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
});
}
});
}
所謂轉換器,其實再剛才的代碼基礎上也沒多大改動嘛,不過是再構造的時候加了一句.addConverterFactory(GsonConverterFactory.create())//添加轉換器
Callback裡面的json轉換不用再使用
Gson gson = new Gson();
mDatas = gson.fromJson(resStr, new TypeToken>() {
}.getType());
直接使用mDatas = response.body();
就可以獲取,當然請求的函數泛型也要改寫
@GET("search")
Call> transSearch(@Query("q") String qTitle);
省去了在回調函數new Gson的干擾,代碼變得跟簡潔。
當然,這裡使用的是Gson,如果你需要JackSon,FastJson選擇合適的解析器傳遞進去就行了。
如果,你需要自定義解析規則,也是沒問題的,這需要自己去實現,但是這不是我們今天要說的重點,放到後面再寫一個這種例子。
總之—使用了解析器,讓我們的代碼可讀性變得更好,代碼也更簡潔。
private void initAdapt() {
final Retrofit build = new Retrofit.Builder()
.baseUrl("http://zhuangbi.info/")
.addConverterFactory(GsonConverterFactory.create())//添加轉換器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加適配器 連接RxJava
.build();
//使用RxBinding處理點擊事件 RxBinding基於RxJava實現
RxView.clicks(mBtnAdapter)
.debounce(500, TimeUnit.MILLISECONDS) //排除500ms內重復點擊事件
.observeOn(AndroidSchedulers.mainThread())//切換到主線程執行響應內容,相當於在回調方法中執行runOnUiThread方法
.subscribe(new Action1() {
@Override
public void call(Void aVoid) {
//不需要Call了,直接使用Observable,順滑的切換到RxJava領域
Observable> observable = build.create(SearchApi.class)
.search("牛逼")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
//這裡主要是為了演示,代碼簡潔,實際中不要這樣,
// 還要考慮取消一個請求,返回錯誤值處理,業務需求等等
observable.subscribe(new Action1>() {
@Override
public void call(List searchImages) {
for (SearchImage searchImage : searchImages) {
Log.d(TAG, "call: " + searchImage.toString());
}
}
});
}
});
}
和解析器一樣,適配器也就是加了一句.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加適配器 連接
代碼雖然簡潔,作用巨大,注意現在我們已經不用Call來做請求了,我們使用Observable
,這直接就可以利用RxJava操作。
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
2.以前的回調Callback
變成了監聽者處理事件observable.subscribe
,有更多
Action1
Subscription
方式讓我們選擇
3.鏈式調用,整個邏輯清晰明了
但是注意一點:
對API調用了 observeOn(MainThread) 之後,線程會跑在主線程上,包括 onComplete 也是, unsubscribe 也在主線程,然後如果這時候調用 call.cancel
會導致 NetworkOnMainThreadException ,所以一定要在 retrofit 的API調用.subscribeOn(io).observeOn(MainThread)
之後加一句 unsubscribeOn(io)
完整的就是
Api
.subscribeOn(io)
.observeOn(MainThread)
.unsubscribeOn(io)
哦,這裡取消一個命令和call的方式不同,用的是unsubscribeOn(io)
,Call對應的方法是cancel()
這裡還加入了一點小元素,注意我們使用的是
RxView.clicks(mBtnAdapter)
.debounce(500, TimeUnit.MILLISECONDS) //排除500ms內重復點擊事件
.observeOn(AndroidSchedulers.mainThread())//切換到主線程執行響應內容,相當於在回調方法中執行runOnUiThread方法
.subscribe(new Action1() {
@Override
public void call(Void aVoid) {
取代
mBtnTrans.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {}
這是使用了RxBinding這個庫,基於RxJava實現,看看用著這種方式實現方式對比傳統方式在上述代碼表達出來的,線程切換、防止重復點擊的實現優勢
我寫了,快一周也沒有找到他的,請求方式,請求頭在哪兒打印,一直好慌。知道再OkHttp的Github上看到HttpLoggingInterceptor
這個庫。
使用方式:導入
compile 'com.squareup.okhttp3:logging-interceptor:3.3.1'
private void initLog() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS);
//自定義攔截方式
HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.v("Retrofit", message);
}
}).setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(logger).build();
final Retrofit build = new Retrofit.Builder()
.baseUrl("http://zhuangbi.info/")
.addConverterFactory(GsonConverterFactory.create())//添加轉換器
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加適配器 連接RxJava
.client(httpClient)
.build();
RxView.clicks(mBtnLog)
.debounce(300, TimeUnit.MILLISECONDS)
.subscribe(new Action1() {
@Override
public void call(Void aVoid) {
//不需要Call了,直接使用Observable,順滑的切換到RxJava領域
Observable> observable = build.create(SearchApi.class)
.search("牛逼")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
//這裡主要是為了演示,代碼簡潔,實際中不要這樣,
// 還要考慮取消一個請求,返回錯誤值處理,業務需求等等
observable.subscribe(new Action1>() {
@Override
public void call(List searchImages) {
for (SearchImage searchImage : searchImages) {
Log.d(TAG, "call: " + searchImage.toString());
}
}
});
}
}
);
}
有4個請求層次,一般BASIC和HEADER用得比較多
public enum Level {
/** No logs. */
NONE,
/**
* Logs request and response lines.
*
*
Example: *
{@code
* --> POST /greeting http/1.1 (3-byte body)
*
* <-- 200 OK (22ms, 6-byte body)
* }
*/ BASIC, /** * Logs request and response lines and their respective headers. * *
Example: *
{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
* --> END POST
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
* <-- END HTTP
* }
*/ HEADERS, /** * Logs request and response lines and their respective headers and bodies (if present). * *
Example: *
{@code
* --> POST /greeting http/1.1
* Host: example.com
* Content-Type: plain/text
* Content-Length: 3
*
* Hi?
* --> END GET
*
* <-- 200 OK (22ms)
* Content-Type: plain/text
* Content-Length: 6
*
* Hello!
* <-- END HTTP
* }
*/ BODY }
還可以通過自定義HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger()
個性化打造自己的網絡數據請求打印。
把攔截器,進一步升華,比如下載數據的時候,攔截下Response,讀取裡面的信息,然後再傳輸給下一層,不就可以實現監聽下行數據麼。
先實現一個監聽器
public interface ProcessListener {
/**
* @param current 已完成字節數
* @param total 總字節數
* @param isCompleted 是否已經完成
*/
void onProcess(long current, long total, boolean isCompleted);
}
實現一個具備攔截能力的Response類
public class ProcessResponseBody extends ResponseBody {
private ProcessListener mProcessListener;
private ResponseBody mResponseBody;
private BufferedSource mBufferedSource;
public ProcessResponseBody(ResponseBody responseBody, ProcessListener processListener) {
mProcessListener = processListener;
mResponseBody = responseBody;
}
@Override
public MediaType contentType() {
return mResponseBody.contentType();
}
@Override
public long contentLength() {
return mResponseBody.contentLength();
}
@Override
public BufferedSource source() {
if (mBufferedSource == null) {
mBufferedSource = Okio.buffer(source(mResponseBody.source()));
}
return mBufferedSource;
}
private Source source(Source source) {
return new ForwardingSource(source) {
long nowSize = 0L;
@Override
public long read(Buffer sink, long byteCount) throws IOException {
long readSize = super.read(sink, byteCount);
nowSize += (readSize != -1 ? readSize : 0);
mProcessListener.onProcess(nowSize, mResponseBody.contentLength(), readSize == -1);
return readSize;
}
};
}
}
調用
final OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response oldResponse = chain.proceed(chain.request());
return oldResponse.newBuilder()
.body(new ProcessResponseBody(oldResponse.body(), new ProcessListener() {
@Override
public void onProcess(long current, long total, boolean isCompleted) {
Log.d(TAG, "onProcess: " + current + "/" + total + "---" + isCompleted);
}
}))
.build();
}
}).build();
查看Log
上傳和下行,同理,實現一個具備攔截能力的Request即可
太快了,模擬器裡面沒有大一點的圖片,本來是有進度條顯示,看下日志吧
實現代碼:
public class ProcessRequestBody extends RequestBody {
private ProcessListener mProcessListener;
private RequestBody mRequestBody;
private BufferedSink mBufferedSink;
public ProcessRequestBody(RequestBody requestBody, ProcessListener processListener) {
mProcessListener = processListener;
mRequestBody = requestBody;
}
@Override
public long contentLength() throws IOException {
return mRequestBody.contentLength();
}
@Override
public MediaType contentType() {
return mRequestBody.contentType();
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
if (mBufferedSink == null) {
mBufferedSink = Okio.buffer(sink(sink));
}
mRequestBody.writeTo(mBufferedSink);
mBufferedSink.flush();
}
private Sink sink(Sink sink) {
return new ForwardingSink(sink) {
long writenBytes = 0L;
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
writenBytes += byteCount;
mProcessListener.onProcess(writenBytes, mRequestBody.contentLength(), writenBytes == mRequestBody.contentLength());
}
};
}
}
調用
final OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
RequestBody requestBody = new ProcessRequestBody(chain.request().body(), new ProcessListener() {
@Override
public void onProcess(final long current, final long total, final boolean isCompleted) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mDialog.show();
mDialog.setProgress((int) (current * 100 / total));
if (isCompleted) {
mDialog.dismiss();
}
}
});
Log.d(TAG, "onProcess: " + current + "/" + total + isCompleted);
}
});
Request newRequest = chain.request().newBuilder().post(requestBody).build();
return chain.proceed(newRequest);
}
})
.build();
源碼下載地址:https://github.com/zhouruikevin/RxJavaSamples-master
簡單做了個相機和圖片編輯模塊,時間原因很多功能還沒有做,尚有BUG,見諒,將在以後抽時間修改代碼地址PS:請點個Star^-^----------------------
Android PullZoomView是github上面的一個第三方開源項目,該項目實現的功能被新浪微博的移動端廣泛使用,其效果就是,當用戶在下拉過程中,頭部的圖片會有
傳統的短信已經逐漸被各種微信、QQ諸如此類的社交軟件所取代,我們能用到的短信最大的功能也無外乎是驗證碼、充值或是購票等等。不過華為榮耀Note8的推出,卻改
1.什麼是類加載器?類加載器(class loader)是 Java?中的一個很重要的概念。類加載器負責加載 Java 類的字節代碼到 Java 虛擬機中。Java 虛擬