为指导 Android 开发者更加高效、高质量地进行 App 开发,呈现给用户体验好、性能优、稳定性佳、安全性高的产品。
本手册以开发者为中心视角分为Java语言规范,Android 资源文件命名与使用,Android 基本组件,UI 与布局,进程、线程与消息通信,文件与数据库,Bitmap、Drawable 与动画,安全,其他等九大部分,根据约束力强弱,规约依次分为强制、推荐、参考三大类:
遵循《阿里巴巴 Java 开发手册》
new-structure ├─ library-foobar ├─ app │ ├─ libs │ ├─ src │ │ ├─ androidTest │ │ │ └─ java │ │ │ └─ com/futurice/project │ │ └─ main │ │ ├─ java │ │ │ └─ com/futurice/project │ │ ├─ res │ │ └─ AndroidManifest.xml │ ├─ build.gradle │ └─ proguard-rules.pro ├─ build.gradle └─ settings.gradle
Gradle Scripts Gradle 是一个项目自动化构建工具,帮我们做了依赖、打包、部署、发布、各种渠道的差异管理等工作。 Gradle Scripts主要是工程的编译配置文件,主要有:
指定了当前项目的总体编译规则,打开该文件在buildscript下面找到repositories和dependencies两个节点,其中repositories节点用于设置Android studio插件的网络仓库地址,而dependencies节点用于设置gradle插件的版本号。由于官方的谷歌仓库位于国外,下载速度相对较慢,因此可在repositories节点添加阿里云的仓库地址,方便国内开发者下载相关插件。
该闭包中声明了jcenter()和google()的配置,其中jcenter是一个代码托管仓库,上面托管了很多Android开源项目,在这里配置了jcenter后我们可以在项目中方便引用jcenter上的开源项目,从Android Studio3.0后新增了google()配置,可以引用google上的开源项目。
该闭包使用classpath声明了一个Gradle插件,由于Gradle并不只是用来构建Android项目,因此此处引入相关插件来构建Android项目,其中’3.3.3’为该插件的版本号,可以根据最新的版本号来调整。
build.gradle(Project级别)
gradle版本与gradle plugin(插件)版本要符合对应匹配关系:
插件版本:
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'此处是android的插件gradle,gradle是一个强大的项目构建工具
}
gradle版本:
在gradle-wrapper.properties里
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
Android Gradle 插件 8.4 版本说明 | Android Studio | Android Developers (google.cn)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {//这里是gradle脚本执行所需依赖,分别是对应的maven库和插件
repositories {
google()//从Android Studio3.0后新增了google()配置,可以引用google上的开源项目
jcenter()//是一个类似于github的代码托管仓库,声明了jcenter()配置,可以轻松引用 jcenter上的开源项目
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'此处是android的插件gradle,gradle是一个强大的项目构建工具
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {//这里是项目本身需要的依赖,比如项目所需的maven库
repositories {
google()
jcenter()
}
}
// 运行gradle clean时,执行此处定义的task任务。
// 该任务继承自Delete,删除根目录中的build目录。
// 相当于执行Delete.delete(rootProject.buildDir)。
// gradle使用groovy语言,调用method时可以不用加()。
task clean(type: Delete) {
delete rootProject.buildDir
}
moudle级别的bulidgradle: 模块级别的 build.gradle 对应于具体模块,每个模块都有自己的 build.gradle,它指定了当前模块的详细编译规则。
plugin块: 表示应用了插件,另一种较早的一种声明方式,也是一种比较常见的方式:
apply plugin: 'com.android.application'
android:
这个闭包主要指定生成安装文件的主要配置,一般包含两个子闭包,一个是debug闭包,用于指定生成测试版安装文件的配置,可以忽略不写;另一个是release闭包,用于指定生成正式版安装文件的配置。
sourceSets{}闭包:配置目录指向
packagingOptions{}闭包:打包时的相关配置
productFlavors{}闭包:多个渠道配置
lintOptions{}闭包:代码扫描分析
Activity 的 layout 以 module_activity 开头
Fragment 的 layout 以 module_fragment 开头
Dialog 的 layout 以 module_dialog 开头
include 的 layout 以 module_include 开头
ListView 的行 layout 以 module_list_item 开头
RecyclerView 的 item layout 以 module_recycle_item 开头
GridView 的行 layout 以 module_grid_item 开头
模块名_业务功能描述_控件描述_控件状态限定词
如:module_login_btn_pressed,module_tabs_icon_home_normal
模块名_逻辑名称_[方向|序号]
【推荐】 color 资源使用#AARRGGBB 格式,写入 module_colors.xml 文件中,命名格式采用以下规则:模块名_逻辑名称_颜色
如:<color name="module_btn_bg_color">#33b5e5e5</color>
【推荐】dimen 资源以小写单词+下划线方式命名,写入 module_dimens.xml 文件中,
采用以下规则:
模块名_描述信息
如:<dimen name="module_horizontal_line_height">1dp</dimen>
【推荐】style 资源采用小写单词+下划线方式命名,写入 module_styles.xml 文件中,
采用以下规则:
父 style 名称.当前 style 名称
如:
<style name="ParentTheme.ThisActivityTheme">
…
</style>
【推荐】string资源文件或者文本用到字符需要全部写入module_strings.xml文件中,字符串以小写单词+下划线的方式命名,采用以下规则:
模块名_逻辑名称
如:moudule_login_tips,module_homepage_notice_desc
【推荐】Id 资源原则上以驼峰法命名,View 组件的资源 id 需要以 View 的缩写作为 前缀。常用缩写表如下:
控件 | 缩写 |
---|---|
LinearLayout | ll |
RelativeLayout | rl |
ConstraintLayout | cl |
ListView | lv |
ScollView | sv |
TextView | tv |
Button | btn |
ImageView | iv |
CheckBox | cb |
RadioButton | rb |
EditText | et |
其它控件的缩写推荐使用小写字母并用下划线进行分割,例如:
ProgressBar
对应的缩写为 progress_bar
DatePicker
对应的缩写为 date_picker
10.【推荐】大分辨率图片(单维度超过 1000)大分辨率图片建议统一放在 xxhdpi 目录下管理,否则将导致占用内存成倍数增加。
Android 基本组件指 Activity
、Fragment
、Service
、BroadcastReceiver
、
ContentProvider
等等。
public void viewUrl(String url, String mimeType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), mimeType);
if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_
ONLY) != null) {
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
if (Config.LOGD) {
Log.d(LOGTAG, "activity not found for " + mimeType + " over " +
Uri.parse(url). getScheme(), e);
}
}
}
}
反例:
Intent intent = new Intent();
intent.setAction("com.great.activity_intent.Intent_Demo1_Result3");
11.【推荐】Service 需要以多线程来并发处理多个启动请求,建议使用 IntentService,可避免各种复杂的设置。
12.【推荐】对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。
14.【强制】不要在 Android 的 Application 对象中缓存数据。基础组件之间的数据共享请使用 Intent 等机制,也可使用 SharedPreferences 等数据持久化机制。
15.【推荐】使用 Toast 时,建议定义一个全局的 Toast 对象,这样可以避免连续显示Toast 时不能取消上一次 Toast 消息的情况(如果你有连续弹出 Toast 的情况,避免使用 Toast.makeText)。
16.【强制】使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需要为其显式设置属性(Textview 的文本为空也需要设置 setText(""),背景透明也需要设置),否则在滑动的过程中,因为 adapter item 复用的原因,会出现内容的显示错乱。
public void showPromptDialog(String text){
DialogFragment promptDialog = new DialogFragment() {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
View view = inflater.inflate(R.layout.fragment_prompt, container);
return view;
}
};
promptDialog.show(getFragmentManager(), text);
10.【推荐】尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。
11.【强制】不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图面。
11.【推荐】谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会遇到如下问题:
10.【强制】如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受信任的外部数据直接拼接在原始 SQL 语句中,可使用一个用于将 ? 作为可替换参数的选择子句以及一个单独的选择参数数组,会避免 SQL 注入。 正例:
// 使用一个可替换参数
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;
10.【推荐】使用 ARGB_565 代替 ARGB_888,在不怎么降低视觉效果的前提下,减少内存占用。
11.【推荐】尽量减少 Bitmap (BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、渐变色(GradientDrawable)、StateSelector(StateListDrawable)等与 Shape 结合的形式构建绘图。
12.【推荐】谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个gif 图片的大小。
13.【参考】大图片资源不要直接打包到 apk,可以考虑通过文件仓库远程下载,减小包体积。
14.【推荐】根据设备性能,选择性开启复杂动画,以实现一个整体较优的性能和体验;
15.【推荐】在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面 , onAnimationEnd 可 能 会 因 各 种 异 常 没 被 回 调 ( 参 考 : https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-called-onanimationstart-works-fine),建议加上超时保护或通过 postDelay 替代onAnimationEnd。
说明: 在 AndroidManifest.xml 文件中为了方便对程序数据的备份和恢复在 Android APIlevel 8 以后增加了 android:allowBackup 属性值。默认情况下这个属性值为 true,故当 allowBackup 标志值为 true 时,即可通过 adb backup 和 adb restore 来备份和恢 复应用程序数据。
正例:
<application
android:allowBackup="false"
android:icon="@drawable/test_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
10.【强制】不要广播敏感信息,只能在本应用使用 LocalBroadcast,避免被别的应用收到,或者 setPackage 做限制。
11.【强制】不要把敏感信息打印到 log 中。
说明: 在 APP 的开发过程中,为了方便调试,通常会使用 log 函数输出一些关键流程的信息,这些信息中通常会包含敏感内容,如执行流程、明文的用户名密码等,这会让攻击者更加容易的了解 APP 内部结构方便破解和攻击,甚至直接获取到有价值的敏感信息。
12.【强制】对于内部使用的组件,显示设置组件的"android:exported"属性为 false。
13.【强制】应用发布前确保 android:debuggable 属性设置为 false。
14.【强制】使用 Intent Scheme URL 需要做过滤。
15.【强制】密钥加密存储或者经过变形处理后用于加解密运算,切勿硬编码到代码中。 说明: 应用程序在加解密时,使用硬编码在程序中的密钥,攻击者通过反编译拿到密钥可以轻易解密 APP 通信数据。
16.【强制】将所需要动态加载的文件放置在 apk 内部,或应用私有目录中,如果应用必须要把所加载的文件放置在可被其他应用读写的目录中(比如 sdcard),建议对不可信的加载源进行完整性校验和白名单处理,以保证不被恶意代码注入。
17.【强制】除非 min API level >=17,请注意 addJavascriptInterface 的使用。
18.【强制】使用 Android 的 AES/DES/DESede 加密算法时,不要使用默认的加密模式ECB,应显示指定使用 CBC 或 CFB 加密模式。
19.【强制】不要使用 loopback 来通信敏感信息。
20.【推荐】对于不需要使用 File 协议的应用,禁用 File 协议,显式设置 webView.getSettings().setAllowFileAccess(false),对于需要使用 File 协议的应用,禁止 File协议调用 JavaScript,显式设置 webView.getSettings().setJavaScriptEnabled(false)。
22.【推荐】Android5.0 以后安全性要求较高的应用应该使用 window.setFlag(LayoutParam.FLAG_SECURE) 禁止录屏。
23.【推荐】zip 中不建议允许../../file 这样的路径,可能被篡改目录结构,造成攻击。 说明:当 zip 压缩包中允许存在"../"的字符串,攻击者可以利用多个"../"在解压时改变zip 文件存放的位置,当文件已经存在是就会进行覆盖,如果覆盖掉的文件是 so、dex 或者 odex 文件,就有可能造成严重的安全问题。
24.【强制】开放的 activity/service/receiver 等需要对传入的 intent 做合法性校验。
25.【推荐】加密算法:使用不安全的 Hash 算法(MD5/SHA-1)加密信息,存在被破解的风险,建议使用 SHA-256 等安全性更高的 Hash 算法。
26.【推荐】Android WebView 组件加载网页发生证书认证错误时,采用默认的处理方法handler.cancel(),停止加载问题页面。
说明: Android WebView 组件加载网页发生证书认证错误时,会调用 WebViewClient 类的onReceivedSslError 方法,如果该方法实现调用了 handler.proceed()来忽略该证书错误,则会受到中间人攻击的威胁,可能导致隐私泄露.
【强制】不要通过 Msg 传递大的对象,会导致内存问题。
【强制】不能使用 System.out.println 打印 log。
正例:
Log.d(TAG, "Some Android Debug info ...");
反例:
System.out.println("System out println ...");
【强制】Log 的 tag 不能是" "。 说明: 日志的 tag 是空字符串没有任何意义,也不利于过滤日志。 正例:
private static String TAG = "LoginActivity";
Log.e(TAG, "Login failed!");
反例: Log.e("", "Login failed!");