Java代码规范及注意事项

一.通用规范:

1.避免对反射的资源进行混淆

说明:需要进行反射的资源不能混淆,无论是字段、方法还是类(这种情况一般多出现在使用Parcelable的场景)。

案例:进入过电话界面之后,进入设置--应用程序,清除电话的数据,手机弹出文件停止运行

     02-28 11:56:30.651  9720  9843 E AndroidRuntime: Caused by: android.os.BadParcelableException: 
    Parcelable protocol requires a Parcelable.Creator  object called CREATOR on class com.android.documentsui.model.RootInfo
    原因分析:android 7.1上,系统app被混淆导致DocumentsUI崩溃。详见STD1616 [B170228-338]

2.同一个功能如果需要方便的打开或者关闭,需提炼出一个总开关,确保只需修改一处

说明:无

案例:相机修改一个水印开关需修改6个类,有些类还需要修改多处,如下图

     
     逻辑调整后只需修改一个默认值即可(修改res/values/strings_nottranslat.xml里面的默认值)满足功能快速开关,1s完成切换。

3.使用某个函数的返回值,如果不能肯定该函数不会返回null的话,要加判空处理

说明:使用某个函数的返回值,而该函数有可能返回null

案例:

    Bitmap bitmap = Bitmap.createBitmap(…);
    int width = bitmap.getWidth();

4.直接使用传入的参数,要对参数进行检查

说明:

   1)对传入的参数进行检查;
   2)字符串比较时,将常量写在前面

案例:

   String state = intent.getStringExtra(“key”);
   if(state.equals(“ready”)) {
   }

5.要小心识别初始化时的运行态,避免在重要对象初始化时加判断条件或延时处理

说明:

   对于包含handler的对象,需要特别注意该对象的初始化,当该对象在线程中初始化时可能会出现问题。特别的,由于存在调用与被调用的关系,有些初始化是很隐藏的。
   

建议:

  1)使用一个成员变量前,先检查它的初始化的地方,保证它是赋值过的。
  2)在构造函数中,如果成员变量耗时/耗内存不多,尽可能初始化它的默认值
  3)避免延迟初始化变量,如:   
     new Handler().postDelayed( {
       mService = ……
     }, 1000);

6.延迟一段时间执行操作时,要重新检查变量的有效性

说明:

案例:异步处理变量,在执行该变量的操作之前,又被别的线程赋值为null

    

7.提前finish并return,会导致某些变量初始化或注册等操作未执行完,引起后面空指针

说明:满足某个条件return后,要检查后面的代码执行是否有影响,如未释放资源等。

案例:

    

建议:插入这类代码时,检查其下方变量的初始化及其影响。

8.单例类中创建的对象要注意释放

说明:把一个对象(如view、Context、Listener、callback等)传给单例,而单例将这个对象保存起来,并且最终没有释放的话,就会引起内存泄漏。

案例:

    

二. 多线程


 请参阅《Efficient Android Threading》,其中讲解了Android中如何进行异步处理。
   https://developer.android.com/training/articles/perf-anr.html

As.png

1.线程资源尽量通过线程池提供,不建议在应用中自行显式创建线程。

说明:这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。

   使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量
   同类线程而导致消耗完内存或者“过度切换”的问题。

2.system_server进程中尽量不要启动新的Thread

说明:

   公司手机上system_server中现有Thread已经很多,进程变的越来越庞大。如果自有业务都去增加Thread,system_server的负载会变的越来越重,
   其实Google针对这个问题进行了很好的设计和思考,Android System _server引入了分类Thread(android.fg,android.bg,android.ui,androud.io)。

建议方案:

   当你在system_server进程中计划启动HandlerThread或者Thread时候,看看能否复用已有的Thread进行。

参考代码:

    PrintManagerService.java:
    BackgroundThread.getHandler().post(new Runnable())...
    LocationManagerService.java:
    mLocationHandler =new LocationWorkerHandler(BackgroundThread.get().getLooper());

