【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数据的加载和测试;