Android开发规范总结

##前言
##一、Java 语言规范
下载地址:[https://yq.aliyun.com/articles/693](https://yq.aliyun.com/articles/693)

##二、Android 资源文件命名与使用

1. 资源文件需带前缀
2. layout文件的命名方式
    1. Activity 的 layout 以 module_activity 开头
    2. Fragment 的 layout 以 module_fragment 开头
    3. Dialog 的 layout 以 module_dialog 开头
    4. include 的 layout 以 module_include 开头
    5. ListView 的行 layout 以 module_list_item 开头
    6. RecyclerView 的 item layout 以 module_recycle_item 开头
    7. GridView 的 item layout 以 module_grid_item 开头
3. drawable资源文件名称以小写单词+下划线的方式命名,根据分辨率存放不同的drawable目录下,
    1. 命名规则:模块名_业务功能描述_控件描述_控件状态限定词
    `eg:module_login_btn_pressed,module_tabs_icon_home_normal`
4. anim资源名称以小写单词+下划线的方式命名
    1. 命名规则:模块名_逻辑名称_[方向|序号]
    2. Tween动画(使用简单图像变换的动画,例如缩放、平移)资源:尽可能以通用的动画名称命名。
    `eg:module_fade_in , module_fade_out , module_push_down_in`
    3. Frame 动画(按帧顺序播放图像的动画)资源:尽可能以模块+功能命名+序号。
    `eg:module_loading_grey_001`
5. color资源使用#AARRGGBB格式,写入 module_colors.xml 文件中
    1. 命名格式采用以下规则:模块名_逻辑名称_颜色
    `eg:<color name="module_btn_bg_color">#33b5e5e5</color>`
6. dimen 资源以小写单词+下划线方式命名,写入 module_dimens.xml 文件中
    1. 命名规则:模块名_描述信息
    `<dimen name="module_horizontal_line_height">1dp</dimen>`
7. style资源采用“父style名称.当前style名称”方式命名,写入module_styles.xml文件中。首字母大写
    `<style name="ParentTheme.ThisActivityTheme">
     …
    </style>`
8. string资源文件或者文本用到字符需要全部写入module_strings.xml文件中,字符串以小写单词+下划线的方式命名
    1. 命名规则:模块名_逻辑名称
    `moudule_login_tips,module_homepage_notice_desc`
9. Id 资源原则上以驼峰法命名,View 组件的资源 id 建议以 View 的缩写作为前缀。
10. 图片根据其分辨率,放在不同屏幕密度的 drawable 目录下管理,否则可能在低密度设备上导致内存占用增加,又可能在高密度设备上导致图片显示不够清晰。
    1. 为了支持对中屏幕尺寸和密度,Android提供了多种通用屏幕密度来适配,常用如下:
    `ldpi - 120dpi 
    mdpi - 160dpi 
    hdpi - 240dpi 
    xhdpi - 320dpi 
    xxhdpi - 480dpi 
    xxxhdpi - 640dpi `

##三、Android 基本组件
基本组件:Android四大基本组件指:Activity(Fragment),Service 、 BroadcastReceiver ,
ContentProvider 等等。

1. Activity间的数据通信,对于数据量较大的避免使用Intent+ Parcelable的方式,可以使用EventBus等代替方案,以免造成TransactionTooLargeException异常。
2.  Activity#onSaveInstanceState()方法不是Activity生命周期方法,不一定会被调用,它是用来在Activity被意外销毁时保存UI状态的,只能用于保存临时性数据,不能跟数据的持久化存储混为一谈。持久化存储应该在Activiy#onPause()/onStop()中执行。
3.  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) {
     startActivity(intent);
     }else {
     // 找不到指定的 Activity
     }
    }`
4.  避免在 Service#onStartCommand()/onBind()方法中执行耗时操作,如果确实有需求,应改用 IntentService 或采用其他异步机制完成。
    `public class MyIntentService extends IntentService {
     public MyIntentService() {
     super("MyIntentService");
     }
     @Override
     protected void onHandleIntent(Intent intent) {
     synchronized (this) {
     try { ......
     } catch (Exception e) {
     }
     }
     }
    }`
5.  避免在 BroadcastReceiver#onReceive()中执行耗时操作,如果有耗时工作,应该创建 IntentService 完成,而不应该在 BroadcastReceiver 内创建子线程去做。
    1.  原因:由于该方法是在主线程执行,如果执行耗时操作会导致 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, UserHomeService.class);
     this.startService(userHomeIntent);
     }
    }`
