简易新闻性能优化篇(简易新闻二十二)
关于
其实关于app的优化我也是学习大佬写的文章,然后自己实践并记录下来,或是记录一些踩坑的点。简易新闻的优化方向还有很多,包括一些代码逻辑,以及使用的控件等等,而且冷启动的优化下面说的也不是很全面,但是现在暂时不想往下面补充了,
使用ProGuard简化代码
关于proguard的详解可以参考这篇博文 《关于proguard,你需要知道的全部》。
第一步我们需要在项目的build中开启混淆功能,如下:
buildTypes {
release {
minifyEnabled true //混淆
shrinkResources true //去除无用资源
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
然后修改我们的proguard-rules.pro文件:
我这里是直接把我写的项目的混淆拿出来了,基本上需要自己需改的地方包括Gson的混淆、第三方引用的混淆,其他的基本一样。
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-ignorewarnings
-dontshrink
#指定代码的压缩级别
-optimizationpasses 5
#包明不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
#优化 不优化输入的类文件
-dontoptimize
#不做预校验
-dontpreverify
#混淆时是否记录日志
-verbose
# 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################
#保护注解
-keepattributes *Annotation*
#内部类
-keepattributes EnclosingMethod
-keepattributes InnerClasses
-dontoptimize
########################反射类不被混淆三部曲##################################
#过滤泛型
-keepattributes Signature
# 保留R下面的资源
# 不混淆R类里及其所有内部static类中的所有static变量字段,$是用来分割内嵌类与其母体的标志
-keep public class **.R$*{
public static final int *;
}
#实体反射类不被混淆
#-keep public class com.henry.arron.dtxj.WellMonitorTask.WM_ListDetail_Bean{*;}
#-keep public class com.henry.arron.dtxj.WaterQualityMonitorTask.WQM_ListDetail_Bean{*;}
########################################################################
# 保持哪些类不被混淆
-keep public class * extends android.app.Fragment
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.database.sqlite.SQLiteOpenHelper{*;}
-keep public class com.android.vending.licensing.ILicensingService
#无视warning
-ignorewarnings
# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
-dontwarn android.support.annotation.Keep
#保留类内部使用@keep 注解的成员变量
-keep @android.support.annotation.Keep class **{
@android.support.annotation.Keep <fields>;
}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
# 保留在Activity中的方法参数是view的方法,
# 这样以来我们在layout中写的onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
public void *(android.view.View);
}
# 保留枚举类不被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# 保留Parcelable序列化类不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# 保留Serializable序列化的类不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
#
#----------------------------- WebView(项目中没有可以忽略) -----------------------------
#
#webView需要进行特殊处理
-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, jav.lang.String);
}
#在app中与HTML5的JavaScript的交互进行特殊处理
#我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:
-keepclassmembers class com.ljd.example.JSInterface {
<methods>;
}
#
# ----------------------------- 其他的 -----------------------------
#
# 删除代码中Log相关的代码
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int v(...);
public static int i(...);
public static int w(...);
public static int d(...);
public static int e(...);
}
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
#com.github.ljw124:LogLibrary:1.0.5
-dontwarn org.apache.log4j.**
-keep class org.apache.log4j.** { *; }
#--------------------------------------------------------------------
#butterknife
-dontwarn butterknife.internal.**
-keep class butterknife.** { *; }
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * { @butterknife.* <methods>; }
-keepclasseswithmembernames class * { @butterknife.* <fields>; }
#greendao
-keep class org.greenrobot.greendao.**{*;}
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao { public static java.lang.String TABLENAME;}
-keep class **$Properties
-keep class org.greenrobot.greendao.database.DatabaseOpenHelper.EncryptedHelper{*;}
# OkHttp3
-dontwarn okhttp3.logging.**
-keep class okhttp3.internal.**{*;}
-dontwarn okio.**
#okhttp3.x
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
#Glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
# RxJava RxAndroid
-dontwarn io.reactivex.**
-keep class io.reactivex.** {*;}
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
# DJI
-dontwarn okio.**
-dontwarn org.bouncycastle.**
-keep class org.bouncycastle.** { *; }
-dontwarn dji.**
-keep class dji.** { *; }
-dontwarn com.dji.**
-keep class com.dji.** { *; }
-keep,allowshrinking class * extends dji.publics.DJIUI.** {
public <methods>;
}
-keep class net.sqlcipher.** { *; }
-keep class net.sqlcipher.database.* { *; }
-keep class com.google.** { *; }
-keep,allowshrinking class org.** { *; }
-keep class com.squareup.wire.** { *; }
-keep class sun.misc.Unsafe { *; }
-keep class com.secneo.** { *; }
-keepclasseswithmembers,allowshrinking class * {
native <methods>;
}
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
-keep class android.support.v7.widget.SearchView { *; }
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
-keep class android.media.** { *; }
-keep class okio.** { *; }
#Arouter
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# If you use the byType method to obtain Service, add the following rules to protect the interface:
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# If single-type injection is used, that is, no interface is defined to implement IProvider, the following rules need to be added to protect the implementation
-keep class * implements com.alibaba.android.arouter.facade.template.IProvider
# If @Autowired is used for injection in non-Activity classes, add the following rules to prevent injection failures
-keepnames class * {
@com.alibaba.android.arouter.facade.annotation.Autowired <fields>;
}
#esri关于混淆的代码
-dontwarn com.esri.**
-keep class com.esri.** {*;}
-dontwarn org.codehaus.jackson.**
-keep class org.codehaus.jackson.** {*;}
-dontwarn org.codehaus.jackson.**
-keep class org.codehaus.jackson.** {*;}
-dontwarn jcifs.**
-keep class jcifs.** {*;}
#-keep interface com.esri.** { *; }
#-dontwarn org.codehaus.jackson.map.ext.**
#-dontwarn jcifs.http.**
#高德开放平台上关于api混淆的代码
#3D 地图 V5.0.0之后:
-dontwarn com.amap.api.maps.**
-keep class com.amap.api.maps.**{*;}
-dontwarn com.autonavi.**
-keep class com.autonavi.**{*;}
-dontwarn com.amap.api.trace.**
-keep class com.amap.api.trace.**{*;}
-dontwarn com.amap.api.location.**
-keep class com.amap.api.location.**{*;}
-dontwarn com.amap.api.fence.**
-keep class com.amap.api.fence.**{*;}
-dontwarn com.autonavi.aps.amapapi.model.**
-keep class com.autonavi.aps.amapapi.model.**{*;}
#搜索
-dontwarn com.amap.api.services.**
-keep class com.amap.api.services.**{*;}
#JPush
-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
-keep class * extends cn.jpush.android.helpers.JPushMessageReceiver { *; }
-dontwarn cn.jiguang.**
-keep class cn.jiguang.** { *; }
#Material Dialog提示框
-dontwarn com.afollestad.**
-keep class com.afollestad.** { *; }
#DHVideo 大华视频监控
-dontwarn com.dh.**
-keep class com.dh.** { *; }
#HCVideo 海康威视视频监控
# 海康威视视频取流播放相关库的混淆配置
-keep class org.MediaPlayer.PlayM4.** {*;}
-keep class com.hikvision.netsdk.** {*;}
-keep class com.hikvision.audio.** {*;}
-keep class hik.common.isms.hpsclient.** {*;}
-keep class com.hikvision.open.hikvideoplayer.** {*;}
#litepal数据库不能被混淆
-keep class org.litepal.** {*;}
-keep class * extends org.litepal.crud.DataSupport {*;}
#EventBus
-keepclassmembers class ** {
public void onEvent*(**);
}
-keepclassmembers class ** {
public void xxxxxx(**);
}
#eventbus 3.0
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
#YS Cloud 萤石平台SDK
-dontwarn com.ezviz.**
-keep class com.ezviz.** { *;}
#MPAndroidChart
-dontwarn com.github.mikephil.charting.**
-keep class com.github.mikephil.charting.** { *;}
#SmartTable
-dontwarn com.bin.david.form.**
-keep class com.bin.david.form.**{ *; }
#SmartGo
-keep class * implements pers.victor.smartgo.SmartGoInjector
-keep class * implements pers.victor.smartgo.SmartPathInjector
-keep class * implements pers.victor.smartgo.InstanceInjector
-dontwarn com.videogo.**
-keep class com.videogo.** { *;}
-dontwarn org.MediaPlayer.PlayM4.**
-keep class org.MediaPlayer.PlayM4.** { *;}
-dontwarn javax.validation.**
-keep class javax.validation.** { *;}
-dontwarn com.sun.jna.**
-keep class com.sun.jna.**{*;}
-dontwarn com.ezvizuikit.open.**
-keep class com.ezvizuikit.open.**{*;}
-dontwarn com.google.gson.**
-keep class com.google.gson.**{*;}
-dontwarn com.google.zxing.**
-keep class com.google.zxing.**{*;}
#utils
-keep class com.blankj.utilcode.** { *; }
-keepclassmembers class com.blankj.utilcode.** { *; }
-dontwarn com.blankj.utilcode.**
# Gson
-keepattributes Annotation
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.** {*;}
-keep class com.google.**{*;}
-keep class com.google.gson.examples.android.model.** { *; }
# 使用Gson时需要配置Gson的解析对象及变量都不混淆。不然Gson会找不到变量。
# 将下面替换成自己的实体类
-keep class com.example.frametest.json.** { *; }
#Mob短信验证
-keep class com.mob.**{*;}
-keep class cn.smssdk.**{*;}
-dontwarn com.mob.**
#引用mars的xlog,混淆配置
-keep class com.tencent.mars.** {
public protected private *;
}
#和风天气
# 排除okhttp
-dontwarn com.squareup.**
-dontwarn okio.**
-keep public class org.codehaus.* { *; }
-keep public class java.nio.* { *; }
# 排除HeWeather
-dontwarn interfaces.heweather.com.interfacesmodule.**
-keep class interfaces.heweather.com.interfacesmodule.** { *;}
#####混淆保护自己项目的部分代码以及引用的第三方jar包library#######
#项目特殊处理代码
#多图选择器不能被混淆
-dontwarn me.nereo.multi_image_selector.**
-keep class me.nereo.multi_image_selector.* {*;}
-keep class me.nereo.multi_image_selector.adapter.* {*;}
-keep class me.nereo.multi_image_selector.bean.* {*;}
-keep class me.nereo.multi_image_selector.utils.* {*;}
-keep class me.nereo.multi_image_selector.view.* {*;}
#Glide不能被混淆
-dontwarn com.bumptech.**
-keep class com.bumptech.**{ *; }
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
-dontwarn com.cazaea.**
-keep class com.cazaea.**{ *; }
-dontwarn com.scwang.**
-keep class com.scwang.**{ *; }
-dontwarn com.davidecirillo.**
-keep class com.davidecirillo.**{ *; }
-dontwarn com.squareup.**
-keep class com.squareup.**{ *; }
-dontwarn org.xutils.**
-keep class org.xutils.**{ *; }
-dontwarn com.xujiaji.**
-keep class com.xujiaji.**{ *; }
-dontwarn com.android.**
-keep class com.android.**{*;}
-dontwarn android.arch.**
-keep class android.arch.**{*;}
-dontwarn com.bumptech.**
-keep class com.bumptech.**{*;}
-dontwarn org.**
-keep class org.**{*;}
-dontwarn javax.**
-keep class javax.**{*;}
-dontwarn android.backport.**
-keep class android.backport.**{*;}
-dontwarn com.wx.**
-keep class com.wx.**{*;}
-dontwarn com.jauker.widget.**
-keep class com.jauker.widget.**{ *; }
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.**{ *; }
##记录生成的日志数据,gradle build时在本项目根目录输出##
#apk 包内所有 class 的内部结构
-dump class_files.txt
#未混淆的类和成员
-printseeds seeds.txt
#列出从 apk 中删除的代码
-printusage unused.txt
#混淆前后的映射
-printmapping mapping.txt
########记录生成的日志数据,gradle build时 在本项目根目录输出-end######
Application启动优化
在学习优化之前,先看一下我们的app的启动消耗时间。
这里我们使用很普遍的计算耗时方法:adb 命令统计
当然了在使用之前,需要配置一下它的环境变量,如下:
配置android\SDK\android-sdk-windows\platform-tools这个目录到ptah中:
然后我们输入如下代码到android studio中的Terminal中:
adb shell am start -S -W 包名/启动类的全限定名 -S 表示重启当前应用
我们先看一下我们项目的包名,类名:
其中包名就是:com.example.logintest,类名我们把重启进入的第一个页面作为类名。
成功输出结果:
E:\PersonalSVN\Blog>adb shell am start -S -W com.example.logintest/com.example.app.Tools.SplashActivity
Stopping: com.example.logintest
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.logintest/com.example.app.Tools.SplashActivity }
Status: ok
Activity: com.example.logintest/com.example.app.Tools.SplashActivity
ThisTime: 1620
TotalTime: 1620
WaitTime: 1634
Complete
- ThisTime : 最后一个 Activity 的启动耗时(例如从 LaunchActivity - >MainActivity「adb命令输入的Activity」 , 只统计 MainActivity 的启动耗时)
- TotalTime : 启动一连串的 Activity 总耗时.(有几个Activity 就统计几个)
- WaitTime : 应用进程的创建过程 + TotalTime .
这里我们只需要参考thisTime即可。优化它就是优化冷启动时间。
优化方式一 懒加载
@Override
public void onCreate() {
super.onCreate();
//获取全局context
context = getApplicationContext();
//初始化
instance = this;
//懒加载
new Thread(){
@Override
public void run() {
super.run();
//这里放入自己的三方SDK
//......初始化第三方SDK
MultiDex.install(getApplicationContext());
// 穿山甲SDK初始化
// 强烈建议在应用对应的Application#onCreate()方法中调用,避免出现content为null的异常
TTAdManagerHolder.init(getApplicationContext());
//如果明确某个进程不会使用到广告SDK,可以只针对特定进程初始化广告SDK的content
//if (PROCESS_NAME_XXXX.equals(processName)) {
// TTAdManagerHolder.init(this)
//}
TTAdSdk.init(context,
new TTAdConfig.Builder()
.appId("5043758")
.useTextureView(false) //使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView
.appName("简易新闻")
.titleBarTheme(TTAdConstant.TITLE_BAR_THEME_DARK)
.allowShowNotify(true) //是否允许sdk展示通知栏提示
.allowShowPageWhenScreenLock(true) //是否在锁屏场景支持展示广告落地页
.debug(true) //测试阶段打开,可以通过日志排查问题,上线时去除该调用
.directDownloadNetworkType(TTAdConstant.NETWORK_STATE_WIFI, TTAdConstant.NETWORK_STATE_3G) //允许直接下载的网络状态集合
.supportMultiProcess(false) //是否支持多进程,true支持
// .httpStack(new MyOkStack3())//自定义网络库,demo中给出了okhttp3版本的样例,其余请自行开发或者咨询工作人员。
.build());
}
}.start();
}
重新输入adb命令查看耗时:
E:\PersonalSVN\Blog>adb shell am start -S -W com.example.logintest/com.example.app.Tools.SplashActivity
Stopping: com.example.logintest
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.logintest/com.example.app.Tools.SplashActivity }
Status: ok
Activity: com.example.logintest/com.example.app.Tools.SplashActivity
ThisTime: 1555
TotalTime: 1555
WaitTime: 1574
Complete
没用懒加载之前是1620,使用懒加载后是1555,优化了,虽然不多。
优化方式二 线程池异步优化
核心思想:子线程分担主线程任务,并行减少执行时间。
代码如下:
//计算线程数量
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2,Math.min(CPU_COUNT-1,4));
private CountDownLatch mCountDownLatch = new CountDownLatch(1);
@Override
public void onCreate() {
super.onCreate();
//获取全局context
context = getApplicationContext();
//初始化
instance = this;
//线程异步优化
ExecutorService pool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
pool.submit(new Runnable() {
@Override
public void run() {
//......初始化第三方SDK
// 穿山甲SDK初始化
// 强烈建议在应用对应的Application#onCreate()方法中调用,避免出现content为null的异常
TTAdManagerHolder.init(getApplicationContext());
//如果明确某个进程不会使用到广告SDK,可以只针对特定进程初始化广告SDK的content
//if (PROCESS_NAME_XXXX.equals(processName)) {
// TTAdManagerHolder.init(this)
//}
TTAdSdk.init(context,
new TTAdConfig.Builder()
.appId("5043758")
.useTextureView(false) //使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView
.appName("简易新闻")
.titleBarTheme(TTAdConstant.TITLE_BAR_THEME_DARK)
.allowShowNotify(true) //是否允许sdk展示通知栏提示
.allowShowPageWhenScreenLock(true) //是否在锁屏场景支持展示广告落地页
.debug(true) //测试阶段打开,可以通过日志排查问题,上线时去除该调用
.directDownloadNetworkType(TTAdConstant.NETWORK_STATE_WIFI, TTAdConstant.NETWORK_STATE_3G) //允许直接下载的网络状态集合
.supportMultiProcess(false) //是否支持多进程,true支持
// .httpStack(new MyOkStack3())//自定义网络库,demo中给出了okhttp3版本的样例,其余请自行开发或者咨询工作人员。
.build());
////调用一次countDown,防止当我们需要在oncreate中完成这个初始化时的错误
mCountDownLatch.countDown();
}
});
pool.submit(new Runnable() {
@Override
public void run() {
MultiDex.install(getApplicationContext());
}
});
try {
//如果await之前没有调用countDown那么就会一直阻塞在这里
mCountDownLatch.await();
}catch (InterruptedException e){
e.printStackTrace();
}
}
再次用adb,运行时间再次缩减:
E:\PersonalSVN\Blog>adb shell am start -S -W com.example.logintest/com.example.app.Tools.SplashActivity
Stopping: com.example.logintest
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.example.logintest/com.example.app.Tools.SplashActivity }
Status: ok
Activity: com.example.logintest/com.example.app.Tools.SplashActivity
ThisTime: 1337
TotalTime: 1337
WaitTime: 1345
Complete
当然了,不是说一定第二种比第一种好,各有优劣。
本篇到此就结束了,关于简易新闻的源码以及一些新的功能,要往后面拖一拖了,比如马上要实现的夜间模式(动态切换,代码优化,listview切换成Recycleview等),包括考虑新增用户注册,登录(支持邮箱验证注册),自定义版本更新(不用第三方更新)以及热更新!(近期推出热更新学习文章)。