Android OkHttp彻底解析 是时分来了解OkHttp了-Android-优质IT资源分享社区

admin
管理员
管理员
  • UID1
  • 粉丝33
  • 关注4
  • 发帖数581
  • 社区居民
  • 忠实会员
  • 原创写手
阅读:379回复:0

  Android OkHttp彻底解析 是时分来了解OkHttp了

楼主#
更多 发布于:2016-05-13 14:56


一、概述
最近在群里听到各种评论okhttp的话题,可见okhttp的口碑适当好了。再加上Google形似在6.0版别里边删除了HttpClient有关API,关于这个做法不做评估。为了非常好的在应对网络拜访,学习下okhttp仍是蛮必要的,本篇博客首要介绍okhttp的简略运用,首要包括:
通常的get恳求
通常的post恳求
依据Http的文件上载
文件下载
加载图像
支撑恳求回调,直接回来方针、方针调集
支撑session的坚持
最终会对上述几个功用进行封装,完好的封装类的地址见:https://github.com/hongyangAndroid/okhttp-utils
运用前,关于Android Studio的用户,能够挑选增加:
compile 'com.squareup.okhttp:okhttp:2.4.0'
或许Eclipse的用户,能够下载最新的jar okhttp he latest JAR ,增加依靠就能够用了。
留意:okhttp内部依靠okio,别忘了一起导入okio:
gradle: compile 'com.squareup.okio:okio:1.5.0'
最新的jar地址:okio the latest JAR
二、运用教程
(一)Http Get
对了网络加载库,那么最常见的必定即是http get恳求了,比方获取一个页面的内容。
//创立okHttpClient方针
OkHttpClient mOkHttpClient = new OkHttpClient();
//创立一个Request
final Request request = new Request.Builder()
.url("https://github.com/hongyangAndroid")
.build();
//new call
Call call = mOkHttpClient.newCall(request);
//恳求参加调度
call.enqueue(new Callback()
{
 public void onFailure(Request request, IOException e)
{
}
 public void onResponse(final Response response) throws IOException
{
//String htmlStr = response.body().string();
}
});
以上即是发送一个get恳求的过程,首要结构一个Request方针,参数最起码有个url,当然你能够经过Request.Builder设置更多的参数比方:header、method等。
然后经过request的方针去结构得到一个Call方针,类似于将你的恳求封装成了使命,既然是使命,就会有execute()和cancel()等办法。
最终,咱们期望以异步的办法去履行恳求,所以咱们调用的是call.enqueue,将call参加调度队列,然后等候使命履行完结,咱们在Callback中即可得到成果。
看到这,你会发现,全体的写法仍是比较长的,所以封装必定是要做的,否则每个恳求这么写,得累死。
ok,需求留意几点:
onResponse回调的参数是response,通常情况下,比方咱们期望取得回来的字符串,能够经过response.body().string()获取;假如期望取得回来的二进制字节数组,则调用response.body().bytes();假如你想拿到回来的inputStream,则调用response.body().byteStream()看到这,你也许会古怪,居然还能拿到回来的inputStream,看到这个最起码能意识到一点,这儿支撑大文件下载,有inputStream咱们就能够经过IO的办法写文件。不过也阐明一个疑问,这个onResponse履行的线程并不是UI线程。确实是的,假如你期望操作控件,仍是需求运用handler等,例如:
 public void onResponse(final Response response) throws IOException
{
final String res = response.body().string();
runOnUiThread(new Runnable()
{
 public void run()
{
mTv.setText(res);
}
});
}
咱们这儿是异步的办法去履行,当然也支撑堵塞的办法,上面咱们也说了Call有一个execute()办法,你也能够直接调用call.execute()经过回来一个Response。
(二) Http Post 带着参数
看来上面的简略的get恳求,根本上整个的用法也就把握了,比方post带着参数,也仅仅是Request的结构的不同。
Request request = buildMultipartFormRequest(
url, new File[]{file}, new String[]{fileKey}, null);
FormEncodingBuilder builder = new FormEncodingBuilder();
builder.add("username","张鸿洋");
Request request = new Request.Builder()
.url(url)
.post(builder.build())
.build();
mOkHttpClient.newCall(request).enqueue(new Callback(){});
咱们都明白,post的时分,参数是包括在恳求体中的;所以咱们经过FormEncodingBuilder。增加多个String键值对,然后去结构RequestBody,最终完结咱们Request的结构。
后边的就和上面相同了。
(三)依据Http的文件上载
接下来咱们在介绍一个能够结构RequestBody的Builder,叫做MultipartBuilder。当咱们需求做类似于表单上载的时分,就能够运用它来结构咱们的requestBody。
File file = new File(Environment.getExternalStorageDirectory(), "balabala.mp4");
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
RequestBody requestBody = new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(Headers.of(
"Content-Disposition",
"form-data; name=\"username\""),
RequestBody.create(null, "张鸿洋"))
.addPart(Headers.of(
"Content-Disposition",
"form-data; name=\"mFile\";
filename=\"wjd.mp4\""), fileBody)
.build();
Request request = new Request.Builder()
.url("http://192.168.1.103:8080/okHttpServer/fileUpload")
.post(requestBody)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback()
{
//...
});
上述代码向服务器传递了一个键值对username:张鸿洋和一个文件。咱们经过MultipartBuilder的addPart办法能够增加键值对或许文件。
本来类似于咱们拼接模拟浏览器做法的办法,假如你对这块不了解,能够参阅:从原理视点解析Android (Java) http 文件上载
ok,关于咱们最开端的目录还剩下图像下载,文件下载;这两个一个是经过回调的Response拿到byte[]然后decode成图像;文件下载,即是拿到inputStream做写文件操作,咱们这儿就不赘述了。
关于用法,也能够参阅泡网OkHttp运用教程
接下来咱们首要看怎么封装上述的代码。
三、封装
由于按照上述的代码,写多个恳求必定包括很多的重复代码,所以我期望封装后的代码调用是这么的:
(一)运用
通常的get恳求
OkHttpClientManager.getAsyn("https://www.baidu.com", new OkHttpClientManager.ResultCallback()
{
 public void onError(Request request, Exception e)
{
e.printStackTrace();
}
 public void onResponse(String u)
{
mTv.setText(u);//留意这儿是UI线程
}
});
关于通常的恳求,咱们期望给个url,然后CallBack里边直接操作控件。
文件上载且带着参数咱们期望供给一个办法,传入url,params,file,callback即可。
OkHttpClientManager.postAsyn("http://192.168.1.103:8080/okHttpServer/fileUpload",//
new OkHttpClientManager.ResultCallback()
{
 public void onError(Request request, IOException e)
{
e.printStackTrace();
}
 public void onResponse(String result)
{
}
},//
file,//
"mFile",//
new OkHttpClientManager.Param[]{
new OkHttpClientManager.Param("username", "zhy"),
new OkHttpClientManager.Param("password", "123")}
);
键值对没什么说的,参数3为file,参数4为file对应的name,这个name不是文件的名字;
对应于http中的
对应的是name后边的值,即mFile.
文件下载关于文件下载,供给url,方针dir,callback即可。
OkHttpClientManager.downloadAsyn(
"http://192.168.1.103:8080/okHttpServer/files/messenger_01.png",
Environment.getExternalStorageDirectory().getAbsolutePath(),
new OkHttpClientManager.ResultCallback()
{
 public void onError(Request request, IOException e)
{
}
 public void onResponse(String response)
{
//文件下载成功,这儿回调的reponse为文件的absolutePath
}
});
展现图像展现图像,咱们期望供给一个url和一个imageview,假如下载成功,直接帮咱们设置上即可。
OkHttpClientManager.displayImage(mImageView,
"http://images.csdn.net/20150817/1.jpg");
内部会主动依据imageview的大小主动对图像进行适宜的紧缩。尽管,这儿也许不适合一次性加载很多图像的场景,但是关于app中偶然有几个图像的加载,仍是可用的。
四、结合Gson
很多人提出项目中运用时,服务端回来的是Json字符串,期望客户端回调能够直接拿到方针,所以结合进入了Gson,完善该功用。
(一)直接回调方针
例如如今有个User实体类:
package com.zhy.utils.http.okhttp;
public class User {
public String username ;
public String password ;
public User() {
// TODO Auto-generated constructor stub
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
 public String toString()
{
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
服务端回来:
{"username":"zhy","password":"123"}
客户端能够如下办法调用:
OkHttpClientManager.getAsyn("http://192.168.56.1:8080/okHttpServer/user!getUser",
new OkHttpClientManager.ResultCallback()
{
 public void onError(Request request, Exception e)
{
e.printStackTrace();
}
 public void onResponse(User user)
{
mTv.setText(u.toString());//UI线程
}
});
咱们传入泛型User,在onResponse里边直接回调User方针。
这儿格外要留意的事,假如在json字符串->实体方针过程中发生过错,程序不会崩溃,onError办法会被回调。
留意:这儿做了少许的更新,接口命名从StringCallback修改为ResultCallback。接口中的onFailure办法修改为onError。
(二) 回调方针调集
依然是上述的User类,服务端回来
[{"username":"zhy","password":"123"},{"username":"lmj","password":"12345"}]
则客户端能够如下调用:
OkHttpClientManager.getAsyn("http://192.168.56.1:8080/okHttpServer/user!getUsers",
new OkHttpClientManager.ResultCallback>()
{
 public void onError(Request request, Exception e)
{
e.printStackTrace();
}
 public void onResponse(List us)
{
Log.e("TAG", us.size() + "");
mTv.setText(us.get(1).toString());
}
});
仅有的差异,即是泛型变为List ,ok , 假如发现bug或许有任何定见期待留言。
源码
ok,根本介绍完了,关于封装的代码本来也很简略,我就直接贴出来了,由于也没什么好介绍的,假如你看完上面的用法,必定能够看懂:
package com.zhy.utils.http.okhttp;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.widget.ImageView;
import com.谷歌.gson.Gson;
import com.谷歌.gson.internal.$Gson$Types;
import com.squareup.okhttp.Call;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.FormEncodingBuilder;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Created by zhy on 15/8/17.
*/
public class OkHttpClientManager
{
private static OkHttpClientManager mInstance;
private OkHttpClient mOkHttpClient;
private Handler mDelivery;
private Gson mGson;
private static final String TAG = "OkHttpClientManager";
private OkHttpClientManager()
{
mOkHttpClient = new OkHttpClient();
//cookie enabled
mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
mDelivery = new Handler(Looper.getMainLooper());
mGson = new Gson();
}
public static OkHttpClientManager getInstance()
{
if (mInstance == null)
{
synchronized (OkHttpClientManager.class)
{
if (mInstance == null)
{
mInstance = new OkHttpClientManager();
}
}
}
return mInstance;
}
/**
* 同步的Get恳求
*
* @param url
* @return Response
*/
private Response _getAsyn(String url) throws IOException
{
final Request request = new Request.Builder()
.url(url)
.build();
Call call = mOkHttpClient.newCall(request);
Response execute = call.execute();
return execute;
}
/**
* 同步的Get恳求
*
* @param url
* @return 字符串
*/
private String _getAsString(String url) throws IOException
{
Response execute = _getAsyn(url);
return execute.body().string();
}
/**
* 异步的get恳求
*
* @param url
* @param callback
*/
private void _getAsyn(String url, final ResultCallback callback)
{
final Request request = new Request.Builder()
.url(url)
.build();
deliveryResult(callback, request);
}
/**
* 同步的Post恳求
*
* @param url
* @param params post的参数
* @return
*/
private Response _post(String url, Param... params) throws IOException
{
Request request = buildPostRequest(url, params);
Response response = mOkHttpClient.newCall(request).execute();
return response;
}
/**
* 同步的Post恳求
*
* @param url
* @param params post的参数
* @return 字符串
*/
private String _postAsString(String url, Param... params) throws IOException
{
Response response = _post(url, params);
return response.body().string();
}
/**
* 异步的post恳求
*
* @param url
* @param callback
* @param params
*/
private void _postAsyn(String url, final ResultCallback callback, Param... params)
{
Request request = buildPostRequest(url, params);
deliveryResult(callback, request);
}
/**
* 异步的post恳求
*
* @param url
* @param callback
* @param params
*/
private void _postAsyn(String url, final ResultCallback callback, Map params)
{
Param[] paramsArr = map2Params(params);
Request request = buildPostRequest(url, paramsArr);
deliveryResult(callback, request);
}
/**
* 同步依据post的文件上载
*
* @param params
* @return
*/
private Response _post(String url, File[] files, String[] fileKeys, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, files, fileKeys, params);
return mOkHttpClient.newCall(request).execute();
}
private Response _post(String url, File file, String fileKey) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null);
return mOkHttpClient.newCall(request).execute();
}
private Response _post(String url, File file, String fileKey, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params);
return mOkHttpClient.newCall(request).execute();
}
/**
* 异步依据post的文件上载
*
* @param url
* @param callback
* @param files
* @param fileKeys
* @throws IOException
*/
private void _postAsyn(String url, ResultCallback callback, File[] files, String[] fileKeys, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, files, fileKeys, params);
deliveryResult(callback, request);
}
/**
* 异步依据post的文件上载,单文件不带参数上载
*
* @param url
* @param callback
* @param file
* @param fileKey
* @throws IOException
*/
private void _postAsyn(String url, ResultCallback callback, File file, String fileKey) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, null);
deliveryResult(callback, request);
}
/**
* 异步依据post的文件上载,单文件且带着别的form参数上载
*
* @param url
* @param callback
* @param file
* @param fileKey
* @param params
* @throws IOException
*/
private void _postAsyn(String url, ResultCallback callback, File file, String fileKey, Param... params) throws IOException
{
Request request = buildMultipartFormRequest(url, new File[]{file}, new String[]{fileKey}, params);
deliveryResult(callback, request);
}
/**
* 异步下载文件
*
* @param url
* @param destFileDir 本地文件存储的文件夹
* @param callback
*/
private void _downloadAsyn(final String url, final String destFileDir, final ResultCallback callback)
{
final Request request = new Request.Builder()
.url(url)
.build();
final Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback()
{
 public void onFailure(final Request request, final IOException e)
{
sendFailedStringCallback(request, e, callback);
}
 public void onResponse(Response response)
{
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
try
{
is = response.body().byteStream();
File file = new File(destFileDir, getFileName(url));
fos = new FileOutputStream(file);
while ((len = is.read(buf)) != -1)
{
fos.write(buf, 0, len);
}
fos.flush();
//假如下载文件成功,第一个参数为文件的绝对路径
sendSuccessResultCallback(file.getAbsolutePath(), callback);
} catch (IOException e)
{
sendFailedStringCallback(response.request(), e, callback);
} finally
{
try
{
if (is != null) is.close();
} catch (IOException e)
{
}
try
{
if (fos != null) fos.close();
} catch (IOException e)
{
}
}
}
});
}
private String getFileName(String path)
{
int separatorIndex = path.lastIndexOf("/");
return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, path.length());
}
/**
* 加载图像
*
* @param view
* @param url
* @throws IOException
*/
private void _displayImage(final ImageView view, final String url, final int errorResId)
{
final Request request = new Request.Builder()
.url(url)
.build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback()
{
 public void onFailure(Request request, IOException e)
{
setErrorResId(view, errorResId);
}
 public void onResponse(Response response)
{
InputStream is = null;
try
{
is = response.body().byteStream();
ImageUtils.ImageSize actualImageSize = ImageUtils.getImageSize(is);
ImageUtils.ImageSize imageViewSize = ImageUtils.getImageViewSize(view);
int inSampleSize = ImageUtils.calculateInSampleSize(actualImageSize, imageViewSize);
try
{
is.reset();
} catch (IOException e)
{
response = _getAsyn(url);
is = response.body().byteStream();
}
BitmapFactory.Options ops = new BitmapFactory.Options();
ops.inJustDecodeBounds = false;
ops.inSampleSize = inSampleSize;
final Bitmap bm = BitmapFactory.decodeStream(is, null, ops);
mDelivery.post(new Runnable()
{
 public void run()
{
view.setImageBitmap(bm);
}
});
} catch (Exception e)
{
setErrorResId(view, errorResId);
} finally
{
if (is != null) try
{
is.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
});
}
private void setErrorResId(final ImageView view, final int errorResId)
{
mDelivery.post(new Runnable()
{
 public void run()
{
view.setImageResource(errorResId);
}
});
}
//*************对外发布的办法************
public static Response getAsyn(String url) throws IOException
{
return getInstance()._getAsyn(url);
}
public static String getAsString(String url) throws IOException
{
return getInstance()._getAsString(url);
}
public static void getAsyn(String url, ResultCallback callback)
{
getInstance()._getAsyn(url, callback);
}
public static Response post(String url, Param... params) throws IOException
{
return getInstance()._post(url, params);
}
public static String postAsString(String url, Param... params) throws IOException
{
return getInstance()._postAsString(url, params);
}
public static void postAsyn(String url, final ResultCallback callback, Param... params)
{
getInstance()._postAsyn(url, callback, params);
}
public static void postAsyn(String url, final ResultCallback callback, Map params)
{
getInstance()._postAsyn(url, callback, params);
}
public static Response post(String url, File[] files, String[] fileKeys, Param... params) throws IOException
{
return getInstance()._post(url, files, fileKeys, params);
}
public static Response post(String url, File file, String fileKey) throws IOException
{
return getInstance()._post(url, file, fileKey);
}
public static Response post(String url, File file, String fileKey, Param... params) throws IOException
{
return getInstance()._post(url, file, fileKey, params);
}
public static void postAsyn(String url, ResultCallback callback, File[] files, String[] fileKeys, Param... params) throws IOException
{
getInstance()._postAsyn(url, callback, files, fileKeys, params);
}
public static void postAsyn(String url, ResultCallback callback, File file, String fileKey) throws IOException
{
getInstance()._postAsyn(url, callback, file, fileKey);
}
public static void postAsyn(String url, ResultCallback callback, File file, String fileKey, Param... params) throws IOException
{
getInstance()._postAsyn(url, callback, file, fileKey, params);
}
public static void displayImage(final ImageView view, String url, int errorResId) throws IOException
{
getInstance()._displayImage(view, url, errorResId);
}
public static void displayImage(final ImageView view, String url)
{
getInstance()._displayImage(view, url, -1);
}
public static void downloadAsyn(String url, String destDir, ResultCallback callback)
{
getInstance()._downloadAsyn(url, destDir, callback);
}
//****************************
private Request buildMultipartFormRequest(String url, File[] files,
String[] fileKeys, Param[] params)
{
params = validateParam(params);
MultipartBuilder builder = new MultipartBuilder()
.type(MultipartBuilder.FORM);
for (Param param : params)
{
builder.addPart(Headers.of("Content-Disposition", "form-data; name=\"" + param.key + "\""),
RequestBody.create(null, param.value));
}
if (files != null)
{
RequestBody fileBody = null;
for (int i = 0; i < files.length; i++)
{
File file = files;
String fileName = file.getName();
fileBody = RequestBody.create(MediaType.parse(guessMimeType(fileName)), file);
//TODO 依据文件名设置contentType
builder.addPart(Headers.of("Content-Disposition",
"form-data; name=\"" + fileKeys + "\"; filename=\"" + fileName + "\""),
fileBody);
}
}
RequestBody requestBody = builder.build();
return new Request.Builder()
.url(url)
.post(requestBody)
.build();
}
private String guessMimeType(String path)
{
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String contentTypeFor = fileNameMap.getContentTypeFor(path);
if (contentTypeFor == null)
{
contentTypeFor = "application/octet-stream";
}
return contentTypeFor;
}
private Param[] validateParam(Param[] params)
{
if (params == null)
return new Param[0];
else return params;
}
private Param[] map2Params(Map params)
{
if (params == null) return new Param[0];
int size = params.size();
Param[] res = new Param[size];
Set> entries = params.entrySet();
int i = 0;
for (Map.Entry entry : entries)
{
res[i++] = new Param(entry.getKey(), entry.getValue());
}
return res;
}
private static final String SESSION_KEY = "Set-Cookie";
private static final String mSessionKey = "JSESSIONID";
private Map mSessions = new HashMap();
private void deliveryResult(final ResultCallback callback, Request request)
{
mOkHttpClient.newCall(request).enqueue(new Callback()
{
 public void onFailure(final Request request, final IOException e)
{
sendFailedStringCallback(request, e, callback);
}
 public void onResponse(final Response response)
{
try
{
final String string = response.body().string();
if (callback.mType == String.class)
{
sendSuccessResultCallback(string, callback);
} else
{
Object o = mGson.fromJson(string, callback.mType);
sendSuccessResultCallback(o, callback);
}
} catch (IOException e)
{
sendFailedStringCallback(response.request(), e, callback);
} catch (com.谷歌.gson.JsonParseException e)//Json解析的过错
{
sendFailedStringCallback(response.request(), e, callback);
}
}
});
}
private void sendFailedStringCallback(final Request request, final Exception e, final ResultCallback callback)
{
mDelivery.post(new Runnable()
{
 public void run()
{
if (callback != null)
callback.onError(request, e);
}
});
}
private void sendSuccessResultCallback(final Object object, final ResultCallback callback)
{
mDelivery.post(new Runnable()
{
 public void run()
{
if (callback != null)
{
callback.onResponse(object);
}
}
});
}
private Request buildPostRequest(String url, Param[] params)
{
if (params == null)
{
params = new Param[0];
}
FormEncodingBuilder builder = new FormEncodingBuilder();
for (Param param : params)
{
builder.add(param.key, param.value);
}
RequestBody requestBody = builder.build();
return new Request.Builder()
.url(url)
.post(requestBody)
.build();
}
public static abstract class ResultCallback
{
Type mType;
public ResultCallback()
{
mType = getSuperclassTypeParameter(getClass());
}
static Type getSuperclassTypeParameter(Class subclass)
{
Type superclass = subclass.getGenericSuperclass();
if (superclass instanceof Class)
{
throw new RuntimeException("Missing type parameter.");
}
ParameterizedType parameterized = (ParameterizedType) superclass;
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
public abstract void onError(Request request, Exception e);
public abstract void onResponse(T response);
}
public static class Param
{
public Param()
{
}
public Param(String key, String value)
{
this.key = key;
this.value = value;
}
String key;
String value;
}
}
源码地址okhttp-utils,咱们能够自个下载查看。










优质IT资源分享社区为你提供此文。
本站有大量优质android教程视频,资料等资源,包含android基础教程,高级进阶教程等等,教程视频资源涵盖传智播客,极客学院,达内,北大青鸟,猎豹网校等等IT职业培训机构的培训教学视频,价值巨大。欢迎点击下方链接查看。

android教程视频
优质IT资源分享社区(www.itziyuan.top)
一个免费,自由,开放,共享,平等,互助的优质IT资源分享网站。
专注免费分享各大IT培训机构最新培训教学视频,为你的IT学习助力!

!!!回帖受限制请看点击这里!!!
!!!资源失效请在此版块发帖说明!!!

[PS:按 CTRL+D收藏本站网址~]

——“优质IT资源分享社区”管理员专用签名~

本版相似帖子

游客