Android开发代码规范

一. 命名

  1. 基本的命名规则:遵循驼峰命名法(小驼峰或大驼峰),除了需要全部大写或全部小写的情况以外。
  2. 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
  3. 代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式,固有名词除外。
  4. 类名必须使用 UpperCamelCase 风格(大驼峰命名法),固有名词除外。Android系统组件的,必须以系统组件名称为结尾,如xxxActivity.java、xxxFragment.java、xxxService.java、xxxAdapter.java,MVP中presenter为xxxPersenter,涉及所有Model、View、Presenter的接口都以I为前缀。
  5. 全局变量按照驼峰规则命名,首字母以m开头(非静态变量)
  6. 方法名、参数名、成员变量、局部变量必须统一使用 lowerCamelCase 风格(小驼峰命名法),固有名词除外。
  7. 局部变量尽量避免单个字符如a, b,i,j, 除非是临时变量循环变量在for循环中使用。
  8. 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
  9. 抽象类命名使用 Abstract(Abs) 或 Base 开头。
  10. 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。包名命名规则为com.公司名.项目名.模块名,如果是以组件化或插件化的方式开发,单个模块或单个插件中的子包划分按照PBL(按层分包Package By Layer)。
  11. 杜绝完全不规范或非公认的缩写,避免望文不知义。
  12. 资源文件的命名以及资源名称统一以小写单词+下划线的方式命名,anim、color、dimen、string等命名格式为:资源类型_模块名_逻辑名称/业务功能
  13. drawable资源的命名,以对应的用途开头,如分割线以divider_开头,图标以ic_开头、背景图以bg_开头,按钮以btn_开头,选择器状态以对应状态结尾,如xxx_normal、xxx_pressed、xxx_selected等。如果UI设计提供多套资源图标则必须按照mdpi :hdpi:xhdpi:xxhdpi:xxxhdpi = 2:3:4:6:8的比例放入对应的drawable文件夹中。
  14. layout布局文件,Activity、Fragment、Dialog等命名格式为activity/frgament/dialog_模块名_逻辑名称/业务功能, include的布局文件统一以layout_开头, layout_模块名_xxx, 列表项的item布局统一以item_开头, item_模块名_xxx
  15. 布局文件中涉及到string、color、dimen等的,需要提取到对应的xml文件中,以@资源的形式引用
  16. 控件id的命名格式为,控件缩写+下划线+逻辑名,常见控件缩写表:
控件 缩写
LinearLayout ll
RelativeLayout rl
FrameLayout fl
ConstraintLayout cl
ListView lv
GridView gv
ScollView sv
RecyclerView rv
TextView tv
EditText et
Button btn
ImageView iv
CheckBox cb
RadioButton rb
RadioGroup rg
WebView wv

其它控件的缩写推荐使用小写字母并用下划线进行分割,例如:ProgressBar 对应的缩写为progress_bar;DatePicker 对应的缩写为date_picker。
17 . 代码中涉及到控件的命名,规则为【控件逻辑名称】+【控件名称】,即跟布局文件中id的命名顺序反过来。其中控件名称可以用缩写,但是不能缩写到两个字母,如xxxImageView可以缩写为xxxImage, 不能缩写为xxxIv, xxxEditText可以缩写为xxxEdit, 不能缩写为xxxEt。

二. 常量

  1. 超过一次以上引用的魔法值(共享值),不允许直接出现在代码中。(即跨方法、跨类、跨页面等用到魔法值必须共享常量)
  2. 在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字1 混淆,造成误解。
  3. 代码中不允许直接出现中文字符串,使用string资源引用或者常量代替。

