开发时打印日志,发布的应用不打印日志,这是几乎每个Android项目都会有的需求。有的会写一个工具类,所有日志通过调用工具类的静态方法来打印,在此静态方法里做判断:
import android.util.Log; public class LogUtils { public static final boolean IS_LOG_ON = true; // 发布时改为false public static void log(String tag, String msg) { if (IS_LOG_ON) { Log.d(tag, msg); } } }
这种做法有一个问题:在release版本中仍然存在打印日志的开销,如下面的代码所示:
void someMethod(int index){ LogUtils.log("tag", "index: " + index); // 拼了! ... }
release版本在这里虽然不会打印日志,但是仍然会new一个StringBuilder
对象来拼接字符串,这是有开销的,如果在循环或者会被多次调用的方法里打印日志,情况会更糟糕。因此,最好在编译期就彻底删除掉日志打印的调用。iOS开发里用宏可以方便地实现这一点,其实Android也提供了这样的工具:ProGuard。在ProGuard的配置文件里可以声明某些方法没有副作用,在打包release版本时ProGuard会自动帮你删除掉这些方法。这样我们可以直接使用android.util.Log
打印日志,然后在ProGuard配置文件中添加下段代码:
-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(...); }
就可以实现release版本无日志。需要注意的是使用此功能必须打开ProGuard的优化开关,具体配置可参考SDK目录下的tools/proguard/proguard-android-optimize.txt文件。
已知最快的办法是美团技术博客的这篇文章介绍的第三种方法。需要注意的是在生成渠道包后需要重新用 zipalign 处理 apk。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(Bundle savedInstanceState); TextView textView = findViewById(R.id.title); textView.setText(getString(R.string.title)); // 应使用更简洁的API textView.setText(R.string.title); // 更易读 StringBuilder sb = new StringBuilder(); sb.append("some text"); sb.append("more text"); textView.setText(sb.toString()); // toString()是多余的 textView.setText(sb); // 更简洁 /* * 拿到一个Editor,更新一个值 */ Editor editor = getSharedPreferences("my preference", Context.MODE_PRIVATE) .edit(); editor.putString("some key", "some value"); editor.commit(); /* * 使用链式API,更简洁 */ getSharedPreferences("my preference", Context.MODE_PRIVATE).edit() .putString("some key", "some value").commit(); } // 对传入的一个目录做操作,如果目录不存在,需要先创建,下面是繁琐的写法: public void mkdirIfNeededVerbose(File file) { boolean flag = true; if (!file.exists()) { flag = file.mkdir(); } if (flag) { // do file operations } } // 简洁的写法,与繁琐的写法完全等价,但短小易读 public void mkdirIfNeeded(File file) { if (file.exists() || file.mkdir()) { // do file operations } }
有这么一行代码:
new DecimalFormat("#0.00").format(2.34);
在某些手机上这一行会返回“2,34”,小数点变成了英文逗号。阅读文档发现,直接用new
这么创建DecimalFormat
对象,会使用系统默认的Locale。进一步搜索发现,在某些国家或地区对应的Locale下,小数点就是应该被格式化为英文逗号,因为在这些国家(如法国)里人们的习惯就是如此。如果要保证格式化后为小数点,必须指定Locale:
DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US); df.applyPattern("#0.00"); df.format(2.34);
第一种写法在Lint下不会报警,在大多数手机上也能正确运行。没有类似经验的人很难想到这里不指定Locale会出问题。另一方面,文档强烈地建议了使用指定Locale的方法来创建对象。所以,我们要重视文档。单就Locale这点来说,还有很多方法使用系统默认Locale,这在API文档中一般都有说明。比如:
"i".toUpperCase(); // 使用系统默认Locale,结果可能不是I,比如在土耳其Locale下,结果为İ "i".toUpperCase(Locale.US); // 指定Locale,结果一定是I
如果开发者想要的行为是在任何情况下结果都为I,那么必须指定Locale,一般使用Locale.US
,Android保证这个Locale在每个Android手机上都可用。
Android代码中有各种视图类,这些类的实例变量取名时最好以View结尾(或其他能清晰表明视图属性的单词,如Button)。这样能避免误把视图对象当作model对象。下面的例子说明了随意起名可能导致的错误:
public class MainActivity { private TextView mPrize; private TextView mPrizeView; // 推荐:变量名以View结尾,表明自己是视图 ... private String getPrize() { return mPrize + "元"; // 出错了!把视图误当作了model } ... }
在项目规模上去了之后,靠命名规范去尽可能地避免这样的低级错误,不失为一个办法。
comments powered by Disqus