首页 文章 精选 留言 我的

精选列表

搜索[架构模式],共10011篇文章
优秀的个人博客,低调大师

【Android架构】基于MVP模式的Retrofit2+RXjava封装之文件下载(二)

上篇中我们介绍了基于MVP的Retrofit2+RXjava封装,还没有看的点击这里,这一篇我们来说说文件下载的实现。 首先,我们先在ApiServer定义好调用的接口 @GET Observable<ResponseBody> downloadFile(@Url String fileUrl); 接着定义一个接口,下载成功后用来回调 public interface FileView extends BaseView { void onSuccess(File file); } 接着是Observer,建议与处理普通接口的Observer区分处理 public abstract class FileObsever extends BaseObserver<ResponseBody> { private String path; public FileObsever(BaseView view, String path) { super(view); this.path = path; } @Override protected void onStart() { } @Override public void onComplete() { } @Override public void onSuccess(ResponseBody o) { } @Override public void onError(String msg) { } @Override public void onNext(ResponseBody o) { File file = FileUtil.saveFile(path, o); if (file != null && file.exists()) { onSuccess(file); } else { onErrorMsg("file is null or file not exists"); } } @Override public void onError(Throwable e) { onErrorMsg(e.toString()); } public abstract void onSuccess(File file); public abstract void onErrorMsg(String msg); } FileUtil 注:如果需要写入文件的进度,可以在将这段方法放在onNext中,在FileObsever这个类写个方法,然后回调。 public static File saveFile(String filePath, ResponseBody body) { InputStream inputStream = null; OutputStream outputStream = null; File file = null; try { if (filePath == null) { return null; } file = new File(filePath); if (file == null || !file.exists()) { file.createNewFile(); } long fileSize = body.contentLength(); long fileSizeDownloaded = 0; byte[] fileReader = new byte[4096]; inputStream = body.byteStream(); outputStream = new FileOutputStream(file); while (true) { int read = inputStream.read(fileReader); if (read == -1) { break; } outputStream.write(fileReader, 0, read); fileSizeDownloaded += read; } outputStream.flush(); } catch (Exception e) { e.printStackTrace(); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } return file; } 下来是FilePresenter public class FilePresenter extends BasePresenter<FileView> { public FilePresenter(FileView baseView) { super(baseView); } public void downFile(String url, final String path) { addDisposable(apiServer.downloadFile(url), new FileObsever(baseView, path) { @Override public void onSuccess(File file) { if (file != null && file.exists()) { baseView.onSuccess(file); } else { baseView.showError("file is null"); } } @Override public void onErrorMsg(String msg) { baseView.showError(msg); } }); } } 最后在Activity中调用 private void downFile() { String url = "http://download.sdk.mob.com/apkbus.apk"; String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) {// 检查是否有存储卡 dir = Environment.getExternalStorageDirectory() + "/ceshi/"; File dirFile = new File(dir); if (!dirFile.exists()) { dirFile.mkdirs(); } } presenter.downFile(url, dir + "app-debug.apk"); } 就在我以为万事大吉的时候,APP崩溃了,错误信息如下: [图片上传失败...(image-39e2a7-1532052138950)] 原来是加入日志监听器,会导致每次都把整个文件加载到内存,那我们就去掉这个 修改FilePresenter#downFile如下: public void downFile(String url, final String path) { OkHttpClient client = new OkHttpClient.Builder().build(); Retrofit retrofit = new Retrofit.Builder().client(client) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl("https://wawa-api.vchangyi.com/").build(); apiServer = retrofit.create(ApiServer.class); addDisposable(apiServer.downloadFile(url), new FileObsever(baseView, path) { @Override public void onSuccess(File file) { if (file != null && file.exists()) { baseView.onSuccess(file); } else { baseView.showError("file is null"); } } @Override public void onErrorMsg(String msg) { baseView.showError(msg); } }); } 这次倒是下载成功了,不过官方建议10M以上的文件用Streaming标签,我们加上Streaming标签试试 修改ApiServer @Streaming @GET /** * 大文件官方建议用 @Streaming 来进行注解,不然会出现IO异常,小文件可以忽略不注入 */ Observable<ResponseBody> downloadFile(@Url String fileUrl); 这次又崩溃了,错误信息如下: [图片上传失败...(image-fc8c28-1532052138951)] 这是怎么回事,我们网络请求是在子线程啊。无奈之下只得翻翻官方文档,原来使用该注解表示响应用字节流的形式返回.如果没使用该注解,默认会把数据全部载入到内存中。我们可以在主线程中处理写入文件(不建议),但不能在主线程中处理字节流。所以,我们需要将处理字节流、写入文件都放在子线程中。 于是,修改FilePresenter#downFile如下: OkHttpClient client = new OkHttpClient.Builder().build(); Retrofit retrofit = new Retrofit.Builder().client(client) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl("https://wawa-api.vchangyi.com/").build(); apiServer = retrofit.create(ApiServer.class); apiServer .downloadFile(url) .map(new Function<ResponseBody, String>() { @Override public String apply(ResponseBody body) throws Exception { File file = FileUtil.saveFile(path, body); return file.getPath(); } }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(new FileObserver(baseView) { @Override public void onSuccess(File file) { baseView.onSuccess(file); } @Override public void onError(String msg) { baseView.showError(msg); } }); 这样,下载文件算是完成了,好像还缺点什么?对,缺个下载进度,还记得拦截器吗,我们可以从这里入手: public class ProgressResponseBody extends ResponseBody { private ResponseBody responseBody; private BufferedSource bufferedSource; private ProgressListener progressListener; public ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) { this.responseBody = responseBody; this.progressListener = progressListener; } @Nullable @Override public MediaType contentType() { return responseBody.contentType(); } @Override public long contentLength() { return responseBody.contentLength(); } @Override public BufferedSource source() { if (bufferedSource == null) { bufferedSource = Okio.buffer(source(responseBody.source())); } return bufferedSource; } private Source source(Source source) { return new ForwardingSource(source) { long totalBytesRead = 0L; @Override public long read(Buffer sink, long byteCount) throws IOException { long bytesRead = super.read(sink, byteCount); totalBytesRead += bytesRead; progressListener.onProgress(responseBody.contentLength(), totalBytesRead); return bytesRead; } }; } public interface ProgressListener { void onProgress(long totalSize, long downSize); } } 在BaseView 中定义接口,个人建议放在BaseView 中,在BaseActivity中实现BaseView,方便复用 /** * 下载进度 * * @param totalSize * @param downSize */ void onProgress(long totalSize, long downSize); 再次修改FilePresenter#downFile如下: public void downFile(final String url, final String path) { OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); return response.newBuilder().body(new ProgressResponseBody(response.body(), new ProgressResponseBody.ProgressListener() { @Override public void onProgress(long totalSize, long downSize) { baseView.onProgress(totalSize, downSize); } })).build(); } }).build(); Retrofit retrofit = new Retrofit.Builder().client(client) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl("https://wawa-api.vchangyi.com/").build(); apiServer = retrofit.create(ApiServer.class); apiServer .downloadFile(url) .map(new Function<ResponseBody, String>() { @Override public String apply(ResponseBody body) throws Exception { File file = FileUtil.saveFile(path, body); return file.getPath(); } }).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribeWith(new FileObserver(baseView) { @Override public void onSuccess(File file) { baseView.onSuccess(file); } @Override public void onError(String msg) { baseView.showError(msg); } }); } 至此,使用Retrofit下载文件暂时告一段落。 你的认可,是我坚持更新博客的动力,如果觉得有用,就请点个赞,谢谢 项目源码

优秀的个人博客,低调大师

架构设计:微服务模式下,实现灰度发布模式

本文源码:GitHub·点这里 || GitEE·点这里 一、基本逻辑 请求通过8001服务,在灰度规则中,会读取下次请求的服务列表,根据版本号参数规则,选中路由的服务。 配置版本号,区分灰度版本和默认正常版本; 自定义拦截器,管理版本号或其他标识参数在请求中传递; 自定义服务选中策略,基于版本标识路由服务; 如果灰度服务不存在,则基于规则选中默认服务; 二、版本配置 在node12-server集群配置两个服务:在8002端口配置版本v7.0.0,在8003端口配置版本v7.0.1,用来测试灰度版本选择。 8002服务 eureka: metadata-map: version: v7.0.0 8003服务 eureka: metadata-map: version: v7.0.1 Eureka注册中心,服务列表: 三、参数传递 微服务下通过实现RequestInterceptor接口,管理服务之间的Feign请求拦截器,在请求路由到服务前,可以对请求执行一些处理操作,常见操作例如传递版本号,用户Token等请求头等属性。 /** * 请求拦截器 */ @Component public class GrayReqInterceptor implements RequestInterceptor { private static final String VERSION_KEY = "versionId" ; /** * 处理请求头参数携带问题 */ @Override public void apply(RequestTemplate requestTemplate) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String versionId = request.getHeader(VERSION_KEY); if (StringUtils.isNotEmpty(versionId)){ requestTemplate.header(VERSION_KEY,versionId); } } } 这里就传递一个versionId参数,作为下次请求路由服务的核心标识。 四、灰度规则 在请求头的Header中添加要访问的版本号,如果有匹配的服务,则路由所有请求的灰度服务,如果没有则返回默认服务。 @Configuration public class GrayRule extends ZoneAvoidanceRule { @Bean public GrayReqInterceptor grayReqInterceptor(){ return new GrayReqInterceptor(); } private static final String VERSION_KEY = "versionId" ; @Override public Server choose(Object key) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String versionId = request.getHeader(VERSION_KEY); // 服务匹配 List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key); Server toServer = getServer(serverList,versionId); if (toServer != null){ return toServer ; } else { return getServer(serverList,GrayConstant.VERSION_DEF); } } private Server getServer (List<Server> serverList,String version){ Server toServer = null ; for (Server server : serverList) { Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata(); String metaVersion = metadata.get("version"); if (!StringUtils.isEmpty(metaVersion)) { if (metaVersion.equals(version)) { toServer = server; } } } return toServer ; } } 在实际的过程中,服务的选择是十分复杂的,如果没有灰度服务,需要根据实际情况制定服务匹配的规则,例如根据响应时间,或者默认轮询等。 更需要注意的一点是,一旦使用底层API的二次封装,项目的整体就会受到框架版本升级的影响,需要持续关注框架的环境。 五、测试流程 1.启动相关服务,观察注册中心服务列表; 2.请求8001服务的接口,并带上版本号; 3.观察不同版本号的路由服务; 4.不携带版本号,观察默认服务选择; 六、源代码地址 GitHub地址:知了一笑 https://github.com/cicadasmile/spring-cloud-base GitEE地址:知了一笑 https://gitee.com/cicadasmile/spring-cloud-base

优秀的个人博客,低调大师

架构师是如何炼成的?以天猫APP架构&开发模式升级工程为例

在集团大数据、算法的背景下,猫客(天猫客户端)首页率先从2015年的坑位运营走向2016年的全面个性化,猫客首页个性化业务点多达50多处,个性化场景大部分通过通过Aladdin(天猫推荐)接入TPP(集团个性化平台)来实现的。走向个性化的同时也接入大量的第三方服务,例如:阿里妈妈钻展、新人礼包等。 2016老版本猫客首页问题梳理 线上问题定位周期长:首页客户端同学+首页服务端同学+阿拉丁+算法同学。定位参与同学过多,定位问下效率低下 个性化业务支持效率低:首页客户端同学+首页服务端同学+阿拉丁+算法同学。参与同学过多、链路过长,对业务的快速支撑有影响 在走向个性化的同时也接入大量的第三方服务,例如流量宝、新人礼包、超品等,每次接入第三方业务服务,首页服务端都需要做开发发布,频繁的发布导致首页的稳定性变差。 2016老版本猫客首页问题分析 从

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册