三. 代码格式

  1. Android Studio/Eclispse编写的代码必须进行代码格式化(包括 .java、.xml文件,使用快捷键按照默认风格)。
  2. 大括号的使用约定。非空代码块则:
    1) 左大括号前不换行。
    2) 左大括号后换行。
    3) 右大括号前换行。
    4) 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
  3. 左小括号和字符之间不能出现空格;同样,右小括号和字符之间也不能出现空格;而左大括号前需要空格。 if (a == b)
  4. if/for/while/switch/do 等保留字与括号之间都必须加空格。
  5. 任何二目、三目运算符的左右两边都必须需要加一个空格。(包括赋值运算符=、逻辑运算符&&、加减乘除符号等。)
  6. 单行代码过长时,必须换行(在Android Studio中以编辑面板的右侧竖线为换行标志),换行时遵循如下原则:
    1) 第二行相对第一行缩进 4 个空格,从第三行开始,保持与第二行对齐。
    2) 运算符与下文一起换行。
    3) 方法调用的点符号与下文一起换行。
    4) 方法调用中的多个参数需要换行时,在逗号后进行。
    5) 在括号前不要换行。
  7. 方法参数在定义和传入时,多个参数逗号后边必须加空格。
  8. 一行一个语句,每个语句后要换行。
  9. 每次只声明一个变量,不要推荐使用组合声明,比如int a, b;。如非必须,变量在需要的时候才去声明并初始化。
  10. 方法参数不宜过多,如果方法参数太多,函数声明方法名会很长,可以考虑用实体类来封装参数。
  11. 方法中的行数不宜过多,一个方法中代码超过80行时,必须进行方法提取(refactor)

四. OOP规约

  1. 必须用类名来访问此类的静态变量或静态方法,避免通过类的对象变量去引用,无谓增加编译器解析成本。
  2. 所有的覆写方法,必须加@Override 注解。
  3. 只有相同参数类型并且相同业务含义的,才可以使用 Java 的可变参数,可变参数类型避免使用 Object。可变参数必须放置在参数列表的最后。
  4. 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
  5. 必须使用常量或确定值的对象来调用equals,避免空指针异常。
  6. 所有相同类型的对象之间值相等的比较,全部使用 equals 方法比较,不能用==(除非是判断相同对象引用或基本数据类型除外)。
  7. 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
  8. 通过用 String.split(xxx); 方法得到的数组,使用前必须做长度检查,否则会有抛 IndexOutOfBoundsException 的风险。
  9. 避免在getter/setter 方法中增加复杂的业务逻辑,增加排查问题的难度。
  10. 循环体内String的拼接,必须使用 StringBuilder 的 append方式。
  11. 不允许被修改的类、对象、方法必须添加final关键字。
  12. 避免出现重复的代码(Don’t Repeat Yourself),即 DRY 原则。(避免不停的复制黏贴代码,使用OO思想提取重复代码)
  13. 方法或构造函数中传递过多的参数时,请使用对象封装。如set(ParamWrap param)
  14. 把一个基本数据类型转为字符串时,一律使用String.valueOf(数据) 的方式,包装类型可使用数据类型.toString(),避免使用数据 + ""的方式

五. 集合处理

  1. 关于 hashCode 和 equals 的处理,遵循如下规则:
    1) 只要重写 equals,就必须重写 hashCode。
    2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
  2. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的参数必须是类型完全一样的数组(否则强转数组类型时将出现 ClassCastException),并且大小就是 list.size()。
  3. 使用 Arrays.asList()把数组转换成集合时,不能对集合使用其修改的相关方法, 调用add/remove/clear 等方法会抛出 UnsupportedOperationException 异常。
  4. 泛型通配符来接收返回的数据,此写法的泛型集合不能使用 add 方法,而不能使用 get 方法,作为接口调用赋值时易出错。(第一、频繁往外读取内容的,适合用。第二、经常往里插入的,适合用)
  5. 不能在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
  6. 在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort,Collections.sort 会报 IllegalArgumentException 异常。
    说明:三个条件如下
    1) x,y 的比较结果和 y,x 的比较结果相反。
    2) x>y,y>z,则 x>z。
    3) x=y,则 x,z 比较结果和 y,z 比较结果相同。
  7. 可以对集合进行快速去重操作时,避免使用 List 的 contains 方法进行遍历、对比、去重操作,可以利用 Set 元素唯一的特性。
  8. 必须高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
集合类 Key Value Super 说明
Hashtable 不允许为null 不允许为null Dictionary 线程安全
ConcurrentHashMap 不允许为null 不允许为null AbstractMap 锁分段技术(JDK8:CAS)
TreeMap 不允许为null 允许为null AbstractMap 线程不安全
HashMap 允许为null 允许为null AbstractMap 线程不安全

由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上,存储 null 值时会抛出 NPE 异常。

