# WanAndroidJava **Repository Path**: trydamer/wan-android-java ## Basic Information - **Project Name**: WanAndroidJava - **Description**: WanAndroid - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-07-23 - **Last Updated**: 2022-07-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## WanAndroid **这是一个实战练手的项目,下文主要是对本次练手项目的总结。** **[WanAndroid-Kotlin版](https://gitee.com/trydamer/wand-android-kotlin)** ### 一、流式布局 #### 1.1 效果   流式布局主要用于搜索、体系和导航等页面。效果图如下: #### 1.2 使用 添加依赖: ``` implementation 'com.google.android:flexbox:1.0.0' ``` 布局: ```xml ``` 布局中,TextView是效果图中的“开发环境”、“基础知识”等等。FlexBoxLayout则是效果图中“开发环境”、“基础知识”等等的内容。从效果图中,我们可以看出,数据结构大致是这样的: ```java public class Data { String title; List childs; // // ... // } // ``` 所以我们在适配器中做一个循环,遍历childs,并把child的数据添加到FlexboxLayout中,这样就可以达到上述效果了。 但是,每次都new TextView太浪费资源了。参考了[GoWeii/WanAndroid](https://github.com/goweii/WanAndroid)项目,他用队列来回收TextView,需要用的时候就从队列中取,如果队列为空再创建新的,这样就减少了许多不必要的开销。于是我决定写一个FlexboxAdapter,用到Flexbox的地方都可以直接继承这个[适配器(FlexboxTextViewAdapter)](app/src/main/java/com/lcj/wanandroid/uibase/FlexboxTextViewAdapter.java)。 #### 1.3 数据 需要注意的是,”体系“、”导航“等虽然都是标题+子数据列表的样式,但是他们的数据却不一样,例如效果图中展示的数据如下: ```json { 其他字段, "name": "开发环境", "order": 1, 其他字段, "children": [ { 其他字段, "name": "Android Studio相关", 其他字段 }, { 其他字段, "name": "gradle", 其他字段 }, { 其他字段, "name": "官方发布", 其他字段 }, { 其他字段, "name": "90-120hz", 其他字段 } ] }, { 下一项数据, }, ... ``` 而导航的数据如下: ```json { "cid": 272, "name": "常用网站", "articles": [ { 其他字段, "chapterName": "常用网站", "title": "Google开发者", 其他字段 }, { "chapterName": "常用网站", "title": "Github" }, { "chapterName": "常用网站", "title": "掘金", }, { "chapterName": "常用网站", "title": "CSDN", }, { "chapterName": "常用网站", "title": "简书", }, { "chapterName": "常用网站", "title": "开发者头条", }, { "chapterName": "常用网站", "title": "segmentfault", }, { "chapterName": "常用网站", "title": "androiddevtools", }, { "chapterName": "常用网站", "title": "小专栏", }, { "chapterName": "常用网站", "title": "国内大牛", }, { "chapterName": "常用网站", "title": "国外大牛", } ] }, ``` 对于”体系“,一级列表只需要name字段,二级(Flexbox)列表只需要name字段。而导航,一级列表只需要name字段,二级列表只需要title字段。由于每种数据的字段名称可能不一样,而且FlexboxTextViewAdapter也不知道该如何展示二级数据,所以需要为适配器的数据做一个约束。定义一个抽象类,实现类需要实现一个返回二级数据列表的方法和实现获取二级数据需要展示的标题方法,定义如下: ```java /** * 一级数据需要继承的类 * T为子数据(展示Flexbox的数据类) * */ public abstract class FlexboxBean { public abstract List getChildList(); public interface FlexboxTextViewName { String getFlexboxName(); } } ``` 一级列表所需要展示的字段的数据由自己的适配器决定,而二级列表展示的数据,就调用接口的”getFlexboxName“方法,至于返回哪个字段的数据,则由二级数据自己决定。 ### 二、App更新功能   因为App更新功能在大多数情况下都差不多,所以我想把该功能单独做成一个模块,这样在其他App需要的时候,可以直接复用。 #### 2.1 需求分析   App更新有两种情况,一种是非强制更新,另一种则是强制更新。强制更新比较简单,必须安装新版本的App才能使用。而非强制更新则随便用户更不更新,如果更新,应该要提供后台下载的功能。既然有可能要后台下载,那么就需要一个Service来做下载的任务。因此,不管是强制更新还是非强制更新,直接把下载任务放到Service中去。当在前台下载时,要能够实时更新下载进度,当在后台下载时,可以把下载进度放到通知栏中去。当下载完成后,自动跳到安装页面。   检查App更新应该在App启动时做一次,再在合适的页面做一个检查App更新的功能,即隐式检查和显示检查,隐式检查主要是为了当有强制更新时能够第一时间更新。 #### 2.2 数据   当我们请求”检查更新“的接口时,该接口应该给我们一些数据来判断是否需要更新,是否为强制更新,新版本App的下载地址等等。因此,可以定义数据如下: ```java public class UpdateAppBean { private String version; // 版本号 private String desc; // 版本描述 private int mode; // 0:强制更新,1:选择更新 private String url; // 下载地址 public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getDesc() { return desc; } public void setDesc(String desc) { this.desc = desc; } public int getMode() { return mode; } public void setMode(int mode) { this.mode = mode; } } ``` 当返回的数据的版本号大于当前App的版本号时,说明需要更新App。因为App的版本号一般是”x.y.z“的形式,所以版本号定义为String类型,在做版本号比较时再把其转换为int类型还比较。 #### 2.3 问题   当任务在后台下载时,我把下载进度放到了通知栏,但是点击通知栏并没有任何反应,因为没有对该通知做任何事件处理。主要原因是不知道点击后应该要干什么,跳到某一个下载页面继续显示下载进度? ### 三、数据缓存   一般来说,当手机没有网络时,应该能够显示上次的一些数据,但是之前并没有对这些数据做缓存,也不知道该如何做。最终还是决定试一下,毕竟以后肯定是要做的。 #### 3.1 缓存框架 **DiskLruCache** 添加依赖: ``` implementation 'com.jakewharton:disklrucache:2.0.2' ``` 简单使用: ```java try { // 打开文件,两个参数1表示一个key(前面的参数)对应一个value(后面的参数) diskLruCache = DiskLruCache.open(cacheDir, 1, 1, 1024 * 1024 * 10); // 写入数据 DiskLruCache.Editor editor = getDiskLruCache().edit(Md5Utils.getMD5(key)); editor.set(0, gson.toJson(object)); editor.commit(); getDiskLruCache().flush(); // 读出数据 DiskLruCache.Snapshot snapshot = getDiskLruCache().get(Md5Utils.getMD5(key)); String json = snapshot.getString(0); snapshot.close(); return json; } catch (IOException e) { e.printStackTrace(); diskLruCache = null; } ``` #### 3.2 实现   百度查询了一些数据缓存的方法,有的说直接用使用拦截器做,但是有的又说Okhttp的拦截器缓存只能缓存GET的数据。总而言之就是没有一个实用的,最后决定认真一下[GoWeii/WanAndroid](https://github.com/goweii/WanAndroid)项目是如何做的,然后再结合当前项目的情况来做。   项目是使用Retrofit+RxAndroid来完成网络请求,并自定义了一个解析数据的类,所有的数据都会经过这里,所以干脆就在这个类中做吧。缓存过程如下: ```java @Override public void onNext(HttpResult httpResult) { switch (httpResult.getErrorCode()) { case 0: onSuccess(httpResult.getData()); /** * 如果请求的接口需要缓存(即key不为空), * 那么就缓存数据。 * */ if (!isEmpty(getKey())) { CacheUtil.getInstance().save(getKey(), httpResult.getData()); } break; /** * 错误码为-1001表示该接口需要登录才能正常访问。 * */ case -1001: if (UserUtils.getInstance().getUserDTO() != null) { doLogin(); } default: onFailure(httpResult.getErrorMsg(), httpResult.getErrorCode()); break; } } ``` 可以看出,每次请求,都要做数据缓存。当没有网络时,则不会进入该方法(onNext),而是进入onError方法。但是onError方法没有错误码,只有一个Throwable。但是我想了想,既然走onError了,说明数据肯定是获取不了了,因此只要走onError,就从缓存中取数据吧,如下: ``` if (!isEmpty(key)) { CacheUtil.getInstance().getData(key, new CacheUtil.CacheUtilListener() { @Override public void getDataSuccess(String data) { if (!isEmpty(data)) { onCacheData(data); } } @Override public void getDataFailure(String error) { } }); } onFailure(resErr, code); ``` 从缓存中取数据,但是也要调一下onFailure,要不然用户可能会奇怪为什么没有网络也能请求数据。 ### 四、已知问题   当使用“暗色模式”时,App可以正常切换主题颜色。但是当跟随系统主题且系统主题为“夜晚模式”时,就会出现问题。 正常使用暗色模式(左)和跟随系统(右)对比如下图所示。当然,不仅仅是statusBar颜色不对,其他页面的颜色也不对。
### 五、开源框架 - [腾讯WebView](https://x5.tencent.com/tbs/sdk.html) - [图片加载Glide](https://github.com/bumptech/glide) - [网络请求Retrofit](https://github.com/square/retrofit) - [Retrofit适配器](https://github.com/square/retrofit/tree/master/retrofit-adapters) - [Retrofit数据转换器](https://github.com/square/retrofit/tree/master/retrofit-converters) - [轮播图Banner](https://github.com/youth5201314/banner) - [GoWeii的ActionBar](https://github.com/goweii/ActionBarEx) - [刷新/加载SmartRefresh](https://gitee.com/scwang90/SmartRefreshLayout) - [圆图CircleImgeView](https://github.com/hdodenhof/CircleImageView) - [数据持久化MMKV](https://github.com/Tencent/MMKV) - [流式布局flexbox](https://github.com/google/flexbox-layout) - [页面切换指示器MagicIndicator](https://github.com/hackware1993/MagicIndicator) - [侧滑操作SwipeDelMenuLayout](https://github.com/mcxtzhang/SwipeDelMenuLayout) - [数据库Sugar](https://github.com/chennaione/sugar) - [多状态页面切换MultiStateView](https://github.com/Kennyc1012/MultiStateView) - [数据缓存DiskLruCache](https://github.com/JakeWharton/DiskLruCache) ### 六、说明   本App是在参考[GoWeii/WanAndroid](https://github.com/goweii/WanAndroid)项目完成的。