【0104】【項目實戰】-【Android通用框架設計與完整電商APP開發】-【3】【高性能傻瓜式網絡請求框架設計(Retrofit+RxJava+Glide)】


1.網絡框架接口創建

1.1 使用的是第三方的框架

【第三方框架】Retrofit,封裝一個通用的框架,可以使用rxJava和rxAndroid進行封裝,比較難,這里不做講解;

1.2 restful 請求

【參考文章】http://www.ruanyifeng.com/blog/2014/05/restful_api

1.3網絡請求的具體實現類

【網絡請求的具體實現類】

【添加依賴】添加依賴可以使用兩種方式:【1】直接復制粘貼;【2】projectStucter尋找添加;

1     //網絡請求依賴
2     compile 'com.squareup.okio:okio:1.13.0'
3     compile 'com.squareup.okhttp3:okhttp:3.8.1'
4     compile 'com.squareup.retrofit2:retrofit:2.3.0'
5     compile 'com.squareup.retrofit2:converter-scalars:2.3.0' //以string 直接轉化來的,最直接的

 

【框架的搭建需要考慮的問題】明白需要使用什么模式,來什么要什么的是建造者模式是最好的。

 

【源碼】com.flj.latte.net.RestService接口的封裝

 1 package com.flj.latte.net;
 2 
 3 import java.util.WeakHashMap;
 4 
 5 import okhttp3.MultipartBody;
 6 import okhttp3.RequestBody;
 7 import okhttp3.ResponseBody;
 8 import retrofit2.Call;
 9 import retrofit2.http.Body;
10 import retrofit2.http.DELETE;
11 import retrofit2.http.FieldMap;
12 import retrofit2.http.FormUrlEncoded;
13 import retrofit2.http.GET;
14 import retrofit2.http.Multipart;
15 import retrofit2.http.POST;
16 import retrofit2.http.PUT;
17 import retrofit2.http.Part;
18 import retrofit2.http.QueryMap;
19 import retrofit2.http.Streaming;
20 import retrofit2.http.Url;
21 
22 /**
23  * Created by 傅令傑 on 2017/4/2
24  */
25 public interface RestService {
26 
27     @GET  //不傳遞任何的路由信息
28     Call<String> get(@Url String url, @QueryMap WeakHashMap<String, Object> params);   //QueryMap是以鍵值對的形式進行存儲的;
29 
30     /**
31      *
32      * @param url
33      * @param params
34      * @return
35      * FieldMap:請求體中包含的內容;
36      */
37     @FormUrlEncoded
38     @POST //不傳遞任何的路由信息
39     Call<String> post(@Url String url, @FieldMap WeakHashMap<String, Object> params);
40 
41     @POST
42     Call<String> postRaw(@Url String url, @Body RequestBody body);
43 
44     @FormUrlEncoded
45     @PUT
46     Call<String> put(@Url String url, @FieldMap WeakHashMap<String, Object> params);
47 
48     @PUT
49     Call<String> putRaw(@Url String url, @Body RequestBody body);
50 
51     @DELETE
52     Call<String> delete(@Url String url, @QueryMap WeakHashMap<String, Object> params);
53 
54     @Streaming //避免一次性將所有的文件下載下來,導致內存的溢出;但是在寫的時候,仍然需要將文件放在單獨的線程,否則在主線程操作任然會報錯。
55     @GET
56     Call<ResponseBody> download(@Url String url, @QueryMap WeakHashMap<String, Object> params);
57 
58     @Multipart
59     @POST
60     Call<String> upload(@Url String url, @Part MultipartBody.Part file);
61 }

  【封裝枚舉類】

【傳入ip地址】

【RetrofitHolder創建成功】構建OkHttp請求

1.5 【restService的創建】

1.6 創建get方法

2.Restful請求的處理-框架

【說明】首先要考慮網絡請求的參數(url傳入的值、文件、回調、及loder加載圈)

【說明】使用建造者模式,將建造者類和宿主類分開;

2.1【新建建造者類】

2.2【restClient類的參數的定義】

【restClient類的參數的定義】restClient類在每次Builder的時候會生成全新的實例,而里面的參數一次更改完畢,不允許二次更改;

 