6.  避免使用隐式Intent广播敏感信息,信息可能被其他注册了对应BroadcastReceiver的App接收。
    1.  原因:通过 Context#sendBroadcast()发送的隐式广播会被所有感兴趣的 receiver 接收,恶意应用注册监听该广播的 receiver 可能会获取到 Intent 中传递的敏感信息,并进行其他危险操作。如果发送的广播为使用 Context#sendOrderedBroadcast()方法发送的有序广播,优先级较高的恶意 receiver 可能直接丢弃该广播,造成服务不可用,或者向广播结果塞入恶意数据。如果广播仅限于应用内,则可以使用 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent 拦截的风险
    `Intent intent = new Intent("my-sensitive-event");
    intent.putExtra("event", "this is a test event");
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent)`
7.  添 加 Fragment 时,确保 FragmentTransaction#commit() 在Activity#onPostResume()或者 ragmentActivity#onResumeFragments()内调用。不要随意使用 FragmentTransaction#commitAllowingStateLoss()来代替,任何commitAllowingStateLoss()的使用必须经过 code review,确保无负面影响。
8.  Activity#onDestroy()内执行释放资源的工作,例如一些工作线程的销毁和停止,因为 onDestroy()执行的时机可能较晚。可根据实际需要,在Activity#onPause()/onStop()中结合 isFinishing()的判断来执行。
9.  如非必须,避免使用嵌套的 Fragment。
10.  总是使用显式 Intent 启动或者绑定 Service,且不要为服务声明 Intent Filter,保证应用的安全性。如果确实需要使用隐式调用,则可为Service 提供 Intent Filter并从 Intent 中排除相应的组件名称,但须搭配使用 Intent#setPackage()方法设置Intent 的指定包名,这样可以充分消除目标服务的不确定性。
11.  Service 需要以多线程来并发处理多个启动请求,建议使用 IntentService,可避免各种复杂的设置。Service 组件一般运行主线程,应当避免耗时操作,如果有耗时操作应该在 Worker线程执行。可以使用 IntentService 执行后台任务。
12.  对于只用于应用内的广播,优先使用 LocalBroadcastManager 来进行注册和发送,LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。
13.  当前 Activity 的 onPause 方法执行结束后才会创建onCreate)或恢复(onRestart)别的 Activity,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率。
14.  Activity或者Fragment中动态注册BroadCastReceiver时,registerReceiver()和 unregisterReceiver()要成对出现。
15.  id 基础组件如果使用隐式调用,应在 AndroidManifest.xml 中使用<intent-filter> 或在代码中使用 IntentFilter 增加过滤。
    `/ 将 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)`
##四、UI 与布局
1. 布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout嵌套,改用 RelativeLayout,可以有效降低嵌套数。
    1. 原因:Android 应用页面上任何一个 View 都需要经过measure、layout、draw 三个步骤才能被正确的渲染。从 xml layout 的顶部节点开始进行 measure,每个子节点都需要向自己的父节点提供自己的尺寸来决定展示的位置,在此过程中可能还会重新measure(由此可能导致measure的时间消耗为原来的2-3倍)。节点所处位置越深,嵌套带来的 measure 越多,计算就会越费时。这就是为什么扁平的 View 结构会性能更好。
    2. 同时,页面拥上的 View 越多,measure、layout、draw 所花费的时间就越久。要缩短这个时间,关键是保持 View 的树形结构尽量扁平,而且要移除所有不需要渲染的View。理想情况下,总共的 measure,layout,draw 时间应该被很好的控制在 16ms以内,以保证滑动屏幕时 UI 的流畅。
    3. 要找到那些多余的 View(增加渲染延迟的 view),可以用 Android Studio Monitor里的 Hierarchy Viewer 工具,可视化的查看所有的 view。
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. 源文件统一采用 UTF-8 的形式进行编码。
4. 禁止在非 UI 线程进行 View 相关操作。
5. 文本大小使用单位 dp,View 大小使用单位 dp。对于 TextView,如果在文字大小确定的情况下推荐使用 wrap_content 布局避免出现文字显示不全的适配问题。
    1. 原因:之所以文本大小也推荐使用 dp 而非 sp,因为 sp 是Android 早期推荐使用的,但其实 sp 不仅和 dp 一样受屏幕密度的影响,还受到系统设置里字体大小的影响,所以使用 dp 对于应用开发会更加保证 UI 的一致性和还原度。
