編輯:關於Android編程
此篇博客,我們通過2種方式來了解下okhttp的文件上傳至服務器
ps一下,還有一種就是添加params參數,生成post提交時的分塊request(這裡就不列出實例效果了,但是封裝代碼博文後附加)
同步上傳
異步上傳
還有一種就是添加params參數,生成post提交時的分塊request
在說明同步、異步上傳代碼前,我們先來了解下上傳圖片文件的大概思路:
我這裡貼出一個截圖,就很簡單明了了,截圖如下:
截圖的意思就是將圖片壓縮成byte[]字節數組,然後通過Base64.encodeToString(bitmap2Bytes, Base64.DEFAULT)將字節數組,轉為64位的字符串,然後在封裝請求體,傳給服務器即可
了解完上傳圖片文件的大概思路後, 我們來看下我們代碼是如何實現的?
我公司請求是用的https,我這個例子,就沒有去https認證證書,我就直接https 不驗證證書方式(信任所有證書)
在SaflyApplication中進行如下代碼設置:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
public static OkHttpClient getUnsafeOkHttpClient() {
try {
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
}
};
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory);
builder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
OkHttpClient okHttpClient = builder.connectTimeout(60, TimeUnit.SECONDS).writeTimeout(60, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build();
return okHttpClient;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
樓上的代碼,也是我找的度娘搜索的,下面這段代碼就初始化了一個OkHttpClient對象,設置了連接時間,讀取時間等信息
OkHttpClient okHttpClient = builder.connectTimeout(60, TimeUnit.SECONDS).writeTimeout(60, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build();
我們在OkHttpManger進行
private Handler okHttpHandler; private OkHttpClient mOkHttpClient; private OkHttpManger(){ this.mOkHttpClient = SaflyApplication.getInstance().getUnsafeOkHttpClient(); this.okHttpHandler = new Handler(Looper.getMainLooper()); } public static final OkHttpManger getInstance(){ return SingleFactory.manger; } private static final class SingleFactory{ private static final OkHttpManger manger = new OkHttpManger(); }
構造一個單利模式,然後為了線程之間通訊,我們還初始化了一個handler,用來進行ui線程的頁面更新操作
我們先來看看同步上傳頭像代碼
Response response = OkHttpManger.getInstance().postSyncJson("https://10.0.5.48:7771/SetVPHUserInfo", Json);
public Response postSyncJson(String url, String json) throws IOException { final RequestBody requestBody = RequestBody.create(JSON_TYPE, json); final Request request = new Request.Builder().url(url).post(requestBody).build(); return mOkHttpClient.newCall(request).execute(); }
本例中我構造json字符串是用的JSONObject的方式,
如果對如何構建Json字符串還不是很明白的,可以看我如下的博客文章:
fastjson–Gson生成Json字符串,並且解析Json字符串生成對象
JsonObject生成Json字符串,並且解析為對象—–JsonReader解析Json字符串
另外還有一種方式,就是自定義拼接的方式(我公司就是用的這樣的方法,這裡就不贅述了)
針對本例的上傳服務端的json格式,我們用JSONObject如下方法即可生成所需要的json字符串
private String createJson() throws JSONException { JSONObject jsonObject = new JSONObject(); JSONObject body = new JSONObject(); body.put("photo_type", "jpeg"); body.put("nickname", "hehe"); body.put("context", Base64.encodeToString(bitmap2Bytes, Base64.DEFAULT)); JSONObject head = new JSONObject(); head.put("godin_id", "0e6ad2a71e31752deb69d6e8c9eabe76"); head.put("app_type", "3"); head.put("os_type", "android"); JSONObject Request = new JSONObject(); Request.put("body", body); Request.put("head", head); jsonObject.put("Request", Request); return jsonObject.toString(); }
然後在response.isSuccessful()進行判斷返回結果就可以了
由於我是直接訪問的公司上傳頭像的接口,godin_id字段是寫死的,服務端沒有注冊的記錄,所以就返回auth error的錯誤,不過我問了服務端的同事,已經收到該請求,通訊成功
我們在來看看異步上傳頭像代碼
OkHttpManger.getInstance().postAsyncJsonn("https://10.0.5.48:7771/SetVPHUserInfo", Json, new OkHttpManger.MyCallback()
public void postAsyncJsonn(String url, String json, MyCallback mCallback) throws IOException { final RequestBody requestBody = RequestBody.create(JSON_TYPE, json); final Request request = new Request.Builder().url(url).post(requestBody).build(); deliveryResult(mOkHttpClient.newCall(request),mCallback); }
然後進行接口回調
private void deliveryResult(final Call call, final MyCallback mCallback) { call.enqueue(new Callback() { @Override public void onFailure(final Call call, final IOException e) { okHttpHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onFailture(); } } }); } @Override public void onResponse(Call call, final Response response) throws IOException { final String responseStr = response.body().string(); okHttpHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onSuccess(responseStr); } } }); } }); }
然後截圖是一樣的,也是可以通訊成功的。
以上就是同步、異步2種方式,上傳頭像至服務器
接下來我們看看另外一個方式,不過這個方法我沒有去實踐代碼,不過有代碼封裝,以下就是代碼
/** * 同步基於post的文件上傳 * @param url 地址 * @param files 提交的文件數組 * @param fileKeys 提交的文件數組key * @param params 提交的鍵值對 * @return Response */ public Response uploadSync(String url, File[] files, String[] fileKeys, Param[] params) throws IOException { final RequestBody requestBody = buildMultipartFormRequestBody(files, fileKeys, params); final Request request = new Request.Builder().url(url).post(requestBody).build(); return mOkHttpClient.newCall(request).execute(); } /** * 異步基於post的文件上傳,回傳上傳進度 * @param url 地址 * @param files 提交的文件數組 * @param fileKeys 提交的文件數組key * @param params 提交的鍵值對 */ public void uploadAsync(String url, File[] files, String[] fileKeys, final OKHttpUICallback.ProgressCallback uploadListener, Param[] params) throws IOException { final RequestBody requestBody = buildMultipartFormRequestBody(files, fileKeys, params); final Request request = new Request.Builder().url(url).post(new ProgressBody.ProgressRequestBody(requestBody, uploadListener, okHttpHandler)).build(); mOkHttpClient.newCall(request).enqueue(new OKHttpThreadCallback(okHttpHandler, uploadListener, false)); }
生成post提交時的分塊request
private RequestBody buildMultipartFormRequestBody(File[] files, String[] fileKeys, Param[] params){ if(params == null){ params = new Param[0]; } MultipartBody.Builder builder = new MultipartBody.Builder(); for(Param param:params){ builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + param.key + "\""), RequestBody.create(null, param.value)); } if(files == null){ files = new File[0]; } if(fileKeys == null){ fileKeys = new String[0]; } if(fileKeys.length != files.length){ throw new ArrayStoreException("fileKeys.length != files.length"); } RequestBody fileBody = null; int length = files.length; for(int i = 0;i
我們重點來看看ProgressBody.ProgressRequestBody的代碼 Okio中有兩個關鍵的接口,Sink和Source,這兩個接口都繼承了Closeable接口;而Sink可以簡單的看做OutputStream,Source可以簡單的看做InputStream。而這兩個接口都是支持讀寫超時設置的。 它們各自有一個支持緩沖區的子類接口,BufferedSink和BufferedSource,而BufferedSink有一個實現類RealBufferedSink,BufferedSource有一個實現類RealBufferedSource
if(bufferedSink == null){ //開始包裝 bufferedSink = Okio.buffer(sink(sink)); } //寫入 requestBody.writeTo(bufferedSink); bufferedSink.flush();
然後在發布進度
private Sink sink(Sink sink){ return new ForwardingSink(sink) { //當前寫入字節數 long byteWriteed = 0L; //總得字節數 long contentBytes = 0L; @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if(mHandler != null && mListener != null){ if(contentBytes == 0L){ contentBytes = contentLength(); } byteWriteed += byteCount; mListener.onProgress(byteWriteed, contentBytes, byteWriteed == contentBytes); } } };
然後在
mOkHttpClient.newCall(request).enqueue(new OKHttpThreadCallback(okHttpHandler, uploadListener, false));
在成功回調的接口中,進行如下操作
@Override public void onResponse(Call call, Response response) throws IOException { if(isDownload){ download(call,response); }else{ postSuccess(call,response); } }
private void postSuccess(final Call call, final Response response){ if(UICallback != null && UIHandler != null){ UIHandler.post(new Runnable() { @Override public void run() { UICallback.onSuccess(call, response,downFile == null?null:downFile.getAbsolutePath()); } }); } }
然後在主界面在進行接口回調,去實現其他一些操作
///////////////以下是代碼////////////////////以下是代碼/////////////////以下是代碼/////////////////////
MainActivity
package com.example.administrator.myapplication; import android.app.Activity; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Base64; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import org.json.JSONException; import org.json.JSONObject; import java.io.ByteArrayOutputStream; import java.io.IOException; import okhttp3.Response; public class MainActivity extends Activity implements View.OnClickListener { Button uploadSync,uploadAsync; private String responseStr; private byte[] bitmap2Bytes; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); uploadSync = (Button) findViewById(R.id.uploadSync); uploadAsync = (Button) findViewById(R.id.uploadAsync); uploadSync.setOnClickListener(this); uploadAsync.setOnClickListener(this); Resources res = getResources(); Bitmap bmp = BitmapFactory.decodeResource(res, R.mipmap.ic_launcher); bitmap2Bytes = Bitmap2Bytes(bmp); } public byte[] Bitmap2Bytes(Bitmap bm) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); bm.compress(Bitmap.CompressFormat.PNG, 100, baos); return baos.toByteArray(); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.uploadSync: try { Json = createJson(); Log.i("MainActivity","postSyncByParams2 Json:"+Json); } catch (JSONException e) { e.printStackTrace(); } new Thread() { @Override public void run() { try { Response response = OkHttpManger.getInstance().postSyncJson("https://10.0.5.48:7771/SetVPHUserInfo", Json); if (response.isSuccessful()){ responseStr = response.body().string(); Log.i("MainActivity","postSyncByParams2:"+responseStr); }else{ Log.i("MainActivity","postSyncByParams2 error"); } } catch (IOException e) { e.printStackTrace(); } } }.start(); break; case R.id.uploadAsync: try { Json = createJson(); Log.i("MainActivity","postSyncByParams2 Json:"+Json); } catch (JSONException e) { e.printStackTrace(); } try { OkHttpManger.getInstance().postAsyncJsonn("https://10.0.5.48:7771/SetVPHUserInfo", Json, new OkHttpManger.MyCallback() { @Override public void onSuccess(String result) { Log.i("MainActivity","result----"+result); } @Override public void onFailture() { } }); } catch (IOException e) { e.printStackTrace(); } break; } } String Json = null; private String createJson() throws JSONException { JSONObject jsonObject = new JSONObject(); JSONObject body = new JSONObject(); body.put("photo_type", "jpeg"); body.put("nickname", "hehe"); body.put("context", Base64.encodeToString(bitmap2Bytes, Base64.DEFAULT)); JSONObject head = new JSONObject(); head.put("godin_id", "0e6ad2a71e31752deb69d6e8c9eabe76"); head.put("app_type", "3"); head.put("os_type", "android"); JSONObject Request = new JSONObject(); Request.put("body", body); Request.put("head", head); jsonObject.put("Request", Request); return jsonObject.toString(); } }
SaflyApplication
package com.example.administrator.myapplication; import android.app.Application; import java.util.concurrent.TimeUnit; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import okhttp3.OkHttpClient; public class SaflyApplication extends Application { private static SaflyApplication instance; @Override public void onCreate() { super.onCreate(); this.instance = this; } public static SaflyApplication getInstance() { return instance; } public static OkHttpClient getUnsafeOkHttpClient() { try { final TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } } }; final SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(null, trustAllCerts, new java.security.SecureRandom()); final javax.net.ssl.SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.sslSocketFactory(sslSocketFactory); builder.hostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return true; } }); OkHttpClient okHttpClient = builder.connectTimeout(60, TimeUnit.SECONDS).writeTimeout(60, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build(); return okHttpClient; } catch (Exception e) { throw new RuntimeException(e); } } }
OkHttpManger
package com.example.administrator.myapplication; import android.os.Handler; import android.os.Looper; import android.util.Log; import com.alibaba.fastjson.JSON; import java.io.File; import java.io.IOException; import java.net.FileNameMap; import java.net.URLConnection; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import okhttp3.Call; import okhttp3.Callback; import okhttp3.FormBody; import okhttp3.Headers; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; /** * OkHttp 工具類, * get的同步異步請求 * post的json字符串同步異步上傳 * post的鍵值對同步異步上傳 * post文件異步上傳,回調結果以及進度 * 異步下載文件,回調結果以及進度 * * Created by Seeker on 2016/6/24. */ public final class OkHttpManger { private static final String TAG = "OkHttpManger"; private static final MediaType JSON_TYPE = MediaType.parse("application/json; charset=utf-8"); private Handler okHttpHandler; private OkHttpClient mOkHttpClient; private OkHttpManger(){ this.mOkHttpClient = SaflyApplication.getInstance().getUnsafeOkHttpClient(); this.okHttpHandler = new Handler(Looper.getMainLooper()); } public static final OkHttpManger getInstance(){ return SingleFactory.manger; } private static final class SingleFactory{ private static final OkHttpManger manger = new OkHttpManger(); } /////////////////////////同步異步上傳頭像////////////////////////////// interface MyCallback{ void onSuccess(String result); void onFailture(); } public Response postSyncJson(String url, String json) throws IOException { final RequestBody requestBody = RequestBody.create(JSON_TYPE, json); final Request request = new Request.Builder().url(url).post(requestBody).build(); return mOkHttpClient.newCall(request).execute(); } public void postAsyncJsonn(String url, String json, MyCallback mCallback) throws IOException { final RequestBody requestBody = RequestBody.create(JSON_TYPE, json); final Request request = new Request.Builder().url(url).post(requestBody).build(); deliveryResult(mOkHttpClient.newCall(request),mCallback); } private void deliveryResult(final Call call, final MyCallback mCallback) { call.enqueue(new Callback() { @Override public void onFailure(final Call call, final IOException e) { okHttpHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onFailture(); } } }); } @Override public void onResponse(Call call, final Response response) throws IOException { final String responseStr = response.body().string(); okHttpHandler.post(new Runnable() { @Override public void run() { if (mCallback != null) { mCallback.onSuccess(responseStr); } } }); } }); } ////////////////////////同步異步上傳頭像////////////////////////////// /** * 同步基於post的文件上傳 * @param url 地址 * @param file 提交的文件 * @param fileKey 提交的文件key * @return Response */ public Response uploadSync(String url, File file, String fileKey) throws IOException { return uploadSync(url, new File[]{file}, new String[]{fileKey}, new Param[0]); } /** * 同步基於post的文件上傳 * @param url 地址 * @param files 提交的文件數組 * @param fileKeys 提交的文件數組key * @param params 提交的鍵值對 * @return Response */ public Response uploadSync(String url, File[] files, String[] fileKeys, Param[] params) throws IOException { final RequestBody requestBody = buildMultipartFormRequestBody(files, fileKeys, params); final Request request = new Request.Builder().url(url).post(requestBody).build(); return mOkHttpClient.newCall(request).execute(); } /** * 異步基於post的文件上傳,回傳上傳進度 * @param url 地址 * @param file 提交的文件 * @param fileKey 提交的文件key */ public void uploadAsync(String url, File file, String fileKey, OKHttpUICallback.ProgressCallback listener) throws IOException { uploadAsync(url, new File[]{file}, new String[]{fileKey}, listener, new Param[0]); } /** * 異步基於post的文件上傳,回傳上傳進度 * @param url 地址 * @param files 提交的文件數組 * @param fileKeys 提交的文件數組key * @param params 提交的鍵值對 */ public void uploadAsync(String url, File[] files, String[] fileKeys, final OKHttpUICallback.ProgressCallback uploadListener, Param[] params) throws IOException { final RequestBody requestBody = buildMultipartFormRequestBody(files, fileKeys, params); final Request request = new Request.Builder().url(url).post(new ProgressBody.ProgressRequestBody(requestBody, uploadListener, okHttpHandler)).build(); mOkHttpClient.newCall(request).enqueue(new OKHttpThreadCallback(okHttpHandler, uploadListener, false)); } /** * 生成post提交時的分塊request * @param files * @param fileKeys * @param params * @return */ private RequestBody buildMultipartFormRequestBody(File[] files, String[] fileKeys, Param[] params){ if(params == null){ params = new Param[0]; } MultipartBody.Builder builder = new MultipartBody.Builder(); for(Param param:params){ builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + param.key + "\""), RequestBody.create(null, param.value)); } if(files == null){ files = new File[0]; } if(fileKeys == null){ fileKeys = new String[0]; } if(fileKeys.length != files.length){ throw new ArrayStoreException("fileKeys.length != files.length"); } RequestBody fileBody = null; int length = files.length; for(int i = 0;i<length;i++){ contenttype="" file="" filebody="RequestBody.create(MediaType.parse(guessMimeType(fileName)),file);" filename="file.getName();" filenamemap="" final="" name="\" pre="" private="" public="" return="" static="" string="" this.key="key;" this.value="value;" todo="" type="=">
ProgressBody 上傳的進度接口
package com.example.administrator.myapplication; import android.os.Handler; import java.io.IOException; import okhttp3.MediaType; import okhttp3.RequestBody; import okhttp3.ResponseBody; import okio.Buffer; import okio.BufferedSink; import okio.BufferedSource; import okio.ForwardingSink; import okio.ForwardingSource; import okio.Okio; import okio.Sink; import okio.Source; /** * Created by Seeker on 2016/7/27. */ public final class ProgressBody { /** * 包裝響應體,用於處理提示上傳進度 * * Created by Seeker on 2016/6/29. */ public static final class ProgressRequestBody extends RequestBody { //實際待包裝的請求體 private final RequestBody requestBody; //上傳進度回調接口 private OKHttpUICallback.ProgressCallback mListener; //包裝完成的BufferedSink private BufferedSink bufferedSink; //傳遞下載進度到主線程 private Handler mHandler; public ProgressRequestBody(RequestBody requestBody, OKHttpUICallback.ProgressCallback listener, Handler handler){ this.requestBody = requestBody; this.mListener = listener; this.mHandler = handler; } @Override public long contentLength() throws IOException { return requestBody.contentLength(); } @Override public MediaType contentType() { return requestBody.contentType(); } @Override public void writeTo(BufferedSink sink) throws IOException { if(bufferedSink == null){ //開始包裝 bufferedSink = Okio.buffer(sink(sink)); } //寫入 requestBody.writeTo(bufferedSink); bufferedSink.flush(); } /** * 寫入,回調進度接口 */ private Sink sink(Sink sink){ return new ForwardingSink(sink) { //當前寫入字節數 long byteWriteed = 0L; //總得字節數 long contentBytes = 0L; @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); if(mHandler != null && mListener != null){ if(contentBytes == 0L){ contentBytes = contentLength(); } byteWriteed += byteCount; mListener.onProgress(byteWriteed, contentBytes, byteWriteed == contentBytes); } } }; } } }
OKHttpThreadCallback上傳完畢後的接口方法
package com.example.administrator.myapplication; import android.os.Handler; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Response; public final class OKHttpThreadCallback implements Callback { private Handler UIHandler; private OKHttpUICallback.ProgressCallback UICallback; private boolean isDownload; private File downFile; public OKHttpThreadCallback(Handler handler, OKHttpUICallback.ProgressCallback callback, boolean isDownload){ this.UIHandler = handler; this.UICallback = callback; this.isDownload = isDownload; } @Override public void onFailure(final Call call, final IOException e) { if(UICallback != null && UIHandler != null){ UIHandler.post(new Runnable() { @Override public void run() { UICallback.onError(call,e); } }); } } @Override public void onResponse(Call call, Response response) throws IOException { if(isDownload){ download(call,response); }else{ postSuccess(call,response); } } /** * 設置保存file * @param file */ public OKHttpThreadCallback setFile(File file){ this.downFile = file; return this; } /** * 獲取下載數據並寫入文件 * @param response */ private void download(Call call, Response response) throws IOException { if(downFile == null){ throw new NullPointerException("downFile == null"); } byte[] buffer = new byte[2048]; InputStream is = response.body().byteStream(); int len; FileOutputStream fos = new FileOutputStream(downFile); while ((len = is.read(buffer)) != -1){ fos.write(buffer,0,len); } fos.flush(); if(is != null){ is.close(); } if (fos != null){ fos.close(); } postSuccess(call,null); } /** * 回調成功信息 * @param call * @param response */ private void postSuccess(final Call call, final Response response){ if(UICallback != null && UIHandler != null){ UIHandler.post(new Runnable() { @Override public void run() { UICallback.onSuccess(call, response,downFile == null?null:downFile.getAbsolutePath()); } }); } } }
OKHttpUICallback成功後需要實現的接口定義類
package com.example.administrator.myapplication; import java.io.IOException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import okhttp3.Call; import okhttp3.Response; /** * Created by safly on 2016/9/1. * * 回調主線程的接口 */ public class OKHttpUICallback { /** * 異步回調接口 */ /** * 帶有進度的上傳、下載回調接口 */ public interface ProgressCallback{ void onSuccess(Call call, Response response, String path); void onProgress(long byteReadOrWrite, long contentLength, boolean done); void onError(Call call, IOException e); } }
群英傳的最後一章,我大致的看了一下這個例子,發現鴻洋大神也做過,就參考兩個人的設計邏輯,感覺都差不多,就這樣實現起來了一.切圖工具類 我們九宮格嘛,肯定要一
最近老板要求在launcher界面做個自動定位,並獲取當地天氣的功能,中間走了不少彎路,我在這裡都寫下來,希望看到這篇文章的人,能少走點彎路。1、接到任務後,我首先想的是
概述在開發過程中,經常會遇到系統中提供的控件無法滿足產品的設計需求,這時可能就需要考慮使用自定義的View來實現產品的設計細節了。對於自定義View,可以分為兩種,一種是
Android結合版最近幾個版本在包大小配額上超標了,先後采用了包括圖片壓縮,功能H5,無用代碼移除等手段減包,還是有著很大的減包壓力。組內希望我能從代碼的角度減少一些包