Android笔记(五)(代码规范)

阿里巴巴Android开发手册:https://yq.aliyun.com/attachment/download/?id=5261​​

Android 资源文件命名与使用

1. 【推荐】 color 资源使用#AARRGGBB 格式

2. 【推荐】大分辨率图片(单维度超过 1000)建议统一放在 xxhdpi 目录下管理,否则将导致占用内存成倍数增加。

Android 基本组件

1. 【强制】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);
         }
      }
   }
}

2. 【强制】避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果确
实有需求,应改用 IntentService 或采用其他异步机制完成。

public class MainActivity extends Activity {
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
   }
   public void startIntentService(View source) {
      Intent intent = new Intent(this, MyIntentService.class);
      startService(intent);
   }
}

public class MyIntentService extends IntentService {
   public MyIntentService() {
      super("MyIntentService");
   }
   @Override
   protected void onHandleIntent(Intent intent) {
      synchronized (this) {
         try {
            //执行耗时操作
            ......
         } catch (Exception e) {
         }
      }
   }
}

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

由于该方法是在主线程执行,如果执行耗时操作会导致 UI 不流畅。可以使用
IntentService 、 创 建 HandlerThread 或者调用 Context#registerReceiver
(BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他 Wroker 线程
执行 onReceive 方法。BroadcastReceiver#onReceive()方法耗时超过 10 秒钟,可
能会被系统杀死。

IntentFilter filter = new IntentFilter();
filter.addAction(LOGIN_SUCCESS);
this.registerReceiver(mBroadcastReceiver, filter);
mBroadcastReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
      Intent userHomeIntent = new Intent();
      userHomeIntent.setClass(this, UseHomeActivity.class);
      this.startActivity(userHomeIntent);
   }
};

4. 【强制】避免使用隐式 Intent 广播敏感信息,信息可能被其他注册了对应
BroadcastReceiver 的 App 接收。

如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent 拦截的风险。

Intent intent = new Intent("my-sensitive-event");
intent.putExtra("event", "this is a test event");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

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

private boolean mIsRelease = false;

    /**
     * 释放资源
     */
    private void release(){
        if (mIsRelease){
            return;
        }
        if (isFinishing()) {
            if (mHttpList != null) {
                mHttpList.release();
                mHttpList = null;
            }
        }
        mIsRelease = true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        //在onPause生命周期执行的瞬间,activity其实是还在前台的,所以有概率出现资源已经被释                放,但是activity里的View是还有被点击的机会导致空指针报错
    }

    @Override
    protected void onStop() {
        super.onStop();     
        release();

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //SingleTask的清理会跳过isFinishing()判断,需要在onDestroy()再次兜底,保证不会因为SingleTask的原因没有释放资源
        release();
    }

6. 【推荐】使用 Toast 时,建议定义一个全局的 Toast 对象,这样可以避免连续显示
Toast 时不能取消上一次 Toast 消息的情况(如果你有连续弹出 Toast 的情况,避免
使用 Toast.makeText)

UI 与布局

1. 【强制】布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套,
改用 RelativeLayout,可以有效降低嵌套数。

2. 【推荐】在 Activity 中显示对话框或弹出浮层时,尽量使用 DialogFragment,而非
Dialog/AlertDialog,这样便于随Activity生命周期管理对话框/弹出浮层的生命周期。

public void showPromptDialog(String text){
   DialogFragment promptDialog = new DialogFragment() {
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
         getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE);
         View view = inflater.inflate(R.layout.fragment_prompt, container);
         return view;
      }
   };
   promptDialog.show(getFragmentManager(), text);
}

3. 【推荐】文本大小使用单位 dp,view 大小使用单位 dp。对于 Textview,如果在文
字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问
题。

4. 【推荐】灵活使用布局,推荐 Merge、ViewStub 来优化布局,尽可能多的减少 UI
布局层级,推荐使用 FrameLayout,LinearLayout、RelativeLayout 次之。

repeat_inclde.xml文件中使用<merge>标签作为layout布局的根节点。<merge/>标签帮助你排除把一个布局插入到另一个布局时产生的多余的View Group。

<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/add"/>
 
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/delete"/>
 
</merge>

要添加可复用布局的xml文件中,添加<include/>标签。系统会忽略merge标签,直接把两个Button替换到include标签的位置。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center_horizontal">
 
    <include layout="@layout/repeat_inclde"/>
 
    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />
 
</LinearLayout>

5. 【推荐】不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog。

6. 【强制】不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这
样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图
面。