6. 禁止在设计布局时多次为子 View 和父 View 设置同样背景进而造成页面过度绘制,推荐将不需要显示的布局进行及时隐藏。
7. 灵活使用布局,推荐 merge、ViewStub 来优化布局,尽可能多的减少 UI布局层级,推荐使用 FrameLayout,LinearLayout、RelativeLayout 次之。
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. 不能在 Activity 没有完全显示时显示 PopupWindow 和 Dialog。
10. 尽量不要使用 AnimationDrawable,它在初始化的时候就将所有图片加载到内存中,特别占内存,并且还不能释放,释放之后下次进入再次加载时会报错。
11. 不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图面。
12. 不要在 Android 的 Application 对象中缓存数据。基础组件之间的数据共享请使用 Intent 等机制,也可使用 SharedPreferences 等数据持久化机制。
13. 使用 Toast 时,建议定义一个全局的 Toast 对象,这样可以避免连续显示Toast 时不能取消上一次 Toast 消息的情况。即使需要连续弹出 Toast,也应避免直接调用 Toast#makeText。
14. 使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需要为其显式设置属性(Textview 的文本为空也需要设置 setText(""),背景透明也需要设置),否则在滑动的过程中,因为 adapter item 复用的原因,会出现内容的显示错乱。
##五、进程、线程与消息通信
1. 不要通过 Intent 在 Android 基础组件之间传递大数据(binder transaction缓存为 1MB),可能导致 OOM。
2. 在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程初始化。特别是后台进程减少不必要的业务初始化。
    `public class MyApplication extends Application {
     @Override
     public void onCreate() {
     //在所有进程中初始化
     ....
     //仅在主进程中初始化
     if (mainProcess) {
     ...
     }
     //仅在后台进程中初始化
     if (bgProcess) {
     ...
     }
     }
    }`
3. 新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor或者其他形式自定义的线程池),不允许在应用中自行显式创建线程。
    1. 原因:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。另外创建匿名线程不便于后续的资源使用分析,对性能分析等会造成困扰。
4. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    1. 原因:
        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());`
5. 子线程中不能更新界面,更新界面必须在主线程中进行,网络操作不能在主线程中调用。
6. 尽量减少不同 APP 之间的进程间通信及拉起行为。拉起导致占用系统资源,影响用户体验。
7. 新建线程时,定义能识别自己业务的线程名称,便于性能优化和问题排查。
    `public class MyThread extends Thread {
     public MyThread(){
     super.setName("ThreadName");
     …
     }
    }`
8. ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时线程能被释放。
9. 禁止在多进程之间用 SharedPreferences 共享数据,虽然可以(MODE_MULTI_PROCESS),但官方已不推荐。
10. 谨慎使用 Android 的多进程,多进程虽然能够降低主进程的内存压力,但会遇到如下问题:
    1. 首次进入新启动进程的页面时会有延时的现象(有可能黑屏、白屏几秒,是白屏还是黑屏和新 Activity 的主题有关);
    2. 应用内多进程时,Application 实例化多次,需要考虑各个模块是否都需要在所有进程中初始化。
##六、文件与数据库
1. 任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。
    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_READ_ONLY.equals(state)) {
     return true;
     }
     return false;
    }`
3. 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用FileProvider。
4. SharedPreference 中只能存储简单数据类型(int、boolean、String 等),复杂数据类型建议使用文件、数据库等其他方式存储。
5. SharedPreference 提交数据时,尽量使用 Editor#apply() ,而非Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用 Editor#commit()。
    1. 原因:SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入磁盘,commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,apply 会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此做相应的其他操作,应当使用 commit。
6. 数据库 Cursor 必须确保使用完后关闭,以免内存泄漏。
    1. 原因:Cursor 是对数据库查询结果集管理的一个类,当查询的结果集较小时,消耗内存不易察觉。但是当结果集较大,长时间重复操作会导致内存消耗过大,需要开发者在操作完成后手动关闭 Cursor。数据库 Cursor 在创建及使用时,可能发生各种异常,无论程序是否正常结束,必须在最后确保 Cursor 正确关闭,以避免内存泄漏。同时,如果 Cursor 的使用还牵涉多线程场景,那么需要自行保证操作同步。
