1、基础组件库
随着时间的增长,代码量的逐渐积累,新旧项目之间有太多可以服用的代码。下面是整理的公共代码库。
2、关于加密
密钥的保护以及网络传输安全是移动应用安全最关键的内容,涉及密码学(用于加密、认证和鉴定的学科)。常见的加密算法,主要分为对称加密算法、非对称加密算法和Hash算法。
---对称加密算法。安全性取决于加密算法本身和密钥的私密性,相对于非对称加密算法,密钥管理比较难,速度快几个数量级,适合大数据量的加密处理,对称加密算法流程图如图6-6所示。
---非对称加密算法。非对称加密算法中需要公开密钥(public key)和私有密钥(private key)两个密钥,密钥与数据是一一对应的。密钥容易管理,安全性高,但加解密速度慢,适合小数据量加解密或数据签名,非对称加密算法流程如图6-6(b)所示。
---Hash(哈希)算法。Hash函数是一种将任意长度的消息压缩到某一固定长度(消息摘要)的函数(该过程不可逆),可用于数字签名、消息的完整性检测、消息起源的认证检测等。Hash算法常见的有MD5、SHA、HMAC、RIPEMD、HAVAL、N-Hash、Tiger等。
具体选择和使用时,对称加密算法建议选择AES,非对称加密算法建议选择ECC或RSA,消息摘要可以用MD5,数字签名相关可以用DSA等。如果使用OkHttp网络库,可以通过OkHttp Builder等certificatePinner方法预设服务端证书等pinging值,也可以通过interceptor插入加解密代码。
---Base64.不要使用Base64来加密数据,Base64只是一种编码方式。
---随机数。使用SecureRandom代替Random类来获取随机数,但注意不要为SecureRandom设置种子。
---Hash算法。建议使用SHA-256、SHA-3算法代替MD2、MD4、MD5、SHA-1、RIPEMD算法来加密用户密码等敏感信息,后者已有很多破解算法。对多个串联字符串做Hash加密,要注意避免Hash值一样。
---消息验证算法。建议使用HMAC-SHA256算法,避免使用CBC-MAC.
---对称加密算法。DES默认的是56位的加密密钥,已经不安全,不建议使用,建议使用AES算法(不要使用Android默认的ECB模式,显示置顶位CBC或CFB模式)。
---非对称算法。密钥长度不要低于512位,建议使用2048位的密钥长度。RSA加密算法应使用Clipher.getInstance(RSA/ECB/OAEPWithSHA256AndMGF1Padding),否则会存在重放攻击的风险。
---密钥存储。动态/运行时密钥存储用Android Keystore,其提供类随机密钥生成和存储密钥功能,其key是依托于硬件的KeyChain存储在系统中,而非App目录下,其他应用是无法访问获取的,预存密钥通过so库预设key/secret存储。
3、启动引导模块
通用简单型启动引导页逻辑:用户单击启动-->启动页(延时或网络加载等)-->引导页-->主页。
如果不是第一次启动,则逻辑变为:用户单击启动-->启动页-->主页。
采用MVP模式,业务逻辑在Presenter中完成,UI相关中**view和**Activity中呈现。
4、注册登陆模块
从产品角度来看,一个App按照是否需要登陆可以分为3类:第一类是依托账号建立产品服务的(如微信),必须登陆;第二类是按需登陆的(如知乎等),浏览无需登陆,收藏/评论等需要登陆;第三类是无需登陆的,主要是工具类应用,如计算器。所以,不同应用对注册登陆模块会有不同的要求,同时用户注册/登陆也是多样性的,可以通过用户名、邮箱账号、手机账号等注册/登陆,另外,现在三方登陆(微信/qq/微博等)也是常见的一种方式。
5、编译打包
---SRC-->DEX(Dalvik Executable)/RES
---- 使用AAPT(The Android Assert Packaing Tool)编译打包资源文件,生成R.java文件、resource.arsc文件和打包资源文件。
---- 使用AIDL(Android Interface Definition Lauguage)处理.aidl文件,生成.java文件
---- 使用Java Compiler(javac)工具,将源码编译成.class文件
---DEX-->APK。使用apkbuilder工具,将资源文件和.dex文件生成未签名的APK安装文件。
---APK sign。使用jarsigner工具,进行APK签名【分为两种:一种是用于调试的debug.keystore(自动生成);另一种是用于发布的release.keystore(手动生成)】
---APK align。使用zipalign工具,将签名后的APK进行对齐处理。
6、Gradle实用技巧
Gradle是一种基于Groovy语法的项目构建工具,其运行在JVM上,借鉴了脚本语言诸多特性,兼容java,可直接使用java各种类库。
---Gradle Task。Task(任务)是Gradle中的一个核心概念,每一个声明的任务都可以看作是一个任务对象,可以拥有自己的属性和方法(默认类型是DefaultTask),同java中java.lang.Object类似。人物之间可以相互依赖,使用关键字dependsOn,还可以通过doFirst和doLast等在任务执行生命周期中插入具体业务逻辑,常见的任务类型有用于复制的Copy、用于打包的Jar、用于执行的JavaExec等。
---- gradle assemble,生成所有渠道Debug和Release包
---- gradle assembleAndroidTest,生成所有渠道的测试包。
---- gradle assembleDebug,生成所有渠道等Debug包。
---- gradle assembleRelese,生成所有渠道的Release包。
---- gradle assebleXXX,生成某个渠道的Debug和Release包。
---Gradle加速。
---- 常规设置。如开启Gradle daemon进程等(gradle.properties文件,建议使用全局配置),代码如下
org.gradle.daemon=true //开启Gradle守护进程
Org.gradle.jvmargs=-Xmx2048 -XX:MaxPermSize=512m --XX:+HeapDumpOnOutOfMemoryError //JVM内存
Org.gradle.parallel=true //并行项目执行(多module依赖复杂慎用)
org.gradle.configureondeamand=true
----开启增量编译。在对应module多build.gradle文件中,按如下设置
dexOptions{
incremental true
}
----屏蔽不需要多Task。屏蔽不需要多Task或特定的Task,按如下设置。
//屏蔽系统Task
tasks.whenTaskAdded{task ->
if(task.name.contains("lint")) //跳过lint检查
||task.name.equals("clean") //如果instant run不生效,把clean这行去掉
||task.name.equals("Aidl") //如果项目中有用到aidl,则不可以舍弃这个任务
||task.name.equals("mockableAndroidJar") //用不到测试的时候,就可以先关闭
||task.name.equals("UnitTest")
||task.name.equals("AndroidTest")
||task.name.equals("Ndk") //用不到NDK和JNI的也可以关闭掉
||task.name.equals("Jni")
){
task.enabled = false
}
}
//屏蔽指定Task XX
gradle.taskGraph.whenReady{
Tasks.each{task ->
if(task.name.contains("XX")){
task.enabled = false
}
}
}
----代理设置。在根目录的gradle.properties中配置,代码如下。
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=1010
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=1010
---Google官方关于Gradle加速的17条实用建议
---- 通过productFlavors设置build variant,针对不同product保留对应的配置信息,加速构建,类似多渠道打包。
---- 避免编译不必要的资源。如dev包通过设置resConfigs “en”“xxhdpi”,只使用英文string资源和xxhdpi的屏幕密度资源,代码如下。
productFlavors{
Dev{
...
//the following configuration limits the "dev"flavor to using
//English string resources and xxhdpi screen-density resources.
resConfigs "en","xxhdpi"
}
...
}
----配置debug构建的Crushlytics为Disable(Crushlytics为崩溃上报分析工具,Debug阶段可能并不需要),如果Debug期间需要开启Crushlytics,那也可以设置alwaysUpdateBuildId为false,避免每次都更新ID,代码如下
Android{
...
buildTypes{
debug{
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
}
}
}
----用静态的构建配置来构建你的Debug版,避免在Debug下使用动态配置(如version codes,version names,resources等),类似下面要阐述的版本号/依赖统一管理
----用静态的版本依赖,避免使用+号
Com.android.tools.build:gradle:2.+ //动态依赖
Com.android.tools.build:gradle:2.3.0 //静态依赖
----配置on demand 为enable状态,指定Gradle仅能配置你想要构建的Modules。
Android Studio路径为:File-->Settings-->Build-->Compiler-->check Configure on demand.
----建议使用library模块,模块化代码抽离。
----当你的构建消耗时间过长时,如果存在较复杂和独立的构建逻辑,考虑将其创建为独立的Tasks(自定义Gradle插件),按需使用。
----配置dexOptions(Android Studio 2.1新增)和开启library pre-dexing(DEX预处理)。两者都是针对DEX构建优化,dexOptions可以配置包括preDexLibraaies、maxProcesCount和javaMaxHeadSize,代码如下。
android{
...
dexOptions{
preDexLibraries true
maxProcessCount 8
//instead of setting the heap size for the DEX process, increase Gradle's
//heap size to enable dex-in-process.
//javaMaxHeapSize "2048m"
}
}
----增加Gradle堆大小(开启Dex-in-process).Dex-in-process默认允许多个DEX进程运行在一个单独的VM中,所以可以通过分配足够的内存来开启这个特性(Android Studio 2.1+)
----将图片转换成WebP格式,不用在构建时做压缩。WebP是一种具备JPEG类似的有损压缩和PNG的透明支持的高压缩质量的图片格式,同时可以减少包Size。
----禁止使用PNG crunching。也是一种禁止构建时默认压缩图片的方法。
android{
...
aaptOptions{
cruncherEnabled false
}
}
----使用Instant Run.
----使用构建缓存,Anroid Gradle插件2.3.0+默认开启了构建缓存。
----避免使用注解处理器,使用注解处理器时将导致增量构建不可用。
----Profile your build。这条主要针对那些超级App(拥有大量自定义构建逻辑等),需要知晓每个阶段/每个Task的时间消耗来优化那些耗时逻辑,build profile的生成通过在Android Studio的命令行中操作(View-->Tool Windows-->Terminal),具体如下:
------清除:gradlew clean(Windows)或./graldew clean(Mac)
------构建:gradlew-->profile-->recompile-scripts-->offline-->rerun-tasks assembleFlavorDe不过(其中,profile表示开启profiling;offline表示禁止Gradle获取离线依赖,防止Gradle更新数据影响报告;rerun-tasks表示强制Gradle返回所有Task并忽略任何Task的优化;recompile-scripts表示强制脚本重新编译跳过cache)。
-----查看:找到project-root/build/reports/profile/目录下的profile_timestamp.html文件,在浏览器中打开即可呈现完整时间消耗的构建报告。
----项目组件化。
---多渠道打包。其本质上productFlavors的使用,结合占位符与AndroidManifest的使用,可以为不同渠道设置不同包名。另外,还可以结合脚本实现快速渠道打包,开源项目packer-ng-plugin声称100个渠道打包只需要10s。
productFlavors{
dev{
applicationIdSuffix ".debug" //不同包名设置,便于线上和开发包安装同一首手机
}
google pay{}
qihoo360{}
xiaomi{}
Tencent{
manifestPlaceholders = [UMENG_CHANNEL:"Tencent"] //结合占位符
}
}
---Gradle通用技巧
----Log开关控制。定义动态编译生成对象,通过buildConfigField控制,然后在java代码中通过BuildConfig.enableLog来获取,代码如下。
buildTypes{
debug{
buildConfigField("boolean","enableLog","true")
}
release{
buildConfigField("boolean","enableLog",“false”)
}
}
----版本号/依赖统一管理。建立独立的gradle(config.gradle),然后apply from进当前gradle,通过设置project.ext,再通过rootProject.ext进行引用,以下代码为XKnife-Android的global_config.gradle文件的一部分。
ext{
abortOnLintError = false
checkLintRelease = false
android= [compileSdkVersion : 24,
buildToolsVersion : "25.0.2",
applicationId : "com.skyseraph.xknife",
applicationIdUserLogin : “com.skyseraph.xknife.module.userlogin”,
applicationIdLaunch : "com.skyseraph.xknife.module.launch",
applicationIdUpgrade : "com.skyseraph.xknife.module.upgrade",
minSdkVersion. : 15,
targetSdkVersion. : 24,
versionCode : 1,
versionName : "1.0.0",
testInstrumentationRunner: "android.support.test.runner.AndroidJUnitRunner"
]
... ...
}
//使用
applicationId rootProject.ext.android ["applicationId"]
另外,还可以在gradle.properties文件中定义一些统一的编译常量(如定义常量XX=1,然后在需要的module中通过project.XX引用)。
----APK输出名字定制化。定制化APK输出名字,自动加上版本号、时间等信息,避免手动重命名
applicationVariants.all{variant ->
variant.outputs.each{output ->
output.outputFile = new File(
output.outputile =new File(
output.outputFile.parent + "/${variant.buildType.name}","XXX-
${variant.buildType.name}-${variant.versionName}-${variant.
productFlavors[0].name.apk".toLowerCase())
}
}
----构建不同的名称、版本号和App ID等,代码如下
buildTypes{
debug{
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
resValue "string","app_name","XXX(debug)"
}
release{
resValue "string","app_name","XXX"
}
}
----修改默认的Build配置文件名(settings.gradle文件)
rootProject.buildFiledName = xx.gradle
----java版本设置。在Gradle中设置java版本,代码如下。
compileOptions{
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility javaVersion.VERSION_1_8
}