2.3 【回調類】

【回調類】在網路請求之后,會存在網絡請求之后的回調,比如:請求失敗、請求異常、請求成功等;

[新建CallBack包,書寫需要調用的接口]

 

2.4 完善RestClient

【完善com.flj.latte.net.RestClient】以Builder的形式構造出來了;

2.5 RestClientBuilder 對數據的設置

【說明】主要完成的數據的傳遞

 

 

2.6 RestClient的調用

2.7 RestClientBuilder的改進

 2.7.1【改進1】【mParams】參數每次都會構建,比較繁瑣;

 

【優化1】

【優化方法2】

【client修改】

2.8 requset請求

【新建callBack類】新建類並實現實現接口, 復寫方法;

【部分源碼】com.flj.latte.net.callback.RequestCallbacks

 1 package com.flj.latte.net.callback;
 2 
 3 import android.os.Handler;
 4 
 5 import com.flj.latte.app.ConfigKeys;
 6 import com.flj.latte.app.Latte;
 7 import com.flj.latte.net.RestCreator;
 8 import com.flj.latte.ui.loader.LatteLoader;
 9 import com.flj.latte.ui.loader.LoaderStyle;
10 
11 import retrofit2.Call;
12 import retrofit2.Callback;
13 import retrofit2.Response;
18 
19 public final class RequestCallbacks implements Callback<String> {
20 
21     private final IRequest REQUEST;
22     private final ISuccess SUCCESS;
23     private final IFailure FAILURE;
24     private final IError ERROR;
25     private final LoaderStyle LOADER_STYLE;
26     private static final Handler HANDLER = Latte.getHandler();
27 
28     public RequestCallbacks(IRequest request, ISuccess success, IFailure failure, IError error, LoaderStyle style) {
29         this.REQUEST = request;
30         this.SUCCESS = success;
31         this.FAILURE = failure;
32         this.ERROR = error;
33         this.LOADER_STYLE = style;
34     }
35 
36     @Override
37     public void onResponse(Call<String> call, Response<String> response) {
38         if (response.isSuccessful()) {
39             if (call.isExecuted()) {
40                 if (SUCCESS != null) {
41                     SUCCESS.onSuccess(response.body());
42                 }
43             }
44         } else {
45             if (ERROR != null) {
46                 ERROR.onError(response.code(), response.message());
47             }
48         }
49 
50         onRequestFinish();
51     }
52 
53     @Override
54     public void onFailure(Call<String> call, Throwable t) {
55         if (FAILURE != null) {
56             FAILURE.onFailure();
57         }
58         if (REQUEST != null) {
59             REQUEST.onRequestEnd();
60         }
61 
62         onRequestFinish();
63     }

 【完善RestClient】 

 

2.9 使用方法

【測試】

【增加權限】

【測試】

【效果】通過get請求返回了數據

3. oading框架集成與完善AVLoadingIndicatorView

3.1 第三方框架的效果

【地址】https://github.com/81813780/AVLoadingIndicatorView

【說明】在該地址中已經存在怎樣使用的步驟;

3.2 集成封裝獲取某種類型的View

【添加依賴】

【說明】各種的效果的獲取使用過的是反射的技術,但是反復使用反射會影響設備的性能;因此做了一個機制的封裝

【原理】以一種緩存的方式創建loader,不需要每次使用loader的時候進行反射,這樣性能會有很大幅度的提高。

3.3 不同的style的枚舉的封裝

【對不同的類型進行封裝】com.flj.latte.ui.loader.LoaderStyle

 1 package com.flj.latte.ui.loader;
 2 
 3 
 4 @SuppressWarnings("unused")
 5 public enum LoaderStyle {
 6     BallPulseIndicator,
 7     BallGridPulseIndicator,
 8     BallClipRotateIndicator,
 9     BallClipRotatePulseIndicator,
10     SquareSpinIndicator,
11     BallClipRotateMultipleIndicator,
12     BallPulseRiseIndicator,
13     BallRotateIndicator,
14     CubeTransitionIndicator,
15     BallZigZagIndicator,
16     BallZigZagDeflectIndicator,
17     BallTrianglePathIndicator,
18     BallScaleIndicator,
19     LineScaleIndicator,
20     LineScalePartyIndicator,
21     BallScaleMultipleIndicator,
22     BallPulseSyncIndicator,
23     BallBeatIndicator,
24     LineScalePulseOutIndicator,
25     LineScalePulseOutRapidIndicator,
26     BallScaleRippleIndicator,
27     BallScaleRippleMultipleIndicator,
28     BallSpinFadeLoaderIndicator,
29     LineSpinFadeLoaderIndicator,
30     TriangleSkewSpinIndicator,
31     PacmanIndicator,
32     BallGridBeatIndicator,
33     SemiCircleSpinIndicator,
34     CustomIndicator
35 }

3.4 對傳入的樣式/參數封裝

【樣式的封裝】需要封裝是否需要透明度、顏色等值的傳入;

【傳入樣式參數並作為根布局】

3.5 工具類-填充參數的設置

【工具類】新建工具類的權限一般 放為:public static;

3.6 繼續完善類

【設置縮放比例】為了適應 不同的設備的屏幕的不同的大小,需要對加載的loader進行縮放

【創建集合,統一管理不同類型的loader】【學習思想】在不需要loaders的時候,只要遍歷集合,一一的關閉loaders即可;

【提供默認的loaders樣式】

【關閉和顯示對話框的loader】

【說明】此處使用的dialog.cancle(),因為在關閉dialog之后會調用onCancle()方法,可以響應在關閉對話框之后的一些動作;

              dialog.dismiss():直接關閉對話框,沒有響應;

3.7 網絡請求中加入loader

【client】

【builder】

 

【說明】handler聲明的時候加關鍵字static;可以避免內存泄露;

【測試】

 【增加失敗時候關閉loader】

4.網絡框架優化與完善

4.1【支持原始數據的post請求】

4.2【支持原始數據的putRaw】

4.3【增加UPLOAD上傳方法】

【會存在文件的傳遞】

5.文件下載功能設計與實現

【說明】比較復雜;

【增加參數】

 

【新建downloadhandler】新建下載處理類;

【使用異步的下載方法】

【file工具類】沒有講解,課下編寫的

  1 package com.flj.latte.util.file;
  2 
  3 import android.content.ContentResolver;
  4 import android.content.Context;
  5 import android.content.Intent;
  6 import android.content.res.AssetManager;
  7 import android.database.Cursor;
  8 import android.graphics.Bitmap;
  9 import android.graphics.Typeface;
 10 import android.media.MediaScannerConnection;
 11 import android.net.Uri;
 12 import android.os.Build;
 13 import android.os.Environment;
 14 import android.provider.MediaStore;
 15 import android.webkit.MimeTypeMap;
 16 import android.widget.TextView;
 17 
 18 import com.flj.latte.app.Latte;
 19 
 20 import java.io.BufferedInputStream;
 21 import java.io.BufferedOutputStream;
 22 import java.io.BufferedReader;
 23 import java.io.File;
 24 import java.io.FileNotFoundException;
 25 import java.io.FileOutputStream;
 26 import java.io.IOException;
 27 import java.io.InputStream;
 28 import java.io.InputStreamReader;
 29 import java.text.SimpleDateFormat;
 30 import java.util.Date;
 31 import java.util.Locale;
 32 
 33 
 34 public final class FileUtil {
 35 
 36     //格式化的模板
 37     private static final String TIME_FORMAT = "_yyyyMMdd_HHmmss";
 38 
 39     private static final String SDCARD_DIR =
 40             Environment.getExternalStorageDirectory().getPath();
 41 
 42     //默認本地上傳圖片目錄
 43     public static final String UPLOAD_PHOTO_DIR =
 44             Environment.getExternalStorageDirectory().getPath() + "/a_upload_photos/";
 45 
 46     //網頁緩存地址
 47     public static final String WEB_CACHE_DIR =
 48             Environment.getExternalStorageDirectory().getPath() + "/app_web_cache/";
 49 
 50     //系統相機目錄
 51     public static final String CAMERA_PHOTO_DIR =
 52             Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath() + "/Camera/";
 53 
 54     private static String getTimeFormatName(String timeFormatHeader) {
 55         final Date date = new Date(System.currentTimeMillis());
 56         //必須要加上單引號
 57         final SimpleDateFormat dateFormat = new SimpleDateFormat("'" + timeFormatHeader + "'" + TIME_FORMAT, Locale.getDefault());
 58         return dateFormat.format(date);
 59     }
 60 
 61     /**
 62      * @param timeFormatHeader 格式化的頭(除去時間部分)
 63      * @param extension        后綴名
 64      * @return 返回時間格式化后的文件名
 65      */
 66     public static String getFileNameByTime(String timeFormatHeader, String extension) {
 67         return getTimeFormatName(timeFormatHeader) + "." + extension;
 68     }
 69 
 70     @SuppressWarnings("ResultOfMethodCallIgnored")
 71     private static File createDir(String sdcardDirName) {
 72         //拼接成SD卡中完整的dir
 73         final String dir = SDCARD_DIR + "/" + sdcardDirName + "/";
 74         final File fileDir = new File(dir);
 75         if (!fileDir.exists()) {
 76             fileDir.mkdirs();
 77         }
 78         return fileDir;
 79     }
 80 
 81     @SuppressWarnings("ResultOfMethodCallIgnored")
 82     public static File createFile(String sdcardDirName, String fileName) {
 83         return new File(createDir(sdcardDirName), fileName);
 84     }
 85 
 86     private static File createFileByTime(String sdcardDirName, String timeFormatHeader, String extension) {
 87         final String fileName = getFileNameByTime(timeFormatHeader, extension);
 88         return createFile(sdcardDirName, fileName);
 89     }
 90 
 91     //獲取文件的MIME
 92     public static String getMimeType(String filePath) {
 93         final String extension = getExtension(filePath);
 94         return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
 95     }
 96 
 97     //獲取文件的后綴名
 98     public static String getExtension(String filePath) {
 99         String suffix = "";
100         final File file = new File(filePath);
101         final String name = file.getName();
102         final int idx = name.lastIndexOf('.');
103         if (idx > 0) {
104             suffix = name.substring(idx + 1);
105         }
106         return suffix;
107     }
108 
109     /**
110      * 保存Bitmap到SD卡中
111      *
112      * @param dir      目錄名,只需要寫自己的相對目錄名即可
113      * @param compress 壓縮比例 100是不壓縮,值約小壓縮率越高
114      * @return 返回該文件
115      */
116     public static File saveBitmap(Bitmap mBitmap, String dir, int compress) {
117 
118         final String sdStatus = Environment.getExternalStorageState();
119         // 檢測sd是否可用
120         if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) {
121             return null;
122         }
123         FileOutputStream fos = null;
124         BufferedOutputStream bos = null;
125         File fileName = createFileByTime(dir, "DOWN_LOAD", "jpg");
126         try {
127             fos = new FileOutputStream(fileName);
128             bos = new BufferedOutputStream(fos);
129             mBitmap.compress(Bitmap.CompressFormat.JPEG, compress, bos);// 把數據寫入文件
130         } catch (FileNotFoundException e) {
131             e.printStackTrace();
132         } finally {
133             try {
134 
135                 if (bos != null) {
136                     bos.flush();
137                 }
138                 if (bos != null) {
139                     bos.close();
140                 }
141                 //關閉流
142                 if (fos != null) {
143                     fos.flush();
144                 }
145                 if (fos != null) {
146                     fos.close();
147                 }
148             } catch (IOException e) {
149                 e.printStackTrace();
150             }
151         }
152         refreshDCIM();
153 
154         return fileName;
155     }
156 
157     public static File writeToDisk(InputStream is, String dir, String name) {
158         final File file = FileUtil.createFile(dir, name);
159         BufferedInputStream bis = null;
160         FileOutputStream fos = null;
161         BufferedOutputStream bos = null;
162 
163         try {
164             bis = new BufferedInputStream(is);
165             fos = new FileOutputStream(file);
166             bos = new BufferedOutputStream(fos);
167 
168             byte data[] = new byte[1024 * 4];
169 
170             int count;
171             while ((count = bis.read(data)) != -1) {
172                 bos.write(data, 0, count);
173             }
174 
175             bos.flush();
176             fos.flush();
177 
178 
179         } catch (IOException e) {
180             e.printStackTrace();
181         } finally {
182             try {
183                 if (bos != null) {
184                     bos.close();
185                 }
186                 if (fos != null) {
187                     fos.close();
188                 }
189                 if (bis != null) {
190                     bis.close();
191                 }
192                 is.close();
193             } catch (IOException e) {
194                 e.printStackTrace();
195             }
196         }
197 
198         return file;
199     }
200 
201     public static File writeToDisk(InputStream is, String dir, String prefix, String extension) {
202         final File file = FileUtil.createFileByTime(dir, prefix, extension);
203         BufferedInputStream bis = null;
204         FileOutputStream fos = null;
205         BufferedOutputStream bos = null;
206 
207         try {
208             bis = new BufferedInputStream(is);
209             fos = new FileOutputStream(file);
210             bos = new BufferedOutputStream(fos);
211 
212             byte data[] = new byte[1024 * 4];
213 
214             int count;
215             while ((count = bis.read(data)) != -1) {
216                 bos.write(data, 0, count);
217             }
218 
219             bos.flush();
220             fos.flush();
221 
222 
223         } catch (IOException e) {
224             e.printStackTrace();
225         } finally {
226             try {
227                 if (bos != null) {
228                     bos.close();
229                 }
230                 if (fos != null) {
231                     fos.close();
232                 }
233                 if (bis != null) {
234                     bis.close();
235                 }
236                 is.close();
237             } catch (IOException e) {
238                 e.printStackTrace();
239             }
240         }
241 
242         return file;
243     }
244 
245     /**
246      * 通知系統刷新系統相冊,使照片展現出來
247      */
248     private static void refreshDCIM() {
249         if (Build.VERSION.SDK_INT >= 19) {
250             //兼容android4.4版本,只掃描存放照片的目錄
251             MediaScannerConnection.scanFile(Latte.getApplicationContext(),
252                     new String[]{Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath()},
253                     null, null);
254         } else {
255             //掃描整個SD卡來更新系統圖庫,當文件很多時用戶體驗不佳,且不適合4.4以上版本
256             Latte.getApplicationContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" +
257                     Environment.getExternalStorageDirectory())));
258         }
259     }
260 
261     /**
262      * 讀取raw目錄中的文件,並返回為字符串
263      */
264     public static String getRawFile(int id) {
265         final InputStream is = Latte.getApplicationContext().getResources().openRawResource(id);
266         final BufferedInputStream bis = new BufferedInputStream(is);
267         final InputStreamReader isr = new InputStreamReader(bis);
268         final BufferedReader br = new BufferedReader(isr);
269         final StringBuilder stringBuilder = new StringBuilder();
270         String str;
271         try {
272             while ((str = br.readLine()) != null) {
273                 stringBuilder.append(str);
274             }
275         } catch (IOException e) {
276             e.printStackTrace();
277         } finally {
278             try {
279                 br.close();
280                 isr.close();
281                 bis.close();
282                 is.close();
283             } catch (IOException e) {
284                 e.printStackTrace();
285             }
286         }
287         return stringBuilder.toString();
288     }
289 
290 
291     public static void setIconFont(String path, TextView textView) {
292         final Typeface typeface = Typeface.createFromAsset(Latte.getApplicationContext().getAssets(), path);
293         textView.setTypeface(typeface);
294     }
295 
296     /**
297      * 讀取assets目錄下的文件,並返回字符串
298      */
299     public static String getAssetsFile(String name) {
300         InputStream is = null;
301         BufferedInputStream bis = null;
302         InputStreamReader isr = null;
303         BufferedReader br = null;
304         StringBuilder stringBuilder = null;
305         final AssetManager assetManager = Latte.getApplicationContext().getAssets();
306         try {
307             is = assetManager.open(name);
308             bis = new BufferedInputStream(is);
309             isr = new InputStreamReader(bis);
310             br = new BufferedReader(isr);
311             stringBuilder = new StringBuilder();
312             String str;
313             while ((str = br.readLine()) != null) {
314                 stringBuilder.append(str);
315             }
316         } catch (IOException e) {
317             e.printStackTrace();
318         } finally {
319             try {
320                 if (br != null) {
321                     br.close();
322                 }
323                 if (isr != null) {
324                     isr.close();
325                 }
326                 if (bis != null) {
327                     bis.close();
328                 }
329                 if (is != null) {
330                     is.close();
331                 }
332                 assetManager.close();
333             } catch (IOException e) {
334                 e.printStackTrace();
335             }
336         }
337         if (stringBuilder != null) {
338             return stringBuilder.toString();
339         } else {
340             return null;
341         }
342     }
343 
344     public static String getRealFilePath(final Context context, final Uri uri) {
345         if (null == uri) return null;
346         final String scheme = uri.getScheme();
347         String data = null;
348         if (scheme == null)
349             data = uri.getPath();
350         else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
351             data = uri.getPath();
352         } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
353             final Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
354             if (null != cursor) {
355                 if (cursor.moveToFirst()) {
356                     final int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
357                     if (index > -1) {
358                         data = cursor.getString(index);
359                     }
360                 }
361                 cursor.close();
362             }
363         }
364         return data;
365     }
366 }

 【保存文件類的封裝】

 

