Android编程规范摘要2 (基本组件)

Android基本组件

Android 基本组件指Activity 、Fragment 、Service 、BroadcastReceiver 、ContentProvider 等等。


其它相关文章:
Android编程规范摘要1 (资源文件命名与使用)
Android编程规范摘要2 (基本组件)
Android编程规范摘要3 (UI与布局)
Android编程规范摘要4 (进程、线程与消息通信)
Android编程规范摘要5 (文件与数据库)
Android编程规范摘要6 (Bitmap、Drawable 与动画)
Android编程规范摘要7 (安全)


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

  2. Activity#onSaveInstanceState()方法不是Activity 生命周期方法,也不保证一定会被调用。

    • 它是用来在Activity 被意外销毁时保存UI 状态的,只能用于保存临时性数据,例如UI 控件的属性等,不能跟数据的持久化存储混为一谈。持久化存储应该在Activity#onPause()/onStop()中实行。
  3. [强制] Activity间通过隐式Intent的跳转,在发出Intent之前必须通过resolveActivity 检查,避免找不到合适的调用组件,造成ActivityNotFoundException 的异常:

    if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null)
  4. [强制] 避免在Service#onStartCommand()/onBind()方法中执行耗时操作,如果确实有需求,应改用IntentService 或采用其他异步机制完成。

  5. [强制] 避免在BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建IntentService 完成,而不应该在BroadcastReceiver 内创建子线程去做。

    • 由于该方法是在主线程执行,如果执行耗时操作会导致UI 不流畅。
    • 正确方式: IntentService 、创建HandlerThread 或者调用
      Context#registerReceiver(BroadcastReceiver, IntentFilter,String, Handler)方法,在其他Wroker 线程执行onReceive 方法。
  6. [强制] 避免使用隐式Intent 广播敏感信息,信息可能被其他注册了对应 BroadcastReceiver 的App 接收。

    • Context#sendBroadcast() : 会被所有感兴趣的receiver 接收。
    • Context#sendOrderedBroadcast() : 优先级高的恶意 receiver 甚至可能直接丢弃广播,造成服务不可用。
    • LocalBroadcastManager#sendBroadcast() : 如果广播仅限于应用内部,使用这种方式发送广播,可以避免泄漏/拦截风险。
  7. 添加 Fragment 时,确保FragmentTransaction#commit()Activity#onPostResume() 或者FragmentActivity#onResumeFragments()内调用。

    • 不要随意使用FragmentTransaction#commitAllowingStateLoss() 来代替,任何commitAllowingStateLoss()的使用必须经过code review,确保无负面影响。
    • 风险: Activity 可能因为各种原因被销毁, Android 支持页面被销毁前通过Activity#onSaveInstanceState() 保存自己的状态。但如果FragmentTransaction.commit()发生在Activity 状态保存之后,就会导致Activity 重建、恢复状态时无法还原页面状态,从而可能出错,系统会抛出IllegalStateExceptionStateLoss 异常。

    • 推荐做法: 在Activity 的onPostResume()onResumeFragments() (FragmentActivity)里面执行FragmentTransaction.commit(),如有必要也可在onCreate()里执行。

    • 警告: 不要随意改用FragmentTransaction.commitAllowingStateLoss() 或者直接使用try-catch 避免
      crash,这不是问题的根本解决之道。当且仅当你确认Activity 重建、恢复状态时,本次commit 丢失不会造成影响时才可这么做。

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

  9. 如非必须,避免使用嵌套的Fragment。

    嵌套Fragment 是在Android API 17添加到SDK以及Support 库中的功能,Fragment嵌套使用会有一些坑,容易出现bug,比较常见的问题有如下几种:

    1. onActivityResult()方法的处理错乱,内嵌的Fragment 可能收不到该方法的回调,需要由宿主Fragment 进行转发处理;
    2. 突变动画效果;
    3. 被继承的setRetainInstance(),导致在Fragment 重建时多次触发不必要的逻辑。
  10. 总是使用显式Intent 启动或者绑定Service,且不要为服务声明Intent Filter,保证应用的安全性。

    • 例外推荐: 如果确实需要使用隐式调用,则可为Service 提供Intent Filter
      并从Intent 中排除相应的组件名称,但必须搭配使用Intent#setPackage()方法设置Intent 的指定包名,这样可以充分消除目标服务的不确定性。
  11. Service 需要以多线程来并发处理多个启动请求,建议使用IntentService,可避免各种复杂的设置。

    • Service 组件一般运行主线程,应当避免耗时操作,如果有耗时操作应该在Worker线程执行。可以使用IntentService 执行后台任务。
    • 正例:

      public class SingleIntentService extends IntentService {
          public SingleIntentService() {
              super("single-service thread");
          }
          @Override
          protected void onHandleIntent(Intent intent) {
              try {
                  ......
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    • 反例:

      public class HelloService extends Service {
          ...
          @Override
          public int onStartCommand(Intent intent, int flags, int startId) {
              Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                  //操作语句
                  }
              }).start();
              ...
          }
      }
  12. 对于只用于应用内的广播,优先使用LocalBroadcastManager 来进行注册
    和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。

    • 注册: LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
    • 注销: LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
    • 发送: LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
    • 全局广播风险,请参考第六条。
  13. 当前Activity 的onPause 方法执行结束后才会创建(onCreate)或恢复
    (onRestart)别的Activity,所以在onPause 方法中不适合做耗时较长的工作,这
    会影响到页面之间的跳转效率。

  14. [强制] Activity 或者Fragment 中动态注册BroadCastReceiver 时,registerReceiver()unregisterReceiver()要成对出现。

    • 风险: 已经注册的receiver没有在合适的时机注销,可能会导致内存泄漏,占用内存空间,加重SystemService负担。
    • 崩溃: 部分华为的机型会对receiver 进行资源管控,单个应用注册过多receiver 会触发管控模块抛出异常,应用直接崩溃。
    • 位置: Activity 的生命周期不对应,可能出现多次onResume 造成receiver 注册多个,但最终只注销一个,其余receiver 产生内存泄漏。
    • 正例: 在onResume中注册, 在onPause中注销。
  15. [强制] Android 基础组件如果使用隐式调用,应在 AndroidManifest.xml 中使用<intent-filter> 或在代码中使用 IntentFilter 增加过滤。

    • 风险: 如果浏览器支持Intent Scheme Uri语法,如果过滤不当,那么恶意用户可能通过浏览器js 代码进行一些恶意行为,比如盗取cookie 等。如果使用了Intent.parseUri函数,获取的intent 必须严格过滤。
    • 正例:

      // 将intent scheme URL 转换为intent 对象
      Intent intent = Intent.parseUri(uri);
      // 禁止没有BROWSABLE category 的情况下启动activity
      intent.addCategory("android.intent.category.BROWSABLE");
      intent.setComponent(null);
      intent.setSelector(null);
      // 使用intent 启动activity
      context.startActivityIfNeeded(intent, -1)
    • 反例:

      Intent intent = Intent.parseUri(uri.toString().trim().substring(15), 0);
      intent.addCategory("android.intent.category.BROWSABLE");
      context.startActivity(intent);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值