网络框架-okhttp

来源:互联网 发布:js prompt() 循环 编辑:程序博客网 时间:2024/06/05 18:45

okhttp是什么?简而言之就是一款优秀的网络框架。
能实现的功能?Get、Post请求,文件上传和下载等等….

从基本功能的调用,看看一篇关于okhttp的封装:

// http Get操作OkHttpClient client = new OkHttpClient();String run(String url) throws IOException {  Request request = new Request.Builder()      .url(url)      .build();  Response response = client.newCall(request).execute();  return response.body().string();}
//http Post提交Json数据public static final MediaType JSON    = MediaType.parse("application/json; charset=utf-8");OkHttpClient client = new OkHttpClient();String post(String url, String json) throws IOException {  RequestBody body = RequestBody.create(JSON, json);  Request request = new Request.Builder()      .url(url)      .post(body)      .build();  Response response = client.newCall(request).execute();  return response.body().string();}

由于okhttp要在子线程中进行操作,所以要对其进行封装,在github搜索okhttp回看到很多封装库,一个事鸿洋的okhttputils,另一个叫okhttp-OkGo,两个star都在3000以上以上了,可见都很好,可以看一下,依赖指定的jar包,或着clone下来,代码里可以看到几种功能的调用。

先看,前两天我看到的一个okhttp的封装工具:

/** * Description : OkHttp网络连接封装工具类 * Author : lauren * Email  : lauren.liuling@gmail.com * Blog   : http://www.liuling123.com * Date   : 15/12/17 */public class OkHttpUtils {    private static final String TAG = "OkHttpUtils";    private static OkHttpUtils mInstance;    private OkHttpClient mOkHttpClient;    private Handler mDelivery;    private OkHttpUtils() {        mOkHttpClient = new OkHttpClient();        mOkHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);        mOkHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);        mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);        //cookie enabled        mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));        mDelivery = new Handler(Looper.getMainLooper());    }    private synchronized static OkHttpUtils getmInstance() {        if (mInstance == null) {            mInstance = new OkHttpUtils();        }        return mInstance;    }    private void getRequest(String url, final ResultCallback callback) {        final Request request = new Request.Builder().url(url).build();        deliveryResult(callback, request);    }    private void postRequest(String url, final ResultCallback callback, List<Param> params) {        Request request = buildPostRequest(url, params);        deliveryResult(callback, request);    }    private void deliveryResult(final ResultCallback callback, Request request) {        mOkHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Request request, final IOException e) {                sendFailCallback(callback, e);            }            @Override            public void onResponse(Response response) throws IOException {                try {                    String str = response.body().string();                    if (callback.mType == String.class) {                        sendSuccessCallBack(callback, str);                    } else {                        Object object = JsonUtils.deserialize(str, callback.mType);                        sendSuccessCallBack(callback, object);                    }                } catch (final Exception e) {                    LogUtils.e(TAG, "convert json failure", e);                    sendFailCallback(callback, e);                }            }        });    }    private void sendFailCallback(final ResultCallback callback, final Exception e) {        mDelivery.post(new Runnable() {            @Override            public void run() {                if (callback != null) {                    callback.onFailure(e);                }            }        });    }    private void sendSuccessCallBack(final ResultCallback callback, final Object obj) {        mDelivery.post(new Runnable() {            @Override            public void run() {                if (callback != null) {                    callback.onSuccess(obj);                }            }        });    }    private Request buildPostRequest(String url, List<Param> params) {        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();    }    /**********************对外接口************************/    /**     * get请求     * @param url  请求url     * @param callback  请求回调     */    public static void get(String url, ResultCallback callback) {        getmInstance().getRequest(url, callback);    }    /**     * post请求     * @param url       请求url     * @param callback  请求回调     * @param params    请求参数     */    public static void post(String url, final ResultCallback callback, List<Param> params) {        getmInstance().postRequest(url, callback, params);    }    /**     * http请求回调类,回调方法在UI线程中执行     * @param <T>     */    public static abstract class ResultCallback<T> {        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]);        }        /**         * 请求成功回调         * @param response         */        public abstract void onSuccess(T response);        /**         * 请求失败回调         * @param e         */        public abstract void onFailure(Exception e);    }    /**     * post请求参数类     */    public static class Param {        String key;        String value;        public Param() {        }        public Param(String key, String value) {            this.key = key;            this.value = value;        }    }}