六. 控制语句

  1. 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。
  2. 在 if/else/for/while/do 语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式:if (condition) statements;
  3. 如果非得使用if()…else if()…else…方式表达逻辑,避免超过 3 层, 以至于后续代码维护困难。(可以使用卫语句、策略模式、状态模式等来实现)
  4. 不要在if/while等条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
  5. 不要在for循环体中执行 try-catch 操作, 如有需要放在外层。
  6. 避免采用取反逻辑运算符。如 if (!isOk)
  7. 在for循环之前, 必须对集合变量进行判空和lengh检查,避免出现NPE或IndexOutBounds异常。

七. 注释规约

  1. 类、类属性、类方法的注释必须使用 Javadoc 规范,必须使用/*内容/格式,不得使用// xxx方式。
  2. 方法内部单行注释,必须在被注释语句上方另起一行,使用//注释。避免使用行尾注释。方法内部多行注释使用/* */注释,注意与代码对齐。
  3. 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释说进行说明含义。
  4. 所有的类都必须添加创建者和创建日期,类的说明。(可以用AS快捷键或模板生成)
  5. 无用的、过时的注释必须删除,避免维护代码时造成阅读歧义和困惑。
  6. 反常规、反常识的代码以及核心业务逻辑,必须添加代码注释。
  7. 注释掉的代码,可以直接删除,如果保留,则需要注释说明是干什么的。
  8. 方法的代码修改时,如果该方法之前有注释,则也要对注释进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。
  9. 如果是临时处理或者打算留待将来处理的,必须添加TODO标记备忘,并且在上线提测之前检查工程中的TODO是否已经全部完成(十分重要)。如果是多人协作开发的项目,TODO后面必须添加作者。如果是错误,不能工作的代码,或者目前难以修复的bug,必须用FIXME来注释。

八. 异常处理

  1. 注意防止 NPE,是程序员的基本修养。注意 NPE 产生的场景:
    1)使用全局对象变量和方法传递进来的对象变量之前,必须进行判空操作。
    2)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。 反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
    3) 数据库的查询结果可能为 null。
    4) 即使集合是 isNotEmpty,集合里取出的数据元素也可能为 null。
    5) 调用一个其他方法返回的对象时,一律要求进行空指针判断,防止 NPE。
    6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
    7) 注意可控与不可控场景,如当前方法内new出来的对象内容是可控的,而接口返回的数据解析生成的对象是不可控,将NPE的重点放在不可控的对象上。
  2. 方法的返回值为 null的,必须添加@Nullable注释,方法参数同样需要添加。
  3. catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。
  4. 捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之(至少要添加Log输出)。如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
  5. 有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。
  6. finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。(如果 JDK7 及以上,可以使用 try-with-resources 方式。)
  7. 不要在 finally 块中使用 return。
  8. 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
  9. 传递资源id的方法,必须用Android注释类,标明参数id类型,如setText(@ResId id), 否则有异常风险。
  10. log输出必须添加TAG, TAG不能为空,方便根据tag定位问题。

九. Android基本组件

  1. Activity 间的数据通信,对于数据量比较大的,避免使用Intent + Parcelable的方式,以免造成TransactionTooLargeException,可以考虑其他替代方案。
  2. Activity 间通过隐式Intent 的跳转,在发出Intent 之前必须通过resolveActivity检查,避免找不到合适的调用组件,造成ActivityNotFoundException 的异常。
  3. 避免在Service#onStartCommand()/onBind()方法中执行耗时操作,如果确实有需求,应改用IntentService 或采用其他异步机制完成。
  4. 避免在BroadcastReceiver#onReceive() 中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在BroadcastReceiver 内创建子线程去做。
  5. 避免使用隐式Intent 广播敏感信息,信息可能被其他注册了对应BroadcastReceiver 的App 接收。
  6. 如非必须,避免使用嵌套的Fragment。
  7. Service 需要以多线程来并发处理多个启动请求,建议使用IntentService,可避免各种复杂的设置。
  8. 对于只用于应用内的广播,优先使用LocalBroadcastManager 来进行注册和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。
  9. 当前Activity 的onPause 方法执行结束后才会创建(onCreate)或恢复(onRestart)别的Activity,所以在onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率。
  10. Activity 或者Fragment 中动态注册BroadCastReceiver 时,registerReceiver() 和 unregisterReceiver() 要成对出现。一般为onCreate/onDestroy或onResume/onPause。
  11. Android 基础组件如果使用隐式调用,应在 AndroidManifest.xml 中使用 或在代码中使用 IntentFilter 增加过滤。