推荐使用 NestedScrollView,通过xrecyclerView.setNestedScrollingEnabled(false);禁用自身的滚动功能

 进程、线程与消息通信

1. 【强制】不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction
缓存为 1MB),可能导致 OOM。

可以使用EventBus等替代方案 

 2. 【强制】新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor
或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。

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());
//执行任务
executorService.execute(new Runnnable() {
 ...
});
  • corePoolSize:核心线程数

当线程池中的线程数目达到 corePoolSize后,新来的任务将会被添加到缓存队列中,也就是那个workQueue。

  • maximumPoolSize:线程池中的最大线程数

当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。

  • keepAliveTime:控制"idle Thread"的空闲存活时间

超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。

keepAliveTime 共7种取值
TimeUnit.DAYS;              //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue:阻塞队列

只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。

常见的阻塞队列:
//基于数组的先进先出队列,此队列创建时必须指定大小;
1)ArrayBlockingQueue  
//基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE; 
2)LinkedBlockingQueue 
//这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。 
3)synchronousQueue
  • threadFactory:线程工厂

用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY。

  • handler:拒绝执行策略

当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。   

通常有以下四种策略:
// 丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.AbortPolicy: 
// 也是丢弃任务,但是不抛出异常。      
ThreadPoolExecutor.DiscardPolicy:
// 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)     
ThreadPoolExecutor.DiscardOldestPolicy: 
// 由调用线程处理该任务 
ThreadPoolExecutor.CallerRunsPolicy:    

 3. 【强制】子线程中不能更新界面,更新界面必须在主线程中进行,网络操作不能在
主线程中调用。

Message msg = new Message();
msg.obj = "需要返回的数据";
handler.sendMessage(msg);
Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if (msg.what == 1) {
            //执行操作
        }
    }
};

4. 【推荐】 禁 止 在 多 进 程 之 间 用 SharedPreferences 共 享 数 据 , 虽 然 可 以
(MODE_MULTI_PROCESS),但官方已不推荐。

使用SystemProperties

使用SettingProvider

Android线程和进程间数据共享_薆的天空-CSDN博客

文件与数据库

1. 【强制】任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。

android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir()

 2. 【强制】当使用外部存储时,必须检查外部存储的可用性。

// 读/写检查
public boolean isExternalStorageWritable() {
   String state = Environment.getExternalStorageState();
   if (Environment.MEDIA_MOUNTED.equals(state)) {
      return true;
   }
   return false;
}
// 只读检查
public boolean isExternalStorageReadable() {
   String state = Environment.getExternalStorageState();
   if (Environment.MEDIA_MOUNTED.equals(state) ||
 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
      return true;
   }
   return false;
}

Bitmap、Drawable 与动画

1. 【强制】加载大图片或者一次性加载多张图片,应该在异步线程中进行。图片的加
载,涉及到 IO 操作,以及 CPU 密集操作,很可能引起卡顿。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
   ...
   // 在后台进行图片解码
   @Override
   protected Bitmap doInBackground(Integer... params) {
      final Bitmap bitmap = BitmapFactory.decodeFile("some path");
      return bitmap;
   }
   ...
}

2. 【强制】png 图片使用 tinypng 或者类似工具压缩处理,减少包体积。

3. 【推荐】谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个
gif 图片的大小。

4. 【参考】大图片资源不要直接打包到 apk,可以考虑通过文件仓库远程下载,减小包
体积。

安全

1. 【强制】使用 PendingIntent 时,禁止使用空 intent,同时禁止使用隐式 Intent

2. 【强制】将 android:allowbackup 属性设置为 false,防止 adb backup 导出数据。

3. 【强制】META-INF 目录中不能包含如.apk,.odex,.so 等敏感文件,该文件夹没有经
过签名,容易被恶意替换。

4. 【强制】Receiver/Provider 不能在毫无权限控制的情况下,将 android:export 设置
为 true。

5. 【参考】数据存储在 Sqlite 或者轻量级存储需要对数据进行加密,取出来的时候进
行解密。

6. 【强制】阻止 webview 通过 file:schema 方式访问本地敏感数据。

7. 【强制】对于内部使用的组件,显示设置组件的"android:exported"属性为 false。

8. 【强制】使用 Intent Scheme URL 需要做过滤。

// 将 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);

9. 【推荐】对于不需要使用 File 协议的应用,禁用 File 协议,显式设置 webView.
getSettings().setAllowFileAccess(false),对于需要使用 File 协议的应用,禁止 File
协议调用 JavaScript,显式设置 webView.getSettings().setJavaScriptEnabled(false)。

 阿里巴巴Java开发手册:

https://yq.aliyun.com/attachment/download/?id=5585

 编程规约 

1. 【强制】bean 类中布尔类型的变量,都不要加 is 前缀。

2. 【强制】包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用
单数形式,但是类名如果有复数含义,类名可以使用复数形式。

3. 【强制】if/for/while/switch/do 等保留字与括号之间都必须加空格。

4. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。

5. 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

  • 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进。
  • 运算符与下文一起换行。
  • 方法调用的点符号与下文一起换行。
  • 方法调用中的多个参数需要换行时,在逗号后进行。
  •  在括号前不要换行。

6. 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。

正例:下例中实参的 args1,后边必须要有一个空格。

method(args1, args2, args3); 

7. 【推荐】单个方法的总行数不超过 80 行。

说明:包括方法签名、结束右大括号、方法内代码、注释、空行、回车及任何不可见字符的总行数不超过 80 行。

8. 【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
说明:任何情形,没有必要插入多个空行进行隔开。

9. 【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用
equals。

正例:"test".equals(object);

说明:推荐使用 java.util.Objects.equals("test",object)

10. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

11. 关于基本数据类型与包装数据类型的使用标准如下:

  • 【强制】所有的 bean 类属性必须使用包装数据类型。
  • 【推荐】所有的局部变量使用基本数据类型。

12. 【推荐】使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无
内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。

String str="a,b,c,,"; 
String []arr=str.split(","); 
System.out.println("数据的长度"+arr.length+" 数组的内容"+Arrays.toString(arr)); 
//打印结果:数据的长度3 数组的内容[a, b, c]
String str="a,b,c,,"; 
String []arr=str.split(",",-1); 
System.out.println("数据的长度"+arr.length+" 数组的内容"+Arrays.toString(arr)); 
// 打印结果:数据的长度5 数组的内容[a, b, c, , ]

13. 【推荐】循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展。

反例:

String str = "start"; 
for (int i = 0; i < 100; i++) { 
    str = str + "hello"; 
}

14. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方
法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。

说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList
体现的是适配器模式,只是转换接口,后台的数据仍是数组。
 String[] str = new String[] { "you", "wu" };
 List list = Arrays.asList(str);

  • list.add("yangguanbao"); 运行时异常。
  • str[0] = "gujin"; 那么 list.get(0)也会随之修改。

15. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator
方式,如果并发操作,需要对 Iterator 对象加锁。
正例:

List<String> list = new ArrayList<>(); 
list.add("1"); 
list.add("2"); 
Iterator<String> iterator = list.iterator(); 
while (iterator.hasNext()) { 
    String item = iterator.next(); 
    if (删除元素的条件) { 
        iterator.remove(); 
    } 
}

反例:

for (String item : list) { 
    if ("1".equals(item)) { 
        list.remove(item); 
    } 
} 

说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的
结果吗?

16. 【推荐】集合初始化时,指定集合初始值大小。initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。

17. 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。如果是 JDK8,使用 Map.foreach 方法。
正例:

// 传统的Map迭代方式
for (Map.Entry<String, Object> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}
// JDK8的迭代方式
map.forEach((key, value) -> {
    System.out.println(key + ":" + value);
});

18. 【参考】利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
contains 方法进行遍历、对比、去重操作。

正例:

Set set = new HashSet(list);
List alist = new ArrayList();
for (Object o : set) {
    alist.add(o);
}
System.out.println("数据的长度为:"+alist.size());
List newList=new ArrayList();
newList.addAll(new HashSet(list));
System.out.println("数据的长度为:"+newList.size());

19. 【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为
static,必须加锁,或者使用 DateUtils 工具类。
正例:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { 
    @Override 
    protected DateFormat initialValue() { 
        return new SimpleDateFormat("yyyy-MM-dd"); 
    } 
}; 

说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat。

20. 【推荐】表达异常的分支时,少用 if-else 方式,【强制】如果非得使用 if()...else if()...else...方式表达逻辑,请勿超过 3 层。推荐使用卫语句、策略模式、状态模式等来实现。

正例:

if (condition) { 
    ... 
    return obj; 
} 
// 接着写 else 的业务逻辑代码; 

21. 【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将
复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。

正例:

final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
    ...
}

22. 【推荐】及时清理不再使用的代码段或配置信息。

说明:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///)来说明注释掉代码的理由。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值