如果不靠工具类,自己去过一遍,因为要在子线程中调用,可能会new一个Thread,启动一个线程,或着来一个asynctask,也方便了更新ui与主线程交互handler了,代码就不写了,刚刚说的很容易实现。由于okhttp提供了一个enqueue这个异步方法,因此省略了,start一个Thread的操作。网络请求成功后,由非主线程–>主线程,需要一个handler.sendMessage()或着handler.post(new Runnable()),runOnUiThread();不在acitivity里或着fragment中,还需要一个接口的监听去返回成功和失败两个方法。成功返回的是结果的值,实体类、String字符串等等。(不看代码我可能还想不出来那么多,哈哈,还是直接分析代码)

 private OkHttpUtils() {       //okhttp初始化,配置信息,同时创建一个handler对象        mOkHttpClient = new OkHttpClient();        mOkHttpClient.setConnectTimeout(10, TimeUnit.SECONDS);        mOkHttpClient.setWriteTimeout(10, TimeUnit.SECONDS);        mOkHttpClient.setReadTimeout(30, TimeUnit.SECONDS);        //cookie enabled        mOkHttpClient.setCookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER));        mDelivery = new Handler(Looper.getMainLooper());    }
     //单例模式,但这样些不太好。    private synchronized static OkHttpUtils getmInstance() {        if (mInstance == null) {            mInstance = new OkHttpUtils();        }        return mInstance;    } //另一种方式的单例模式 private static OkHttpUtils mInstance=null; private static OkHttpUtils getmInstance(){  if(mInstance==null){    synchronized(OkHttpUtils.class){        if(mInstance==null){          mInstance=new OkHttpUtils();        }    }  }return mInstance;}

Get请求:

    /**     * get请求     * @param url  请求url     * @param callback  请求回调     */     //get方法的调用,在里面对get操作进一步封装,同时指定一个回调    public static void get(String url, ResultCallback callback) {        getmInstance().getRequest(url, callback);    }
//拼接得到request对象,提交网络操作在方法deliveryResult中进行    private void getRequest(String url, final ResultCallback callback) {        final Request request = new Request.Builder().url(url).build();        deliveryResult(callback, request);    }
//实现真正的提交网络操作,在异步中进行enqueue(params)private void deliveryResult(final ResultCallback callback, Request request) {        mOkHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Request request, final IOException e) {               //请求失败,回调自定义接口失败的方法。                sendFailCallback(callback, e);            }            @Override            public void onResponse(Response response) throws IOException {                try {                    String str = response.body().string();                    if (callback.mType == String.class) {                        sendSuccessCallBack(callback, str);                    } else {                        Object object = JsonUtils.deserialize(str, callback.mType);                        //请求成功,回调自定义接口成功的方法。                        sendSuccessCallBack(callback, object);                    }                } catch (final Exception e) {                    LogUtils.e(TAG, "convert json failure", e);                    sendFailCallback(callback, e);                }            }        });    }
//对应上面两个回调方法,用handler.post实现与主线程数据交互 private void sendFailCallback(final ResultCallback callback, final Exception e) {        mDelivery.post(new Runnable() {            @Override            public void run() {                if (callback != null) {                    callback.onFailure(e);                }            }        });    }    private void sendSuccessCallBack(final ResultCallback callback, final Object obj) {        mDelivery.post(new Runnable() {            @Override            public void run() {                if (callback != null) {                    callback.onSuccess(obj);                }            }        });    }

看上面代码中的callback这个参数,是在deliveryResult方法中传递的参数,需要我们调用get或者post时,去生成一个callback,看看这个类:

//抽象类,封装了两个方法,当要拿到这个类的引用时,需要复写这两个方法,看下面调用 /**     * http请求回调类,回调方法在UI线程中执行     * @param <T>     */    public static abstract class ResultCallback<T> {        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]);        }        /**         * 请求成功回调         * @param response         */        public abstract void onSuccess(T response);        /**         * 请求失败回调         * @param e         */        public abstract void onFailure(Exception e);    }
//主线程中调用okhttp工具类进行网络请求//这里就是上面两个方法的回调,使用handler后变为在主线程中        OkHttpUtils.ResultCallback<String> loadNewsCallback = new OkHttpUtils.ResultCallback<String>() {            @Override             public void onSuccess(String response) {                List<NewsBean> newsBeanList = NewsJsonUtils.readJsonNewsBeans(response, getID(type));                listener.onSuccess(newsBeanList);            }            @Override            public void onFailure(Exception e) {                listener.onFailure("load news list failure.", e);            }        };

Post请求:
和Get请求不同的在于requertBody的不同,Post请求要提交一些数据参数之类,另外Post使用起来更安全。

    /**     * post请求     * @param url       请求url     * @param callback  请求回调     * @param params    请求参数     */    //和Get请求一样,去进一步封装    public static void post(String url, final ResultCallback callback, List<Param> params) {        getmInstance().postRequest(url, callback, params);    }
    //与get不同的是,多了一个List的参数,这里是用来传递拼接的字段。    private void postRequest(String url, final ResultCallback callback, List<Param> params) {        //调用buildPostRequest方法,去拼接        Request request = buildPostRequest(url, params);        //执行网络操作        deliveryResult(callback, request);    }
//遍历list集合,使用FormEncodingBuilder去add键值对private Request buildPostRequest(String url, List<Param> params) {        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();    }
    /**     * post请求参数类     */     //List集合中的参数类    public static class Param {        String key;        String value;        public Param() {        }        public Param(String key, String value) {            this.key = key;            this.value = value;        }    }

下面和Get操作一样了,执行网络操作,区别就在于上面的requestbody的不同。同样的调用方法:deliveryResult()

    private void deliveryResult(final ResultCallback callback, Request request) {        mOkHttpClient.newCall(request).enqueue(new Callback() {            @Override            public void onFailure(Request request, final IOException e) {                sendFailCallback(callback, e);            }            @Override            public void onResponse(Response response) throws IOException {                try {                    String str = response.body().string();                    if (callback.mType == String.class) {                        sendSuccessCallBack(callback, str);                    } else {                        Object object = JsonUtils.deserialize(str, callback.mType);                        sendSuccessCallBack(callback, object);                    }                } catch (final Exception e) {                    LogUtils.e(TAG, "convert json failure", e);                    sendFailCallback(callback, e);                }            }        });    }

实现文件下载和上传:
依赖一个jar包工具类:详细信息点击看这里:https://github.com/jeasonlzy/okhttp-OkGo

compile 'com.lzy.net:okgo:+'        //版本号使用 + 可以自动引用最新版compile 'com.lzy.widget:imagepicker:0.3.2'  //这个是一个图片选择器工具类compile 'com.github.bumptech.glide:glide:3.6.1'

仿微信图片选择imagepicker:https://github.com/jeasonlzy/ImagePicker

文件下载:

/**     * 文件下载     *     * @param view     */    public void download(View view) {        OkGo.get(Urls.URL_DOWNLOAD)                .tag(this)                .headers("header1", "headerValue1")                .params("paramq", "paramValue1")                .execute(new FileCallback() {                    @Override                    public void onBefore(BaseRequest request) {                        super.onBefore(request);                        bt_download.setText("下载中....");                    }                    @Override                    public void onSuccess(File file, Call call, Response response) {                        bt_download.setText("下载成功");                        tv_download_speed.setText("--");                    }                    @Override                    public void onError(Call call, Response response, Exception e) {                        super.onError(call, response, e);                        bt_download.setText("下载失败");                        tv_download_speed.setText("--");                    }                    @Override                    public void downloadProgress(long currentSize, long totalSize, float progress, long networkSpeed) {                        super.downloadProgress(currentSize, totalSize, progress, networkSpeed);                        String downloadLength = Formatter.formatFileSize(getApplicationContext(), currentSize);                        String totalLength = Formatter.formatFileSize(getApplicationContext(), totalSize);                        tv_current_size.setText(downloadLength);                        tv_total_size.setText(totalLength);                        tv_current_progress.setText((int) (progress * 100) + "%");                        String netSpeed = Formatter.formatFileSize(getApplicationContext(), networkSpeed);                        tv_download_speed.setText(netSpeed + "/S");                        load_progressbar.setProgress((int) (progress * 100));                    }                });    }

可以设置多个头部header参数和params参数信息,这里是任意设置的,可以自定义callback,实现依赖jar包中的AbsCallback。AbsCallback里有几个回调方法:代码如下:

public abstract class AbsCallback<T> implements Converter<T> {    /** 请求网络开始前,UI线程 */    public void onBefore(BaseRequest request) {    }    /** 对返回数据进行操作的回调, UI线程 */    public abstract void onSuccess(T t, Call call, Response response);    /** 缓存成功的回调,UI线程 */    public void onCacheSuccess(T t, Call call) {    }    /** 请求失败,响应错误,数据解析错误等,都会回调该方法, UI线程 */    public void onError(Call call, Response response, Exception e) {    }    /** 缓存失败的回调,UI线程 */    public void onCacheError(Call call, Exception e) {    }    /** 网络失败结束之前的回调 */    public void parseError(Call call, Exception e) {    }    /** 请求网络结束后,UI线程 */    public void onAfter(T t, Exception e) {        if (e != null) e.printStackTrace();    }    /**     * Post执行上传过程中的进度回调,get请求不回调,UI线程     *     * @param currentSize  当前上传的字节数     * @param totalSize    总共需要上传的字节数     * @param progress     当前上传的进度     * @param networkSpeed 当前上传的速度 字节/秒     */    public void upProgress(long currentSize, long totalSize, float progress, long networkSpeed) {    }    /**     * 执行下载过程中的进度回调,UI线程     *     * @param currentSize  当前下载的字节数     * @param totalSize    总共需要下载的字节数     * @param progress     当前下载的进度     * @param networkSpeed 当前下载的速度 字节/秒     */    public void downloadProgress(long currentSize, long totalSize, float progress, long networkSpeed) {    }}

这里FileCallback的代码如下:

public abstract class FileCallback extends AbsCallback<File> {    private FileConvert convert;    //文件转换类    public FileCallback() {        this(null);    }    public FileCallback(String destFileName) {        this(null, destFileName);    }    public FileCallback(String destFileDir, String destFileName) {        convert = new FileConvert(destFileDir, destFileName);        convert.setCallback(this);    }    @Override    public File convertSuccess(Response response) throws Exception {        File file = convert.convertSuccess(response);        response.close();        return file;    }}

上传代码如下:

/**     * 文件上传     *     * @param view     */    public void upload(View view) {        List<File> files = new ArrayList<>();        if (imageItems != null && imageItems.size() > 0) {            for (ImageItem imageItem : imageItems) {                files.add(new File(imageItem.path));            }        }        try {            OkGo.post(Urls.URL_FORM_UPLOAD)                    .headers("header1", "heardvalue1")                    .headers("header2", "heardvalue2")                    .headers("header3", "heardvalue3") //看服务器那边要求去拼接了。                    .params("params1", "value1")                    .params("params2", "value2")                    .params("params3", "value3")                    .addFileParams("file", files) //传个集合,一个key,对应一个集合文件上传。                    //                    .params("file1",new File("imagePath1"))                    //                    .params("file2",new File("imagePath2"))                    //                    .params("file3",new File("imagePath3"))                    .execute(new StringCallback() {                        @Override                        public void onBefore(BaseRequest request) {                            super.onBefore(request);                            bt_upload.setText("正在上传中....");                        }                        @Override                        public void onSuccess(String s, Call call, Response response) {                            bt_upload.setText("上传成功");                            upload_speed.setText("--");                        }                        @Override                        public void onError(Call call, Response response, Exception e) {                            super.onError(call, response, e);                            bt_upload.setText("上传失败");                            upload_speed.setText("--");                        }                        @Override                        public void upProgress(long currentSize, long totalSize, float progress, long networkSpeed) {                            super.upProgress(currentSize, totalSize, progress, networkSpeed);                            String uploadSize=Formatter.formatFileSize(MainActivity.this,currentSize);                            String totalSizeNum=Formatter.formatFileSize(MainActivity.this,totalSize);                            upload_current_progress.setText(uploadSize+"/"+totalSizeNum);                            upload_percentage.setText((int) (progress * 100) + "%");                            String netSpeed = Formatter.formatFileSize(getApplicationContext(), networkSpeed);                            upload_speed.setText(netSpeed);                            upload_progressbar.setProgress((int) (progress * 100));                        }                    });        } catch (Exception e) {            e.printStackTrace();        }    }

这块和下载逻辑差不错,headersrparams都可以有多对,在上传时,服务端都会要求拼接许多header和一些params键值对,代码那里有标注,可以直接传一个list集合,里面放图片路径就可以了。图片的选择是跳到相册页,这里就用到了刚刚依赖的imagePicker

跳转到相册代码:

/**     * 去相册选择图片     *     * @param view     */    public void choose(View view) {        Intent intent = new Intent(this, ImageGridActivity.class);        startActivityForResult(intent, IMAGE_PICKER);    }

回调代码:

    @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        super.onActivityResult(requestCode, resultCode, data);        if (requestCode == IMAGE_PICKER && resultCode == ImagePicker.RESULT_CODE_ITEMS) {            if (data != null) {                StringBuilder sb = new StringBuilder();                imageItems = (ArrayList<ImageItem>) data.getSerializableExtra(ImagePicker.EXTRA_RESULT_ITEMS);                for (int i = 0; i < imageItems.size(); i++) {                    if (i == imageItems.size() - 1) {                        sb.append("图片" +(i+1) + ":" + imageItems.get(i).path);                    } else {                        sb.append("图片" + (i+1) + ":" + imageItems.get(i).path + ",");                    }                }                tv_image_path.setText(sb.toString());            }        }    }

这里需要注意的是,依赖的imagepicker需要配置初始化,不清楚点击看这里:
还可以按照我这个demo里的配置来,当然我也是按照它这里配置的:

public class MyApplication extends Application {    @Override    public void onCreate() {        super.onCreate();        ImagePicker imagePicker = ImagePicker.getInstance();        imagePicker.setImageLoader(new GlideImageLoad());   //设置图片加载器        imagePicker.setShowCamera(true);  //显示拍照按钮        imagePicker.setCrop(true);        //允许裁剪(单选才有效)        imagePicker.setSaveRectangle(true); //是否按矩形区域保存        imagePicker.setSelectLimit(9);    //选中数量限制        imagePicker.setStyle(CropImageView.Style.RECTANGLE);  //裁剪框的形状        imagePicker.setFocusWidth(800);   //裁剪框的宽度。单位像素(圆形自动取宽高最小值)        imagePicker.setFocusHeight(800);  //裁剪框的高度。单位像素(圆形自动取宽高最小值)        imagePicker.setOutPutX(1000);//保存文件的宽度。单位像素        imagePicker.setOutPutY(1000);//保存文件的高度。单位像素    }}

GlideImageLoad代码:

 @Override    public void displayImage(Activity activity, String path, ImageView imageView, int width, int height) {      //Glide的配置去加载图片      Glide.with(activity.getApplicationContext()).load(path)                .placeholder(R.mipmap.default_image)                .error(R.mipmap.default_image)                .override(width,height)                .into(imageView);    }    @Override    public void clearMemoryCache() {     //这里去清除缓存    }

这样配置就完成了,最后是xml文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical"    android:padding="2dp"    tools:context="user.example.com.okgosample.acitivity.MainActivity">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal"        android:padding="10dp"        >        <LinearLayout            android:layout_width="0dp"            android:layout_height="wrap_content"            android:orientation="horizontal"            android:layout_weight="1"            >            <TextView                android:id="@+id/tv_current_size"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="-"                />            <TextView                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="/"                />            <TextView                android:id="@+id/tv_total_size"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="-M"                />        </LinearLayout>        <TextView            android:id="@+id/tv_current_progress"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:text="--%"            android:layout_weight="1"            />        <TextView            android:id="@+id/tv_download_speed"            android:layout_width="0dp"            android:layout_height="wrap_content"            android:text="kb/s"            android:layout_weight="1"            />    </LinearLayout>    <ProgressBar        android:id="@+id/load_progressbar"        android:layout_width="match_parent"        android:layout_height="wrap_content"        style="?android:attr/progressBarStyleHorizontal"        android:max="100"        android:progress="0"        android:visibility="visible"        />    <Button        android:id="@+id/bt_download"        android:onClick="download"        android:text="down_load"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <ProgressBar        android:id="@+id/upload_progressbar"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginTop="20dp"        style="?android:attr/progressBarStyleHorizontal"        android:max="100"        android:progress="0"        android:visibility="visible"        />     <LinearLayout         android:layout_width="match_parent"         android:layout_height="wrap_content"         android:orientation="horizontal"         android:padding="10dp">         <TextView             android:id="@+id/upload_current_progress"             android:layout_width="0dp"             android:layout_height="wrap_content"             android:layout_weight="1"             android:text="-/M"             />         <TextView             android:id="@+id/upload_percentage"             android:layout_width="0dp"             android:layout_height="wrap_content"             android:layout_weight="1"             android:text="-%"             />         <TextView             android:id="@+id/upload_speed"             android:layout_width="0dp"             android:layout_height="wrap_content"             android:layout_weight="1"             android:text="kb/s"             />     </LinearLayout>    <Button        android:id="@+id/bt_choose_images"        android:onClick="choose"        android:text="选择图片"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <Button        android:id="@+id/bt_upload"        android:onClick="upload"        android:text="up_down"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <TextView        android:id="@+id/tv_image_path"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

最后再来张效果图吧,效果还不错: (这里是实现3张图片上传,和下载一个文件)


这里写图片描述

结论: okGo封装类中提供了许多强大的很实用的功能,具体需要哪块就去实现那块吧,demo就不上传了,可以把okGo源码clone下来,里面还是很详细。有问题就拍砖,多多指教~~

0 0
原创粉丝点击