十. UI 与布局

  1. 布局中不得不使用ViewGroup 多重嵌套时,不要使用LinearLayout 嵌套,改用RelativeLayout,可以有效降低嵌套数。
  2. 源文件统一采用UTF-8 的形式进行编码。
  3. 禁止在非UI 线程进行View 相关操作。
  4. TextView等文本大小使用sp,View 宽高间距大小使用单位dp。
  5. 禁止在设计布局时多次为子View 和父View 设置同样背景进而造成页面过度绘制,推荐将不需要显示的布局进行及时隐藏。
  6. 尽量不要使用AnimationDrawable展示过多的图片资源(如几十个),它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。
  7. 不能使用ScrollView 包裹 ListView/GridView/ExpandableListVIew; 因为这样会把ListView 的所有Item 都加载到内存中,要消耗巨大的内存和cpu 去绘制图面。
  8. 使用Adapter 的时候,如果你使用了ViewHolder 做缓存,在 getView() 的方法中无论这项convertView 的每个子控件是否需要设置属性(比如某个TextView 设置的文本可能为null,某个按钮的背景色为透明,某控件的颜色为透明等),都需要为其显式设置属性(Textview 的文本为空也需要设置 setText(“”),背景透明也需要设置),否则在滑动的过程中,因为adapter item 复用的原因,会出现内容的显示错乱。
  9. 灵活使用布局,推荐merge、ViewStub 来优化布局,尽可能多的减少UI布局层级,推荐使用FrameLayout,LinearLayout、RelativeLayout 次之。

十一. 进程、线程与消息通信

  1. 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
  2. 不允许在应用中自行显式创建线程(如new Thread().start()), 必须通过线程池提供(AsyncTask 或者ThreadPoolExecutor 或者其他形式自定义的线程池工具类),并且要限制最大线程数,减少系统资源的消耗。创建线程池避免使用Executors 去创建,而是通过 ThreadPoolExecutor 的方式。
  3. 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。(线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是 A、B、C,否则可能出现死锁。)
  4. 并发环境下,单例模式避免使用双重检查锁(double-checked locking)实现延迟初始化的方式, 可以采用静态内部类的方式实现。
  5. 在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
  6. 不要通过Intent 在Android 基础组件之间传递大数据(binder transaction 缓存为1MB),可能导致 OOM。
  7. 在Application 的业务初始化代码加入进程判断,确保只在自己需要的进程初始化。特别是后台进程减少不必要的业务初始化。
  8. 子线程中不能更新界面,更新界面必须在主线程中进行,网络操作不能在主线程中调用。
  9. 谨慎使用Android 的多进程,多进程虽然能够降低主进程的内存压力,但会遇到如下问题:
    1)首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白屏还是黑屏和新Activity 的主题有关);
    2)应用内多进程时,Application 实例化多次,需要考虑各个模块是否都需要在所有进程中初始化。

十二. 文件与数据库

  1. 任何时候不要硬编码文件路径,请使用Android 文件系统API 访问。
  2. 当使用外部存储时,必须检查外部存储的可用性。
  3. 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用FileProvider。
  4. SharedPreference 中只能存储简单数据类型(int、boolean、String 等),复杂数据类型建议使用文件、数据库等其他方式存储。
  5. 数据库Cursor 必须确保使用完后关闭,以免内存泄漏。可以使用JDK1.7推荐的try-with-resource语法简化操作。
  6. 多线程操作写入数据库时,需要使用事务,以免出现同步问题。
  7. 执行SQL 语句时,应使用SQLiteDatabase#insert()、update()、delete(),不要使用SQLiteDatabase#execSQL(),以免SQL 注入风险。
  8. 如果ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受信任的外部数据直接拼接在原始 SQL 语句中。推荐使用selectionWhere和selectionArgs搭配。

十三. Bitmap、Drawable 与动画

  1. 当你不得不手动去加载一张图片到内存中去展示的时候,一定要注意图片的宽高尺寸,不管来源是sd卡、网络还是应用内置的背景图片,因为图片的宽高和格式决定了它的内存占用大小,避免OOM,推荐使用Glide等第三方图片加载库。
  2. 展示大图之前,必须进行尺寸压缩,应根据实际展示需要,压缩图片尺寸,而不是直接显示原图。手机屏幕比较小,直接显示原图,并不会增加视觉上的收益,但是却会耗费大量内存并易导致OOM。
  3. 网络传输图片之前必须进行质量/体积压缩,以减少流量,加快上传速度,推荐使用三方库压缩处理。
  4. 加载大图片或者一次性加载多张图片,应该在异步线程中进行。图片的加载,涉及到 IO 操作,以及CPU 密集操作,很可能引起卡顿。
  5. 在ListView,ViewPager,RecyclerView,GirdView 等组件中使用图片时,应做好图片的缓存,避免始终持有图片导致内存溢出,也避免重复创建图片,引起性能问题。建议使用Fresco、Glide等图片库。
  6. png 图片使用TinyPNG 或者类似工具压缩处理,减少包体积。(尤其针对UI设计师提供过来的图标资源)
  7. 使用 RGB_565 代替 RGB_888,在不怎么降低视觉效果的前提下,减少内存占用。
  8. 谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个 gif 图片的大小。
  9. 在 Activity#onPause() 或 Activity#onStop() 回调中,关闭当前 activity 正在执行的的动画。
  10. 在动画或者其他异步任务结束时,应该考虑回调时刻的环境是否还支持业务处理。例如Activity 的 onStop() 函数已经执行,且在该函数中主动释放了资源,此时回调中如果不做判断就会空指针崩溃。
  11. 在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面, onAnimationEnd 可能会因各种异常没被回调建议加上超时保护或通过 postDelay 替代 onAnimationEnd。
  12. 当View Animation 执行结束时,调用 View.clearAnimation() 释放相关资源。

十四. 安全

  1. 将 android:allowbackup 属性必须设置为false,阻止应用数据被导出。
  2. 确保应用发布版本的 android:debuggable 属性设置为 false。
  3. 不要把敏感信息打印到 log 中,发布版必须关闭开发模式的log输出,log工具类必须设置开关。
  4. 在SDK 支持的情况下,Android 应用必须使用 V2 签名,这将对 APK 文件的修改做更多的保护。
  5. 所有的 Android 基本组件(Activity、Service、BroadcastReceiver、ContentProvider 等)都不应在没有严格权限控制的情况下,将 android:exported 设置为 true。
  6. WebView 应设置 WebView#getSettings()#setAllowFileAccess(false)、
    WebView#getSettings()#setAllowFileAccessFromFileURLs(false)、
    WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false),阻止 filescheme URL 的访问。
  7. 本地加密秘钥不能硬编码在代码中,更不能使用 SharedPreferences 等本地持久化机制存储。应选择 Android 自身的秘钥库(KeyStore)机制或者其他安全性更高的安全解决方案保存。
  8. 在 Android 4.2(API Level 17) 及以上,对安全性要求较高的应用可在Activity中,对 Activity 所关联的 Window 应用WindowManager.LayoutParams.FLAG_SECURE,防止被截屏、录屏。但要注意的是,一个 Activity 关联的 Window 可能不止一个,如果使用了 Dialog / DialogFragment 等控件弹出对话框,它们本身也会创建一个新的 Window,也一样需要保护。
  9. zip 中不要包含 …/…/file 这样的路径,可能被篡改目录结构,造成攻击。
  10. 使用Android 的 AES/DES/DESede 加密算法时,不要使用 ECB 加密模式,应使用 CBC 或 CFB 加密模式。
  11. 应用开启混淆必须十分小心,注意必须keep的类,尤其第三方的库,否则将导致release版本调用异常。
  12. 应用中涉及到so库时,必须注意保证每一个armeabi文件夹中so的数量一致(尤其是多module大量依赖三方的时候),否则将导致应用加载so异常崩溃。如非必要,只保留一个armeabi文件夹。

十五. AS配置

  1. 组件化插件化的方式下,多个module依赖相同的依赖库,依赖库版本必须集中控制,如建立公共的gradle文件来管理。同样compileSdkVersion、minSdkVersion、targetSdkVersion也需要集中控制。
  2. 签名文件、IP Host、一些key或控制开关等尽量使用配置文件,通过gradle去配置,在变更时避免修改代码。高效使用BuildConfig文件可以避免很多麻烦。

你可能感兴趣的:(Android开发代码规范)