检测工具: 暂无,后续可以用UT冒烟测试用例点检。

3.获取单例对象要线程安全,在单例对象里面做操作也要保证线程安全。

说明:

    资源驱动类、工具类、单例工厂类都需要注意。

4.多线程中的资源需保持同步。

说明:

   多线程读写全局变量,需要加锁;如果都是读,可以不加锁;对于多线程导致的空指针、数组越界问题,
   建议将全局变量深度拷贝(clone为浅拷贝)到局部变量,然后判断使用该局部变量(对于集合类型不可以简单地使用“=”局部化,需要这样局局部化a = new set(b))。
    

案例:

    (1)在应用的生命周期onPause里面有个子线程发送一个250ms的延时消息执行屏幕色彩相关的操作setDefaultMode(),其中包括对象
         ColorManager的使用。在生命周期onDestroy的主线程release()里面释放了对象ColorManager。 从代码设计逻辑上看,是有较
         明显的缺陷的:release()很可能比setDefaultMode先执行,从而报空指针,应用程序崩溃。
         反思:1)对象的申请以及其他相关的操作跟对象的释放不在同一条线程中(初始化与释放逻辑不在同一线程,可能会出现释放出错情况)。
               2)延时消息具有不稳定性,应慎用。
         改进建议: 1)对象的申请以及其他相关的操作跟对象的释放需要在同一线程或者采用同步,以保证时序正确。
                   2)如果非得加延时,那么对象的释放操作也应该放在该延时里面进行。
     (2)对关键的资源闪光灯进行操作没有做同步处理,导致重复操作闪光灯引起ANR,详见:
          http://192.168.2.91:8000/rdms/qm/bug/bugAction.do?action=getBugDetail&id=1070294&popup=true 
     (3) 多线程操作全局变量
         

5.避免子线程和主线程持有同一把锁执行耗时操作。

说明:

案例:系统用户界面死锁崩溃问题

    1)原因分析:系统用户界面应用的RecentHelper中mRecentTaskItems的列表更新与主线程使用了同步锁。 
    2)解决方案:取消同步锁,在子线程中使用局部变量承载数据库列表,然后把局部变量值转至主线程更新全局变量列表。

6.避免主线程的耗时操作。

说明:

   通常的耗时操作有:网络操作、IO操作、数据库操作、Binder调用、反射调用、Bitmap操作及频繁循环查询等;
   Application的onCreate、Service的onCreate&onBind&...、Receiver的onReceive等,都默认运行在主线程;
   在主线程通过Settings.System.putInt写数据应减少调用次数,如加判断在需要时才执行,建议putInt异步执行;
   避免应用主线程调用getPhoneStorageState()等可能由于系统原因引起阻塞的接口。

案例:VoLTE导致ANR

   (1)原因分析:一方面,SIM卡状态变化、切换数据卡事件非常频繁;另一方面,在主线程查询短信中心号码,需要从modem中查询数据,较慢。
   (2)反思:为什么发现不了这个问题?
        1)开发阶段——人员、需求变更频繁,新的开发人员对模块不够熟悉,代码风格和可读性差,影响新的人员尽快熟悉代码。
        2)DR阶段——评审时,主要关注需求是否满足,不够重视非功能特性(如性能等),没有充分评估广播的频繁度,代码执行时间。
        3)测试阶段——BG ANR问题的隐蔽性。
        4)跟踪阶段——云诊断数据突变很明显,但组内没有关注ANR的数据。

7.线程泄露

说明:

反例:相机线程泄露

     1)原因分析:new AsyncTask<...>() {}.executeOnExecutor(Executors.newCachedThreadPool());  
             因为newCachedThreadPool会一直创建不同的对象导致线程泄露。
     2)解决方案:Executors.newCachedThreadPool()用静态常量代替,即换成AsyncTask.THREAD_POOL_EXECUTOR或者
                 定义一个static类型的对象Executors.newCachedThreadPool()作为参数传入进去:
               private static  ExecutorService Cached_TASK_EXECUTOR = (ExecutorService) Executors.newCachedThreadPool();

