本文只是为了方便自己使用,是根据慕课网上的《Android通用框架设计与完整电商App开发》编写的
引用:
//网络依赖
api 'com.squareup.okio:okio:1.14.1'
api 'com.squareup.okhttp3:okhttp:3.10.0'
api 'com.squareup.retrofit2:retrofit:2.4.0'
api 'com.squareup.retrofit2:converter-scalars:2.4.0'
目录结构:
一、最终调用:建造者模式,书写比较方便,逻辑比较清楚,易于扩展,并且不需要客户知道具体的过程
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
private void testRestClient(){
RestClient.builder()
.url("https://news.baidu.com/")
.loader(getContext())
.success(new ISuccess() {
@Override
public void onSuccess(String response) {
Toast.makeText(getContext(),response,Toast.LENGTH_LONG).show();
}
})
.failure(new IFailure() {
@Override
public void onFailure() {
Toast.makeText(getContext(), "onFailure",Toast.LENGTH_LONG).show();
}
}).error(new IError() {
@Override
public void onError(int code, String msg) {
Toast.makeText(getContext(),"onError",Toast.LENGTH_LONG).show();
}
}).build()
.get();
}
二、Retrofit封装
RestService:接口
public interface RestService {
@GET
Call get(@Url String url, @QueryMap Map params);
@FormUrlEncoded
@POST
Call post(@Url String url, @FieldMap Map params);
@POST
Call postRaw(@Url String url, @Body RequestBody body);
@FormUrlEncoded
@PUT
Call put(@Url String url, @FieldMap Map params);
@PUT
Call putRaw(@Url String url, @Body RequestBody body);
@DELETE
Call delete(@Url String url, @QueryMap Map params);
@Streaming
@GET
Call download(@Url String url, @QueryMap Map params);
@Multipart
@POST
Call upload(@Url String url, @Part MultipartBody.Part file);
}
在这里并没有将Url直接放在注解上,而是作为一个参数传入方法中,这样的话作为框架比较通用
RestCreator
public class RestCreator {
private static final class ParamsHolder {
public static final WeakHashMap PARAMS = new WeakHashMap<>();
}
public static WeakHashMap getParams () {
return ParamsHolder.PARAMS;
}
public static RestService getRestService() {
return RestServiceHolder.REST_SERVICE;
}
private static final class RetrofitHolder {
private static final String BASE_URL = (String) Latte.getConfigurations().get(ConfigType.API_HOST.name());
private static final Retrofit RETROFIT_CLIENT = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(OkHttpHolder.OK_HTTP_CLIENT)
.addConverterFactory(ScalarsConverterFactory.create())
.build();
}
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();
}
private static final class RestServiceHolder {
private static final RestService REST_SERVICE =
RetrofitHolder.RETROFIT_CLIENT.create(RestService.class);
}
}
在这里初始化Retrofit,在这里使用单例模式对Retrofit以及OKHttp进行初始化,单例模式的写法有多种,这里采用了静态内部类的方式
单例的特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
静态内部类优点:
1.采用静态内部类的方式,作为单例,直接用classLoader(jvm类加载机制)进行处理异步加锁问题,并减少内存消耗
2.懒加载(饿汉式),即延迟加载。
3.线程安全。
RestClient
public class RestClient {
private final String URL;
private static final WeakHashMap PARAMS = RestCreator.getParams();
private final IRequest REQUEST;
private final ISuccess SUCCESS;
private final IFailure FAILURE;
private final IError ERROR;
private final RequestBody BODY;
private final LoaderStyle LOAD_STYLE;
private final Context CONTEXT;
private final File FILE;
private final String DOWMLOAD_DIR;
private final String EXTENSION;
private final String NAME;
public RestClient(String url,
Map params,
IRequest request,
ISuccess success,
IFailure failure,
IError error,
RequestBody body,
LoaderStyle loaderStyle,
Context context,
File file,
String downloadDir,
String extension,
String name) {
this.URL = url;
PARAMS.putAll(params);
this.REQUEST = request;
this.SUCCESS = success;
this.FAILURE = failure;
this.ERROR = error;
this.BODY = body;
this.LOAD_STYLE = loaderStyle;
this.CONTEXT = context;
this.FILE = file;
this.DOWMLOAD_DIR = downloadDir;
this.EXTENSION = extension;
this.NAME = name;
}
public static RestClientBuilder builder() {
return new RestClientBuilder();
}
private void request(HttpMethod method) {
final RestService service = RestCreator.getRestService();
Call call = null;
if (REQUEST != null) {
REQUEST.onRequestStart();
}
if (LOAD_STYLE != null) {
LatterLoader.showLoading(CONTEXT,LOAD_STYLE);
}
switch (method) {
case GET:
call = service.get(URL, PARAMS);
break;
case POST:
call = service.post(URL, PARAMS);
break;
case POST_RAW:
call = service.postRaw(URL,BODY);
break;
case PUT:
call = service.put(URL, PARAMS);
break;
case PUT_RAW:
call = service.putRaw(URL,BODY);
break;
case DELETE:
call = service.delete(URL, PARAMS);
break;
case UPLOAD:
final RequestBody requestBody = RequestBody.create(MediaType.parse(MultipartBody.FORM.toString()),FILE);
final MultipartBody.Part body = MultipartBody.Part.createFormData("file",FILE.getName(),requestBody);
call = service.upload(URL,body);
break;
default:
break;
}
if (call != null) {
call.enqueue(getRequestCallbacks());
}
}
private Callback getRequestCallbacks() {
return new RequestCallbacks(REQUEST, SUCCESS, FAILURE, ERROR,LOAD_STYLE);
}
public final void get() {
request(HttpMethod.GET);
}
public final void post() {
if (BODY == null) {
request(HttpMethod.POST);
} else {
if (!PARAMS.isEmpty()) {
throw new RuntimeException("PARAMS must be empty");
}
request(HttpMethod.POST_RAW);
}
}
public final void put() {
if (BODY == null) {
request(HttpMethod.PUT);
} else {
if (!PARAMS.isEmpty()) {
throw new RuntimeException("PARAMS must be empty");
}
request(HttpMethod.PUT_RAW);
}
}
public final void delete() {
request(HttpMethod.DELETE);
}
public final void download(){
new DownloadHandler(URL,REQUEST,SUCCESS,FAILURE,ERROR,DOWMLOAD_DIR,EXTENSION,NAME)
.handlerDownload();
}
}
具体的使用过程使用的是建造者模式进行构建
有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合,因此上述RestClient既是指挥者,同时也是建造者,其中的get()、post()等public方法就是对外暴露,供客户端使用的,,当然还有buider()方法返回了具体的RestClientBuilder产品
重点注意下request()方法,这个方法就是编写的真正去请求RestService中的接口以及调用RestCallBack的回调方法的逻辑
RestClientBuilder
public class RestClientBuilder {
private String mUrl;
private static final Map PARAMS = RestCreator.getParams();
private IRequest mIRquest;
private ISuccess mISuccess;
private IFailure mIFailure;
private IError mError;
private RequestBody mBody;
private LoaderStyle mLoaderStyle;
private Context mContext;
private File mFIle;
private String mDownloadDir;
private String mExtension;
private String mName;
RestClientBuilder() {
}
public final RestClientBuilder url(String url) {
this.mUrl = url;
return this;
}
public final RestClientBuilder params(WeakHashMap params) {
PARAMS.putAll(params);
return this;
}
public final RestClientBuilder params(String key,Object value) {
PARAMS.put(key,value);
return this;
}
public final RestClientBuilder request(IRequest iRequest){
this.mIRquest = iRequest;
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 success(ISuccess iSuccess) {
this.mISuccess = iSuccess;
return this;
}
public final RestClientBuilder failure(IFailure iFailure){
this.mIFailure = iFailure;
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 error(IError iError) {
this.mError = iError;
return this;
}
public final RestClientBuilder loader(Context context,LoaderStyle loaderStyle) {
this.mLoaderStyle = loaderStyle;
this.mContext = context;
return this;
}
public final RestClientBuilder loader(Context context) {
this.mLoaderStyle = LoaderStyle.BallClipRotatePulseIndicator;
this.mContext = context;
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 name (String name) {
this.mName = name;
return this;
}
public final RestClient build(){
return new RestClient(mUrl,PARAMS,mIRquest,mISuccess,mIFailure,mError,mBody,mLoaderStyle,mContext,mFIle,mDownloadDir,mExtension,mName);
}
}
具体的产品生产方法,类似set方法,以及对外暴露产品的方法build()
HttpMethod相关的方法枚举
public enum HttpMethod {
GET,
POST,
POST_RAW,
PUT,
PUT_RAW,
DELETE,
UPLOAD
}
三、Retrofit回调callback相关
IRequest
public interface IRequest {
void onRequestStart();
void onRequestEnd();
}
ISuccess
public interface ISuccess {
void onSuccess(String response);
}
IFailure
public interface IFailure {
void onFailure();
}
IError
public interface IError {
void onError(int code, String msg);
}
RequestCallbacks
public class RequestCallbacks implements Callback {
private final IRequest REQUEST;
private final ISuccess SUCCESS;
private final IFailure FAILURE;
private final IError ERROR;
private final LoaderStyle LOADER_STYLE;
private static final Handler HANDLER = new Handler();
public RequestCallbacks(IRequest request,
ISuccess success,
IFailure failure,
IError error,
LoaderStyle loaderStyle) {
this.REQUEST = request;
this.SUCCESS = success;
this.FAILURE = failure;
this.ERROR = error;
this.LOADER_STYLE = loaderStyle;
}
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful()) {
if (call.isExecuted()) {
if (SUCCESS != null) {
SUCCESS.onSuccess(response.body());
}
}
} else {
if (ERROR != null) {
ERROR.onError(response.code(), response.message());
}
}
stopLoading();
}
@Override
public void onFailure(Call call, Throwable t) {
Log.e("response---------------",t.getMessage());
if (FAILURE != null) {
FAILURE.onFailure();
}
if (REQUEST != null) {
REQUEST.onRequestEnd();
}
stopLoading();
}
private void stopLoading() {
if (LOADER_STYLE != null) {
HANDLER.postDelayed(new Runnable() {
@Override
public void run() {
LatterLoader.stopLoading();
}
},1000);
}
}
}
四、download封装
DownloadHandler
public class DownloadHandler {
private final String URL;
private static final WeakHashMap PARAMS = RestCreator.getParams();
private final IRequest REQUEST;
private final ISuccess SUCCESS;
private final IFailure FAILURE;
private final IError ERROR;
private final String DOWMLOAD_DIR;
private final String EXTENSION;
private final String NAME;
public DownloadHandler(String url,
IRequest request,
ISuccess success,
IFailure failure,
IError error,
String downloadDir,
String extension,
String name) {
this.URL = url;
this.REQUEST = request;
this.SUCCESS = success;
this.FAILURE = failure;
this.ERROR = error;
this.DOWMLOAD_DIR = downloadDir;
this.EXTENSION = extension;
this.NAME = name;
}
public final void handlerDownload() {
if (REQUEST != null){
REQUEST.onRequestStart();
}
RestCreator.getRestService().download(URL,PARAMS)
.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if(response.isSuccessful()) {
final ResponseBody body = response.body();
final SaveFileTask task = new SaveFileTask(REQUEST,SUCCESS);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,DOWMLOAD_DIR,
EXTENSION,
body,
NAME);
// 这里一定要判断文件是否下载完成
if (task.isCancelled()) {
if(REQUEST != null) {
REQUEST.onRequestEnd();
}
}
} else {
if (ERROR != null) {
ERROR.onError(response.code(),response.message());
}
}
}
@Override
public void onFailure(Call call, Throwable t) {
if (FAILURE != null) {
FAILURE.onFailure();
}
}
});
}
}
上面这是下载的具体使用,这里需要重新启用一个线程去下载,所以用到了AsyncTask
SaveFileTask
public class SaveFileTask extends AsyncTask
具体的下载逻辑在这里进行,doInBackground写的下载的线程,onPostExecute返回的主线程,autoInstallApk()安装apk
FileUtil
public final class FileUtil {
//格式化的模板
private static final String TIME_FORMAT = "_yyyyMMdd_HHmmss";
private static final String SDCARD_DIR =
Environment.getExternalStorageDirectory().getPath();
//默认本地上传图片目录
public static final String UPLOAD_PHOTO_DIR =
Environment.getExternalStorageDirectory().getPath() + "/a_upload_photos/";
//网页缓存地址
public static final String WEB_CACHE_DIR =
Environment.getExternalStorageDirectory().getPath() + "/app_web_cache/";
//系统相机目录
public static final String CAMERA_PHOTO_DIR =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath() + "/Camera/";
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);
}
//获取文件的MIME
public static String getMimeType(String filePath) {
final String extension = getExtension(filePath);
return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
}
//获取文件的后缀名
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;
}
/**
* 保存Bitmap到SD卡中
*
* @param dir 目录名,只需要写自己的相对目录名即可
* @param compress 压缩比例 100是不压缩,值约小压缩率越高
* @return 返回该文件
*/
public static File saveBitmap(Bitmap mBitmap, String dir, int compress) {
final String sdStatus = Environment.getExternalStorageState();
// 检测sd是否可用
if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) {
return null;
}
FileOutputStream fos = null;
BufferedOutputStream bos = null;
File fileName = createFileByTime(dir, "DOWN_LOAD", "jpg");
try {
fos = new FileOutputStream(fileName);
bos = new BufferedOutputStream(fos);
mBitmap.compress(Bitmap.CompressFormat.JPEG, compress, bos);// 把数据写入文件
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (bos != null) {
bos.flush();
}
if (bos != null) {
bos.close();
}
//关闭流
if (fos != null) {
fos.flush();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
refreshDCIM();
return fileName;
}
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;
}
/**
* 通知系统刷新系统相册,使照片展现出来
*/
private static void refreshDCIM() {
if (Build.VERSION.SDK_INT >= 19) {
//兼容android4.4版本,只扫描存放照片的目录
MediaScannerConnection.scanFile(Latte.getApplicationContext(),
new String[]{Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath()},
null, null);
} else {
//扫描整个SD卡来更新系统图库,当文件很多时用户体验不佳,且不适合4.4以上版本
Latte.getApplicationContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" +
Environment.getExternalStorageDirectory())));
}
}
/**
* 读取raw目录中的文件,并返回为字符串
*/
public static String getRawFile(int id) {
final InputStream is = Latte.getApplicationContext().getResources().openRawResource(id);
final BufferedInputStream bis = new BufferedInputStream(is);
final InputStreamReader isr = new InputStreamReader(bis);
final BufferedReader br = new BufferedReader(isr);
final StringBuilder stringBuilder = new StringBuilder();
String str;
try {
while ((str = br.readLine()) != null) {
stringBuilder.append(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
br.close();
isr.close();
bis.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return stringBuilder.toString();
}
public static void setIconFont(String path, TextView textView) {
final Typeface typeface = Typeface.createFromAsset(Latte.getApplicationContext().getAssets(), path);
textView.setTypeface(typeface);
}
/**
* 读取assets目录下的文件,并返回字符串
*/
public static String getAssetsFile(String name) {
InputStream is = null;
BufferedInputStream bis = null;
InputStreamReader isr = null;
BufferedReader br = null;
StringBuilder stringBuilder = null;
final AssetManager assetManager = Latte.getApplicationContext().getAssets();
try {
is = assetManager.open(name);
bis = new BufferedInputStream(is);
isr = new InputStreamReader(bis);
br = new BufferedReader(isr);
stringBuilder = new StringBuilder();
String str;
while ((str = br.readLine()) != null) {
stringBuilder.append(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) {
br.close();
}
if (isr != null) {
isr.close();
}
if (bis != null) {
bis.close();
}
if (is != null) {
is.close();
}
assetManager.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (stringBuilder != null) {
return stringBuilder.toString();
} else {
return null;
}
}
public static String getRealFilePath(final Context context, final Uri uri) {
if (null == uri) return null;
final String scheme = uri.getScheme();
String data = null;
if (scheme == null)
data = uri.getPath();
else if (ContentResolver.SCHEME_FILE.equals(scheme)) {
data = uri.getPath();
} else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) {
final Cursor cursor = context.getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null);
if (null != cursor) {
if (cursor.moveToFirst()) {
final int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
if (index > -1) {
data = cursor.getString(index);
}
}
cursor.close();
}
}
return data;
}
}