【繼續封裝com.flj.latte.net.download.DownloadHandler】

【調用的方法】在后面的使用文件下載然后更新應用程序;

【提交代碼】

 

6.攔截器功能設計與實現之攔截器的初始化

【說明】沒有搭建服務器,然后使用okhttp庫中的攔截功能,將接收到請求之后做出響應,返回json文件;

6.1【配置文件中的攔截器的配置】

 

6.2 將配置文件中的interceptors請求配置到okhttp

【接收請求】

7.攔截器功能設計與實現之模擬請求

【說明】模擬服務器:獲取傳入的參數,

【get方法】則:從url獲取參數;

【post方法】:從請求體中獲取參數;

7.1 基類封裝

 

7.3 調試類封裝

 

 1 package com.flj.latte.net.interceptors;
 2 
 3 import android.support.annotation.NonNull;
 4 import android.support.annotation.RawRes;
 5 
 6 import com.flj.latte.util.file.FileUtil;
 7 
 8 import java.io.IOException;
 9 
10 import okhttp3.MediaType;
11 import okhttp3.Protocol;
12 import okhttp3.Response;
13 import okhttp3.ResponseBody;
14 
15 
16 public class DebugInterceptor extends BaseInterceptor {
17 
18     private final String DEBUG_URL;
19     private final int DEBUG_RAW_ID;
20 
21     public DebugInterceptor(String debugUrl, int rawId) {
22         this.DEBUG_URL = debugUrl;
23         this.DEBUG_RAW_ID = rawId;
24     }
25 
26     private Response getResponse(Chain chain, String json) {
27         return new Response.Builder()
28                 .code(200)
29                 .addHeader("Content-Type", "application/json")
30                 .body(ResponseBody.create(MediaType.parse("application/json"), json))
31                 .message("OK")
32                 .request(chain.request())
33                 .protocol(Protocol.HTTP_1_1)
34                 .build();
35     }
36 
37     //debug的封裝,根據rawId查詢獲取json;
38     private Response debugResponse(Chain chain, @RawRes int rawId) {
39         final String json = FileUtil.getRawFile(rawId); //根據rawId取出原始文件;
40         return getResponse(chain, json);//返回Response請求的響應;
41     }
42 
43     /**
44      * 說明:此時存在的json文件是存在在單個應用程序的res/raw文件夾下的json;
45      * @param chain
46      * @return
47      * @throws IOException
48      */
49     @Override
50     public Response intercept(@NonNull Chain chain) throws IOException {
51         final String url = chain.request().url().toString(); //得到攔截的url;
52         if (url.contains(DEBUG_URL)) { //攔截的url包含了DEBUG_URL,返回存在的json文件;
53             return debugResponse(chain, DEBUG_RAW_ID);
54         }
55         return chain.proceed(chain.request()); //否則原樣返回數據;
56     }
57 }

 7.4 使用

【效果】就可以不適用服務器的數據,直接可以在本地進行json數據的加載和測試;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM