在上一次https://www.cnblogs.com/webor2006/p/12445157.html中已经对于MVP的基础框架进行了搭建,但是呢该框架还少了很多东东,所以这次打算进一步完善一下,封装一个核心Module。
App基础配置封装:
框架搭建:
新建一个核心Module,所以基础配置都放到这个下面:
此时添加依赖关系:
而由于app又依赖于isolation_processor层,所以也传递依赖于core。
然后建一个包,对于APP的基础配置都放在其中:
ConfigKeys:配置类型定义
然后这里先为建立一个枚举类,里面标识着基本配置的类别,如下:
package com.android.core.app; /** * 只是一个标识信息,这里的所有信息都是后面用来做KEY的 */ public enum ConfigKeys { API_HOST, APPLICATION_CONTEXT, INTERCEPTOR, //标识配置是否可用 CONFIG_READY }
有啥用?接着往下写就明白了,接下来则来建立一下配置类,里面则是具体配置存放获取的实现,如下:
Configurator.java:APP配置写入和获取
先将其定义为单例,这里采用枚举的方式来创建:
接一来则来定义一些操作配置的方法,比较简单,直接贴出:
package com.android.core.app; import java.util.HashMap; public class Configurator { private static final HashMap<Object, Object> CONFIGS = new HashMap<>(); //单例:java并发实战中推的方法 private static class Holder { private static final Configurator INSTANCE = new Configurator(); } private Configurator() { CONFIGS.put(ConfigKeys.CONFIG_READY.name(), false); } public static Configurator getInstance() { return Holder.INSTANCE; } //获取配置信息 final HashMap<Object, Object> getConfigs() { return CONFIGS; } //配置APIHOST public final Configurator withApiHost(String host) { CONFIGS.put(ConfigKeys.API_HOST, host); return this; } //配置完成 public final void configure() { CONFIGS.put(ConfigKeys.CONFIG_READY.name(), true); } //检查配置是否完成 private void checkConfiguration() { final boolean isReady = (boolean) CONFIGS.get(ConfigKeys.CONFIG_READY.name()); if (!isReady) { throw new RuntimeException("Configuration is not ready,call configure()"); } } final <T> T getConfiguration(Object key) { checkConfiguration(); final Object value = CONFIGS.get(key); if (value == null) { throw new NullPointerException(key.toString() + "IS NULL"); } return (T) value; } }
其中这块用到了一个简单的建造者模式【当然不是标准的】可以链式的来调用,如下:
ProjectInit.java:此类是开放给应用层来进行调用的。
package com.android.core.app; import android.content.Context; /** * 这里就是给用户的API */ public class ProjectInit { public static Configurator init(Context context) { Configurator.getInstance() .getConfigs() .put(ConfigKeys.APPLICATION_CONTEXT.name(), context.getApplicationContext()); return Configurator.getInstance(); } public static Configurator getConfigurator() { return Configurator.getInstance(); } public static <T> T getConfiguration(Object key) { return getConfigurator().getConfiguration(key); } public static Context getApplicationContext() { return getConfiguration(ConfigKeys.APPLICATION_CONTEXT.name()); } }
对于上面的这个类中定义的方法貌似有点啰嗦,但是都是为了方便应用层来调用的,接下来咱们就可以来进行调用了,如下:
Okhttp+Retrofit封装:
接下来则来封装一下Okhttp+Retrofit网络请求功能,先来新建一个包,所有关于网络请求的都放其中:
既然要用到Retrofit,则先添加相关的依赖:
而OkHttp在上一次的隔离层已经添加了,但是由于隔离层是依赖于这个core模块的,所以这里将隔离层的OkHttp的依赖挪到这个core层上来:
说到Retrofit,肯定得要先定义接口嘛,所以这里定义一下:
RestService.java:定义网络请口
package com.android.core.net; import java.util.Map; import okhttp3.MultipartBody; import okhttp3.RequestBody; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.DELETE; import retrofit2.http.FieldMap; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.Multipart; import retrofit2.http.POST; import retrofit2.http.PUT; import retrofit2.http.Part; import retrofit2.http.QueryMap; import retrofit2.http.Streaming; import retrofit2.http.Url; public interface RestService { @GET Call<String> get(@Url String url, @QueryMap Map<String, Object> params); @FormUrlEncoded @POST Call<String> post(@Url String url, @FieldMap Map<String, Object> params); @FormUrlEncoded @PUT Call<String> put(@Url String url, @FieldMap Map<String, Object> params); @DELETE Call<String> delete(@Url String url, @QueryMap Map<String, Object> params); //下载是直接到内存,所以需要 @Streaming @Streaming @GET Call<ResponseBody> download(@Url String url, @QueryMap Map<String, Object> params); //上传 @Multipart @POST Call<String> upload(@Url String url, @Part MultipartBody.Part file); //原始数据 @POST Call<String> postRaw(@Url String url, @Body RequestBody body); @PUT Call<String> putRaw(@Url String url, @Body RequestBody body); }
RestCreator.java:Retrofit对象封装
这里可以看一下具体的写法,有一些技巧的,用静态内部类的方式来分层次的进行封装,既清晰又灵活,具体如下:
package com.android.core.net; import com.android.core.app.ConfigKeys; import com.android.core.app.ProjectInit; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import retrofit2.Retrofit; import retrofit2.converter.scalars.ScalarsConverterFactory; public final class RestCreator { /** * 产生一个全局的retrofit客户端 */ private static final class RetrofitHolder { private static final String BASE_URL = ProjectInit.getConfiguration(ConfigKeys.API_HOST); private static final Retrofit RETROFIT_CLIENT = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(ScalarsConverterFactory.create()) .client(OkHttpHolder.OK_HTTP_CLIENT) .build(); } /** * 可以单独设置的okhttp */ private static final class OkHttpHolder { private static final int TIME_OUT = 60; private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder() .connectTimeout(TIME_OUT, TimeUnit.SECONDS) .build(); } //提供一个接口让调用者得到retrofit对象 private static final class RestServiceHolder { private static final RestService REST_SERVICE = RetrofitHolder.RETROFIT_CLIENT .create(RestService.class); } /** * 获取对象 */ public static RestService getRestService() { return RestServiceHolder.REST_SERVICE; } }
RestClient:具体用户调用网络客户端的逻辑封装
这里面的代码量稍稍大一点,但也是比较好理解的,所以不一一细说了,重点是掌握其封装方法,这里面先定义相关的一些字段:
package com.android.core.net; import com.android.core.net.callback.IError; import com.android.core.net.callback.IFailure; import com.android.core.net.callback.IRequest; import com.android.core.net.callback.ISuccess; import java.io.File; import java.util.HashMap; import okhttp3.RequestBody; public class RestClient { private final HashMap<String, Object> PARAMS; private final String URL; private final IRequest REQUEST; private final ISuccess SUCCESS; private final IFailure FAILURE; private final IError ERROR; private final RequestBody BODY; //上传下载 private final File FILE; private final String DOWNLOAD_DIR; private final String EXTENSION; private final String FILENAME; public RestClient(HashMap<String, Object> params, String url, IRequest request, ISuccess success, IFailure failure, IError error, RequestBody body, File file, String downloadDir, String extension, String filename) { this.PARAMS = params; this.URL = url; this.REQUEST = request; this.SUCCESS = success; this.FAILURE = failure; this.ERROR = error; this.BODY = body; this.FILE = file; this.DOWNLOAD_DIR = downloadDir; ///sdcard/XXXX.ext this.EXTENSION = extension; this.FILENAME = filename; } }
其中有四个回调方法,所以定义一下:
package com.android.core.net.callback; public interface IError { void onError(int code, String msg); }
package com.android.core.net.callback; public interface IFailure { void onFailure(); }
package com.android.core.net.callback; public interface IRequest { void onRequestStart(); void onRequestEnd(); }
package com.android.core.net.callback; public interface ISuccess { void onSuccess(String responce); }
然后此时就可以再定义一个Okhttp的请求回调,将上面的各回调进行整合一下:
package com.android.core.net.callback; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class RequestCallbacks implements Callback<String> { private final IRequest REQUEST; private final ISuccess SUCCESS; private final IFailure FAILURE; private final IError ERROR; public RequestCallbacks(IRequest request, ISuccess success, IFailure failure, IError error) { this.REQUEST = request; this.SUCCESS = success; this.FAILURE = failure; this.ERROR = error; } @Override public void onResponse(Call<String> call, Response<String> response) { if (response.isSuccessful()) { if (call.isExecuted()) { if (SUCCESS != null) { SUCCESS.onSuccess(response.body()); } } } else { if (ERROR != null) { ERROR.onError(response.code(), response.message()); } } } @Override public void onFailure(Call<String> call, Throwable t) { if (FAILURE != null) { FAILURE.onFailure(); } if (REQUEST != null) { REQUEST.onRequestEnd(); } } }
接下来则还是回到RestClient继续,既然它里面有这么多参数,要构建的话是不是用构建者模式相当合适,是的,所以下面来定义一下:
package com.android.core.net; import com.android.core.net.callback.IError; import com.android.core.net.callback.IFailure; import com.android.core.net.callback.IRequest; import com.android.core.net.callback.ISuccess; import java.io.File; import java.util.HashMap; import okhttp3.MediaType; import okhttp3.RequestBody; public class RestClientBuilder { private HashMap<String, Object> mParams; private String mUrl; private IRequest mRequest; private ISuccess mSuccess; private IFailure mFailure; private IError mError; private RequestBody mBody; //上传下载 private File mFile; private String mDownloadDir; private String mExtension; private String mFilename; RestClientBuilder() { } public final RestClientBuilder url(String url) { this.mUrl = url; return this; } public final RestClientBuilder params(HashMap<String, Object> params) { this.mParams = params; return this; } public final RestClientBuilder success(ISuccess success) { this.mSuccess = success; return this; } public final RestClientBuilder request(IRequest request) { this.mRequest = request; return this; } public final RestClientBuilder error(IError error) { this.mError = error; return this; } public final RestClientBuilder failure(IFailure failure) { this.mFailure = failure; return this; } public final RestClientBuilder raw(String raw) { this.mBody = RequestBody.create( MediaType.parse("application/json;charset=UTF-8"), raw); return this; } //上传 public final RestClientBuilder file(File file) { this.mFile = file; return this; } public final RestClientBuilder file(String file) { this.mFile = new File(file); return this; } //下载 public final RestClientBuilder dir(String dir) { this.mDownloadDir = dir; return this; } public final RestClientBuilder extension(String extension) { this.mExtension = extension; return this; } public final RestClientBuilder filename(String filename) { this.mFilename = filename; return this; } public final RestClient build() { return new RestClient(mParams, mUrl, mRequest, mSuccess, mFailure, mError, mBody, mFile, mDownloadDir, mExtension, mFilename); } }
接下来则需要封装一个真正请求的方法,具体代码也直接贴出来,比较好理解:
package com.android.core.net; import com.android.core.net.callback.IError; import com.android.core.net.callback.IFailure; import com.android.core.net.callback.IRequest; import com.android.core.net.callback.ISuccess; import com.android.core.net.callback.RequestCallbacks; import java.io.File; import java.util.HashMap; import okhttp3.MultipartBody; import okhttp3.RequestBody; import retrofit2.Call; import retrofit2.Callback; public class RestClient { private final HashMap<String, Object> PARAMS; private final String URL; private final IRequest REQUEST; private final ISuccess SUCCESS; private final IFailure FAILURE; private final IError ERROR; private final RequestBody BODY; //上传下载 private final File FILE; private final String DOWNLOAD_DIR; private final String EXTENSION; private final String FILENAME; public RestClient(HashMap<String, Object> params, String url, IRequest request, ISuccess success, IFailure failure, IError error, RequestBody body, File file, String downloadDir, String extension, String filename) { this.PARAMS = params; this.URL = url; this.REQUEST = request; this.SUCCESS = success; this.FAILURE = failure; this.ERROR = error; this.BODY = body; this.FILE = file; this.DOWNLOAD_DIR = downloadDir; ///sdcard/XXXX.ext this.EXTENSION = extension; this.FILENAME = filename; } public static RestClientBuilder create() { return new RestClientBuilder(); } private Callback<String> getRequestCallback() { return new RequestCallbacks(REQUEST, SUCCESS, FAILURE, ERROR); } //开始真实的网络操作 参数HTTP_METHOD.GET HTTP_METHOD.POST...... private void request(HttpMethod method) { final RestService service = RestCreator.getRestService(); Call<String> call = null; if (REQUEST != null) { REQUEST.onRequestStart(); } //开始进行网络访问 switch (method) { case GET: call = service.get(URL, PARAMS); break; case POST: call = service.post(URL, PARAMS); break; case PUT: call = service.put(URL, PARAMS); break; case DELETE: call = service.delete(URL, PARAMS); break; case UPLOAD:
final RequestBody requestBody = RequestBody.create(MultipartBody.FROM, FILE);
final MultipartBody.Part body = MultipartBody.Part.createFormData( "file", FILE.getName(), requestBody); call = service.upload(URL, body); break; } if (call != null) { call.enqueue(getRequestCallback()); } if (REQUEST != null) { REQUEST.onRequestEnd(); } } //各种请求(给用户使用的) //各种请求 public final void get() { request(HttpMethod.GET); } public final void post() { request(HttpMethod.POST); } public final void put() { request(HttpMethod.PUT); } public final void delete() { request(HttpMethod.DELETE); } public final void upload() { request(HttpMethod.UPLOAD); } }
其中HttpMethod是一个枚举:
package com.android.core.net; enum HttpMethod { GET, POST, POST_RAW, PUT, PUT_RAW, DELETE, UPLOAD, DOWNLOAD }
下载文件功能封装:
接下来再增加网络下载功能,细节也不多说了,贴出代码:
package com.android.core.download; import android.os.AsyncTask; import com.android.core.net.RestCreator; import com.android.core.net.callback.IError; import com.android.core.net.callback.IFailure; import com.android.core.net.callback.IRequest; import com.android.core.net.callback.ISuccess; import java.util.HashMap; import okhttp3.ResponseBody; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class DownloadHandler { private final HashMap<String, Object> PARAMS; private final String URL; private final IRequest REQUEST; private final ISuccess SUCCESS; private final IFailure FAILURE; private final IError ERROR; private final String DOWNLOAD_DIR; private final String EXTENSION; private final String FILENAME; public DownloadHandler(HashMap<String, Object> params, String url, IRequest request, ISuccess success, IFailure failure, IError error, String downloadDir, String extension, String filename) { this.PARAMS = params; this.URL = url; this.REQUEST = request; this.SUCCESS = success; this.FAILURE = failure; this.ERROR = error; this.DOWNLOAD_DIR = downloadDir; this.EXTENSION = extension; this.FILENAME = filename; } /** * 下载文件 */ public final void handleDownload() { RestCreator.getRestService().download(URL, PARAMS) .enqueue(new Callback<ResponseBody>() { @Override public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) { if (response.isSuccessful()) { //开始把这次下载的结果保存到文件中,使用线程 SaveFileTask task = new SaveFileTask(REQUEST, SUCCESS); task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, DOWNLOAD_DIR, EXTENSION, response.body(), FILENAME); //如果下载成功了,就告诉用户 if (task.isCancelled()) { if (REQUEST != null) { REQUEST.onRequestEnd(); } } } else { if (ERROR != null) { ERROR.onError(response.code(), response.message()); } } } @Override public void onFailure(Call<ResponseBody> call, Throwable t) { FAILURE.onFailure(); } }); } }
package com.android.core.download; import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import com.android.core.app.ProjectInit; import com.android.core.file.FileUtil; import com.android.core.net.callback.IRequest; import com.android.core.net.callback.ISuccess; import java.io.File; import java.io.InputStream; import okhttp3.ResponseBody; public class SaveFileTask extends AsyncTask<Object, Void, File> { private IRequest REQUEST; private ISuccess SUCCESS; public SaveFileTask(IRequest REQUEST, ISuccess SUCCESS) { this.REQUEST = REQUEST; this.SUCCESS = SUCCESS; } @Override protected File doInBackground(Object... params) { String downloadDir=(String)params[0]; String extension=(String)params[1]; ResponseBody body=(ResponseBody)params[2]; String name=(String)params[3]; InputStream is=body.byteStream(); if(downloadDir==null || downloadDir.equals("")){ downloadDir="downloads"; } if(extension==null){ extension=""; } if(name==null){ return FileUtil.writeToDisk(is,downloadDir,extension.toUpperCase(),extension); }else{ return FileUtil.writeToDisk(is,downloadDir,name); } } //如果文件已经下完了 @Override protected void onPostExecute(File file) { super.onPostExecute(file); if(SUCCESS!=null){ SUCCESS.onSuccess(file.getPath()); } if(REQUEST!=null){ REQUEST.onRequestEnd(); } //如果下了APK文件,就直接安装 autoInstallApk(file); } private void autoInstallApk(File file) { if(FileUtil.getExtension(file.getPath()).equals("apk")){ Intent intent=new Intent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setAction(Intent.ACTION_VIEW); intent.setDataAndType(Uri.fromFile(file),"application/vnd.android.package-archive"); ProjectInit.getApplicationContext().startActivity(intent); } } }
package com.android.core.file; import android.os.Environment; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class FileUtil { //格式化的模板 private static final String TIME_FORMAT = "_yyyyMMdd_HHmmss"; private static final String SDCARD_DIR = Environment.getExternalStorageDirectory().getPath(); private static String getTimeFormatName(String timeFormatHeader) { final Date date = new Date(System.currentTimeMillis()); //必须要加上单引号 final SimpleDateFormat dateFormat = new SimpleDateFormat("'" + timeFormatHeader + "'" + TIME_FORMAT, Locale.getDefault()); return dateFormat.format(date); } /** * @param timeFormatHeader 格式化的头(除去时间部分) * @param extension 后缀名 * @return 返回时间格式化后的文件名 */ public static String getFileNameByTime(String timeFormatHeader, String extension) { return getTimeFormatName(timeFormatHeader) + "." + extension; } @SuppressWarnings("ResultOfMethodCallIgnored") private static File createDir(String sdcardDirName) { //拼接成SD卡中完整的dir final String dir = SDCARD_DIR + "/" + sdcardDirName + "/"; final File fileDir = new File(dir); if (!fileDir.exists()) { fileDir.mkdirs(); } return fileDir; } @SuppressWarnings("ResultOfMethodCallIgnored") public static File createFile(String sdcardDirName, String fileName) { return new File(createDir(sdcardDirName), fileName); } private static File createFileByTime(String sdcardDirName, String timeFormatHeader, String extension) { final String fileName = getFileNameByTime(timeFormatHeader, extension); return createFile(sdcardDirName, fileName); } //获取文件的后缀名 public static String getExtension(String filePath) { String suffix = ""; final File file = new File(filePath); final String name = file.getName(); final int idx = name.lastIndexOf('.'); if (idx > 0) { suffix = name.substring(idx + 1); } return suffix; } public static File writeToDisk(InputStream is, String dir, String name) { final File file = FileUtil.createFile(dir, name); BufferedInputStream bis = null; FileOutputStream fos = null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(is); fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos); byte data[] = new byte[1024 * 4]; int count; while ((count = bis.read(data)) != -1) { bos.write(data, 0, count); } bos.flush(); fos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bos != null) { bos.close(); } if (fos != null) { fos.close(); } if (bis != null) { bis.close(); } is.close(); } catch (IOException e) { e.printStackTrace(); } } return file; } public static File writeToDisk(InputStream is, String dir, String prefix, String extension) { final File file = FileUtil.createFileByTime(dir, prefix, extension); BufferedInputStream bis = null; FileOutputStream fos = null; BufferedOutputStream bos = null; try { bis = new BufferedInputStream(is); fos = new FileOutputStream(file); bos = new BufferedOutputStream(fos); byte data[] = new byte[1024 * 4]; int count; while ((count = bis.read(data)) != -1) { bos.write(data, 0, count); } bos.flush(); fos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (bos != null) { bos.close(); } if (fos != null) { fos.close(); } if (bis != null) { bis.close(); } is.close(); } catch (IOException e) { e.printStackTrace(); } } return file; } }
应用:
目前网络层已经用Okhttp+Retrofit进行改造完了,对于之前隔离层中就可以增加一个Retrofit的网络访问方式,如下:
package com.android.isolation_processor.httpprocessor; import android.util.Log; import com.android.core.net.RestClient; import com.android.core.net.callback.IFailure; import com.android.core.net.callback.ISuccess; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; public class RetrofitProcessor implements IHttpProcessor { @Override public void post(String url, Map<String, Object> params, ICallback callback) { //在这里用已经写好的代码来访问网络 HashMap param = new HashMap(); Set<String> keySet = params.keySet(); Iterator<String> keys = keySet.iterator(); while (keys.hasNext()) { String key = (String) keys.next(); Object value = params.get(key); param.put(key, value); } RestClient.create() .url(url) .params(param) .success(new ISuccess() { @Override public void onSuccess(String responce) { Log.i("cexo", "retrofit=" + responce); } }) .failure(new IFailure() { @Override public void onFailure() { } }) .build() .get(); } }
其运行结果:
另外对于上传下载这块的调用这里贴出来一个模拟的,就不来验证下载成功与否了:
关于整个框架其实还有待改进的,这块在下一次继续完善,其中Dagger2在之前已经学习过了会将其应用进这个框架中。