Android开发代码规范总结

本篇开始总结Android开发中的一些注意事项,提高代码质量(仅供参考):

1.  Activity间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable 的方式,可以考虑 EventBus 等替代方案,以免造成TransactionTooLargeException 。

2.  Activity间通过隐式 Intent 的跳转,在发出 Intent 之前必须通过 resolveActivity 检查,避免找不到合适的调用组件,造成 ActivityNotFoundException 的异常。

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);
                }
            }
        }
    }

3.  避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果确实有需求,应改用 IntentService 或 采用其他异步机制完成;避免在BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时操作,应该创建IntentService完成,而不应该在BroadcastReceiver内创建子线程去做。查看IntentService的具体使用教程(后补)

4.  建议不要在 Activity#onDestory() 内执行释放资源的工作,例如一些工作线程的销毁和停止,因为 onDestory() 执行的时机可能比较晚。可根据实际需要,在Activity#onPause()/onStop()中结合isFinishing的判断来执行。

5.  对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册和发送,LocalBroadcastManager 安全性更好,可以避免广播泄露以及广播被拦截等安全问题,同时相对全局广播,本地广播的更高效

LocalBroadcastManager.getInstance(context).sendBroadcast(intent);

@Override
protected void onResume() {
   super.onResume();
   LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
}
@Override
protected void onPause() {
   super.onPause();
   LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
}

6.  当前 Activity 的 onPause 方法执行结束后才会执行下一个 Activity 的 onCreate 方法,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率。

7.  Activity 或者 Fragment 中动态注册 BroadcastReceiver 时, registerReceiver()和unregisterReceiver()要成对出现。否则会导致内存泄露,加重 SystemService 负担。

8.  在需要时刻刷新某一区域的组件时,建议通过以下方式避免引发全局 layout 刷新:

  1) 设置固定的 view 大小的高宽,如倒计时组件

  2) 调用 view 的 layout 方式修改位置,如弹幕组件等;

  3) 通过修改 canvas 位置并且调用 invalidate(int l, int t, int r, int b)等方式限定刷新区域;

  4) 通过设置一个是否允许 requestLayout 的变量,然后重写控件的 requestlayout、onSizeChanged 方法,判断控件的大小没有改变的情况下,当进入requestLayout 的时候,直接返回而不调用
    super 的 requestLayout 方法。
 
9.  新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。说明:Executors 返回的线程池对象的弊端如下:
  1) FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM;
  2) CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());

11.  谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会遇到如下问题:
  1) 不能实现完全退出所有 Activity 的功能
  2) 首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白屏还是黑屏和新 Activity 的主题有关);
  3) 应用内多进程时,Application 实例化多次,需要考虑各个模块是否都需要在所有进程中初始化;
  4) 多进程间通过 SharedPreferences 共享数据时不稳定。

12.  使用完毕的图片,应该及时回收,释放宝贵的内存。

// 使用结束,在 2.3.3 及以下需要调用 recycle()函数,在 2.3.3 以上 GC 会自动管理,除非你明确不需要再用。if (Build.VERSION.SDK_INT <= 10) {
  bitmap.recycle();
}
bitmap = null;

13.  在 Activity.onPause() 或 Activity.onStop() 回调中,关闭当前 activity 正在执行的动画。

public void onPause(){
   //页面退出,及时清理动画资源
   mImageView.clearAnimation();
}

14. 使用 ARGB_565 代替 ARGB_888,在不怎么降低视觉效果的前提下,减少内存占用。

      android.graphics.Bitmap.Config 类中关于图片颜色的存储方式定义:
   1) ALPHA_8 代表 8 位 Alpha 位图;
   2) ARGB_4444 代表 16 位 ARGB 位图;
   3) ARGB_8888 代表 32 位 ARGB 位图;
   4) RGB_565 代表 8 位 RGB 位图。
位图位数越高,存储的颜色信息越多,图像也就越逼真。大多数场景使用的是ARGB_8888 和 RGB_565,RGB_565 能够在保证图片质量的情况下大大减少内存的开销,是解决 oom 的一种方法。但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么就不能使用 RGB_565。

Config config = drawableSave.getOpacity() != PixelFormat.OPAQUE ? Config.ARGB_8888 : Config.RGB_565; 
Bitmap bitmap = Bitmap.createBitmap(w, h, config);

15. ※ ※ ※  在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面 , onAnimationEnd 可能会 因各种异常没被回调 (参考:https://*.com/questions/5474923/onanimationend-is-not-getting-called-onanimationstart-works-fine),建议加上超时保护 或通过 postDelay 替 代 onAnimationEnd。

View v = findViewById(R.id.xxxViewID);
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
new Handler().postDelayed(new Runnable() {
        public void run() {
            if (v != null) {
                v.clearAnimation(); // 当 View Animation 执行结束时,调用 View.clearAnimation()释放相关资源
            }
        }
    }, anim.getDuration());
v.startAnimation();

 16. 给Fragment传递参数时,不要创建有参构造方法,要通过Arguments传参。因为Activity在恢复Fragment会通过反射查找无参构造函数。