7. 多线程操作写入数据库时,需要使用事务,以免出现同步问题。
    1. 原因:通过 SQLiteOpenHelper 获取数据库 SQLiteDatabase 实例,Helper 中会自动缓存已经打开的 SQLiteDatabase 实例,单个 App 中应使用 SQLiteOpenHelper 的单例模式确保数据库连接唯一。由于 SQLite 自身是数据库级锁,单个数据库操作是保证线程安全的(不能同时写入),transaction 是一次原子操作,因此处于事务中的操作是线程安全的。若同时打开多个数据库连接,并通过多线程写入数据库,会导致数据库异常,提示数据库已被锁住。
8. 大数据写入数据库时,请使用事务或其他能够提高 I/O 效率的机制,保证执行速度。
    `public void insertBulk(SQLiteDatabase db, ArrayList<UserInfo> users) {
     db.beginTransaction();
     try {
     for (int i = 0; i < users.size; i++) {
     ContentValues cv = new ContentValues();
     cv.put("userId", users[i].userId);
     cv.put("content", users[i].content);
     db.insert(TUserPhoto, null, cv);
     }
     // 其他操作
     db.setTransactionSuccessful();
     } catch (Exception e) {
     // TODO
     } finally {
     db.endTransaction();
     }
    }`
9. 执行 SQL 语句时,应使用 SQLiteDatabase#insert()、update()、delete(),不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入风险。
10. 如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受信任的外部数据直接拼接在原始 SQL 语句中。
    `// 使用一个可替换参数
    String mSelectionClause = "var = ?";
    String[] selectionArgs = {""};
    selectionArgs[0] = mUserInput;`