8.CountDownLatch

说明:

   使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法;
   线程执行代码注意catch异常,确保countDown方法可以执行,避免主线程无法执行至countDown方法,直到超时才返回结果。
   注意,子线程抛出异常堆栈,不能在主线程try-catch到。

9.HandlerThread在onDestroy或unregister等释放操作时,要调用quit()退出

说明:

   HandlerThread会创建一个线程,除非主动调用quit(),否则是不会退出的;详见此链接

案例:相机中使用的HandleThread没有主动quit,引发内存泄漏。

    CameraActivity.mInitializeThread,多次进入相机然后按返回键,CameraActivity来回多次执行onCreate--onDestoty可复现。


Application

1.基本概念

说明:

   Application可以说是单例 (singleton)模式的一个类,且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。
   因为它是全局的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象,因此在安卓中我们可以避免使用静态变量来存储长久保存的值,而用Application。
   可通过Application来进行一些数据传递、数据共享及数据缓存等操作。

使用场景:

   通常Application全局对象是通过Context或者Activity的getApplicationContext()方法获得的,在activity中这样做:
       appContext = (AppContext)this.getApplicationContext();
   如果有Context对象,还可以:appContext = (AppContext)mContext.getApplicationContext();
   但是很多时候我们的代码可能在activity之外,且没有context对象的引用,但是又需要获得AppContext对象,原始的做法
    可能是想办法将activity或者context传递到需要调用的地方,但是这样代码耦合度太高,可读性差。
    Application生命周期函数中的onCreate是被自动调用的,可以利用这点来获得这个Application对象:
        private static AppContext instance;
       public static AppContext getInstance() {
            return instance;
       } 
       @Override
       public void onCreate() {
         // TODO Auto-generated method stub
         super.onCreate();
         instance = this;
       }
    这样我们就能在app工程的任何地方通过AppContext.getInstance()来获得Application全局对象。比如我定义了一个工具类,
    在工具类中我们需要使用Context的getExternalFilesDir()方法,但是这个工具类没有直接的办法获取到context,于是我们可以:
       return AppContext.getInstance().getExternalFilesDir(null);

2.避免在Application的onCreate中执行比较耗时的操作

说明:

   如果在Application的onCreate中执行比较耗时的操作,将直接影响的程序的启动时间。

案例:

3.通过Application在两个Activity间传递数据需防范内存泄露

说明:

   在Application中创建一个HashMap<String,Object> ,以字符串为索引,Object为value这样我们的HashMap就可以存储任何类型的对象了。
   在Activity A中把需要传递的对象放入这个HashMap,然后通过Intent或者其它途经再把这人索引的字符串传递给Activity B ,Activity B 
   就可以根据这个字符串在HashMap中取出这个对象了。只要再向下转个型 ,就实现了对象的传递。
   

反思:

   数据传递完成之后,把存放在application的HashMap中的数据remove掉,以免发生内存泄漏。

4.自定义了Application后,必须在manifest中修改application标签属性

说明:

   为了更好的利用Application的这一特性,比如我们需要Application来保存一些静态值,需要自定义继承于Application的类,然后在这个类中
   定义一个变量来保存。在默认情况下应用系统会自动生成Application对象,但是如果我们自定义了Application,那就需要告知系统,实例化的时候,
   是实例化我们自定义的,而非默认的。

Log使用注意事项

1.避免易被忽略的空指针问题

说明:Log.d(TAG, null); 这个会报空指针

案例:Log.d(TAG, e.getMessage()); e.getMessage()这个可能为null进而导致空指针问题。





猜你喜欢

转载自blog.csdn.net/zhiwenwei/article/details/68067279