##七、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. 在 ListView,ViewPager,RecyclerView,GirdView 等组件中使用图片时,应做好图片的缓存,避免始终持有图片导致内存溢出,也避免重复创建图片,引起性能问题。建议 使 用 Fresco ( https://github.com/facebook/fresco )、 Glide(https://github.com/bumptech/glide)等图片库。
    1. 例如使用系统 LruCache 缓存
    2. 参考:[https://developer.android.com/topic/performance/graphics/cache-bitmap.html](https://developer.android.com/topic/performance/graphics/cache-bitmap.html)
3. png 图片使用 TinyPNG 或者类似工具压缩处理,减少包体积。
4. 应根据实际展示需要,压缩图片,而不是直接显示原图。手机屏幕比较小,直接显示原图,并不会增加视觉上的收益,但是却会耗费大量宝贵的内存。
    `public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {
     // 首先通过 inJustDecodeBounds=true 获得图片的尺寸
     final BitmapFactory.Options options = new BitmapFactory.Options();
     options.inJustDecodeBounds = true;
     BitmapFactory.decodeResource(res, resId, options);
     // 然后根据图片分辨率以及我们实际需要展示的大小,计算压缩率
     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
     // 设置压缩率,并解码
     options.inJustDecodeBounds = false;
     return BitmapFactory.decodeResource(res, resId, options);
    }`
5. 使用完毕的图片,应该及时回收,释放宝贵的内存。
    `Bitmap bitmap = null;
    loadBitmapAsync(new OnResult(result){
     bitmap = result;
    });
    ...使用该 bitmap...
    // 使用结束,在 2.3.3 及以下需要调用 recycle()函数,在 2.3.3 以上 GC 会自动管理,除非你明
    确不需要再用。if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
     bitmap.recycle();
    }
    bitmap = null;`
6. 在 Activity#onPause()或 Activity#onStop()回调中,关闭当前 activity 正在执行的的动画。
7. 在动画或者其他异步任务结束时,应该考虑回调时刻的环境是否还支持业务处理。例如 Activity 的 onStop()函数已经执行,且在该函数中主动释放了资源,此时回调中如果不做判断就会空指针崩溃。
8. 使用 inBitmap 重复利用内存空间,避免重复开辟新内存。
9. 使用 RGB_565 代替 RGB_888,在不怎么降低视觉效果的前提下,减少内存占用。
    1. ALPHA_8 代表 8 位 Alpha 位图;
    2. ARGB_4444 代表 16 位 ARGB 位图;
    3. ARGB_8888 代表 32 位 ARGB 位图;
    4. RGB_565 代表 8 位 RGB 位图。
    5. 位图位数越高,存储的颜色信息越多,图像也就越逼真。大多数场景使用的是ARGB_8888 和 RGB_565,RGB_565 能够在保证图片质量的情况下大大减少内存的开销,是解决 OOM 的一种方法。
    6. 但是一定要注意 RGB_565 是没有透明度的,如果图片本身需要保留透明度,那么就不能使用 RGB_565。
10. 尽量减少 Bitmap(BitmapDrawable)的使用,尽量使用纯色(ColorDrawable)、渐变色(GradientDrawable)、StateSelector(StateListDrawable)等与 Shape 结合的形式构建绘图。
11. 谨慎使用 gif 图片,注意限制每个页面允许同时播放的 gif 图片,以及单个gif 图片的大小。
12. 根据设备性能,选择性开启复杂动画,以实现一个整体较优的性能和体验;
13. 在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页面 , onAnimationEnd 可能会因各种异常没被回调 (参考:[https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle](https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle) ),建议加上超时保护或通过 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();
     }
     }
    }, anim.getDuration());
    v.startAnimation(anim)`
14. 当 View Animation 执行结束时,调用 View.clearAnimation()释放相关资源。
    `View v = findViewById(R.id.xxxViewID);
    final FadeUpAnimation anim = new FadeUpAnimation(v);
    anim.setInterpolator(new AccelerateInterpolator());
    anim.setDuration(1000);
    anim.setFillAfter(true);
    anim.setAnimationListener(new AnimationListener() {
     @Override
     public void onAnimationEnd(Animation arg0) {
     //判断一下资源是否被释放了
     if (v != null) {
     v.clearAnimation();
     }
     }
    });
    v.startAnimation(anim)`
##八、安全
1. 将 android:allowbackup 属性必须设置为 false,阻止应用数据被导出。
2. 如果使用自定义 HostnameVerifier 实现类,必须在 verify()方法中校验服务器主机名的合法性,否则可能受到中间人攻击。
3. 如果使用自定义 X509TrustManager 实现类,必须在 checkServerTrusted() 方法中校验服务端证书的合法性,否则可能受到中间人攻击。
4. 在 SDK 支持的情况下,Android 应用必须使用 V2 签名,这将对 APK 文件的修改做更多的保护。
5. 所有的 Android 基本组件(Activity、Service、BroadcastReceiver、ContentProvider 等)都不应在没有严格权限控制的情况下,将 android:exported 设置为 true。
6. WebView 应设置 WebView#getSettings()#setAllowFileAccess(false)、WebView#getSettings()#setAllowFileAccessFromFileURLs(false) 、WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false),阻止 file scheme URL 的访问。
7. 不要把敏感信息打印到 log 中。
8. 确保应用发布版本的 android:debuggable 属性设置为 false。
9. 本地加密秘钥不能硬编码在代码中,更不能使用 SharedPreferences 等本地持久化机制存储。应选择 Android 自身的秘钥库(KeyStore)机制或者其他安全性更高的安全解决方案保存。
10. 使用 Android 的 AES/DES/DESede 加密算法时,不要使用 ECB 加密模式,应使用 CBC 或 CFB 加密模式。
11. Android APP 在 HTTPS 通信中,验证策略需要改成严格模式。
12. zip 中不要包含 ../../file 这样的路径,可能被篡改目录结构,造成攻击。
13. 在 Android 4.2(API Level 17)及以上,对安全性要求较高的应用可在 Activity中,对 Activity 所关联的 Window 应用WindowManager.LayoutParams.FLAG_SECURE,防止被截屏、录屏。但要注意的是,一个 Activity 关联的 Window 可能不止一个,如果使用了 Dialog / DialogFragment 等控件弹出对话框,它们本身也会创建一个新的 Window,也一样需要保护。
14. MD5 和 SHA-1、SHA-256 等常用算法是 Hash 算法,有一定的安全性,但不能代替加密算法。敏感信息的存储和传输,需要使用专业的加密机制。
##九、其他
1. 不能使用 System.out.println 打印 log。
2. Log 的 tag 不能是" "。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值