Android & Java规范

Android

【强制】
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
}
}
反例:
Intent intent = new Intent();
intent.setAction("com.example.DemoIntent ");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}

【强制】避免在 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, UserHomeService.class);
this.startService(userHomeIntent);
}
};
反例:
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
MyDatabaseHelper myDB = new MyDatabaseHelper(context);
myDB.initData();
// have more database operation here
}
};

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

【强制】布局中不得不使用 ViewGroup 多重嵌套时,不要使用 LinearLayout 嵌套,
改用 RelativeLayout,可以有效降低嵌套数。
【推荐】灵活使用布局,推荐 merge、ViewStub 来优化布局,尽可能多的减少 UI
布局层级,推荐使用 FrameLayout,LinearLayout、RelativeLayout 次之。
(merge标签必须使用在根布局,并且ViewStub标签中的layout布局不能使用merge标签.)

【推荐】在 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);
}

【强制】不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因为这
样会把 ListView 的所有 Item 都加载到内存中,要消耗巨大的内存和 cpu 去绘制图
面。
说明:
ScrollView 中嵌套 List 或 RecyclerView 的做法官方明确禁止。除了开发过程中遇到
的各种视觉和交互问题,这种做法对性能也有较大损耗。ListView 等 UI 组件自身有
垂直滚动功能,也没有必要在嵌套一层 ScrollView。目前为了较好的 UI 体验,更贴
近 Material Design 的设计,推荐使用 NestedScrollView。
正例:

<?xml version="1.0" encoding="utf-8"?>

【强制】使用 Adapter 的时候,如果你使用了 ViewHolder 做缓存,在 getView()的
方法中无论这项 convertView 的每个子控件是否需要设置属性(比如某个 TextView
设置的文本可能为 null,某个按钮的背景色为透明,某控件的颜色为透明等),都需
要为其显式设置属性(Textview 的文本为空也需要设置 setText(""),背景透明也需要
设置),否则在滑动的过程中,因为 adapter item 复用的原因,会出现内容的显示错
乱。

【强制】在 Application 的业务初始化代码加入进程判断,确保只在自己需要的进程
初始化。特别是后台进程减少不必要的业务初始化。
正例:
public class MyApplication extends Application {
@Override
public void onCreate() {
//在所有进程中初始化

//仅在主进程中初始化
if (mainProcess) {

}
//仅在后台进程中初始化
if (bgProcess) {

}
}
}
应用内多进程时,Application 实例化多次,需要考虑各个模块是否都需要在所
有进程中初始化。

【强制】新建线程时,必须通过线程池提供(AsyncTask 或者 ThreadPoolExecutor
或者其他形式自定义的线程池)
,不允许在应用中自行显式创建线程。
说明:
使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解
决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致
消耗完内存或者“过度切换”的问题。另外创建匿名线程不便于后续的资源使用分析,
对性能分析等会造成困扰。
正例:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue taskQueue = new LinkedBlockingQueue();
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() {

});
反例:
new Thread(new Runnable() {
@Override
public void run() {
//操作语句

}
}).start();

【强制】线程池不允许使用 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 taskQueue = new LinkedBlockingQueue();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());
反例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

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

【强制】任何时候不要硬编码文件路径,请使用 Android 文件系统 API 访问。
说明:
Android 应用提供内部和外部存储,分别用于存放应用自身数据以及应用产生的用
户数据。可以通过相关 API 接口获取对应的目录,进行文件操作。
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
正例:
public File getDir(String alName) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.
DIRECTORY_PICTURES), alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, “Directory not created”);
}
return file;
}
反例:
public File getDir(String alName) {
// 任何时候都不要硬编码文件路径,这不仅存在安全隐患,也让 app 更容易出现适配问题
File file = new File("/mnt/sdcard/Download/Album", alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, “Directory not created”);
}
return file;

【强制】当使用外部存储时,必须检查外部存储的可用性。
正例:
// 读/写检查
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;
}

【强制】应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用
FileProvider。
正例:

... ... ... void getAlbumImage(String imagePath) { File image = new File(imagePath); Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri imageUri = FileProvider.getUriForFile( this, "com.example.provider", image); getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE); } 反例: void getAlbumImage(String imagePath) { File image = new File(imagePath); Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //不要使用 file://的 URI 分享文件给别的应用,包括但不限于 Intent getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image)); startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE); }

【 推 荐 】 SharedPreference 提 交 数 据 时 , 尽 量 使 用 Editor#apply() , 而 非
Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使
用 Editor#commit()。
说明:
SharedPreference 相关修改使用 apply 方法进行提交会先写入内存,然后异步写入
磁盘, commit 方法是直接写入磁盘。如果频繁操作的话 apply 的性能会优于 commit,
apply 会将最后修改内容写入磁盘。但是如果希望立刻获取存储操作的结果,并据此
做相应的其他操作,应当使用 commit。

【强制】多线程操作写入数据库时,需要使用事务,以免出现同步问题。
说明:
通过 SQLiteOpenHelper 获取数据库 SQLiteDatabase 实例,Helper 中会自动缓存
已经打开的 SQLiteDatabase 实例,单个 App 中应使用 SQLiteOpenHelper 的单例
模式确保数据库连接唯一。由于 SQLite 自身是数据库级锁,单个数据库操作是保证
线程安全的(不能同时写入),transaction 是一次原子操作,因此处于事务中的操作
是线程安全的。
若同时打开多个数据库连接,并通过多线程写入数据库,会导致数据库异常,提示
数据库已被锁住。
正例:
public void insertUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put(“userId”, userId);
cv.put(“content”, content);
db.beginTransaction();
try {
db.insert(TUserPhoto, null, cv);
// 其他操作
db.setTransactionSuccessful();
} catch (Exception e) {
// TODO
} finally {
db.endTransaction();
}
}
反例:
public void insertUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put(“userId”, userId);
cv.put(“content”, content);
db.insert(TUserPhoto, null, cv);
}

【推荐】大数据写入数据库时,请使用事务或其他能够提高 I/O 效率的机制,保证执
行速度。
正例:
public void insertBulk(SQLiteDatabase db, ArrayList 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();
}
}

【强制】执行 SQL 语句时,应使用 SQLiteDatabase#insert()、update()、delete(),
不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入风险。
正例:
public int updateUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put(“content”, content);
String[] args = {String.valueOf(userId)};
return db.update(TUserPhoto, cv, “userId=?”, args);
}
反例:
public void updateUserPhoto(SQLiteDatabase db, String userId, String content) {
String sqlStmt = String.format(“UPDATE %s SET content=%s WHERE userId=%s”,
TUserPhoto, userId, content);
//请提高安全意识,不要直接执行字符串作为 SQL 语句
db.execSQL(sqlStmt);
}

【强制】如果 ContentProvider 管理的数据存储在 SQL 数据库中,应该避免将不受
信任的外部数据直接拼接在原始 SQL 语句中。
正例:
// 使用一个可替换参数
String mSelectionClause = “var = ?”;
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;

【强制】在 ListView,ViewPager,RecyclerView,GirdView 等组件中使用图片时,
应做好图片的缓存,避免始终持有图片导致内存溢出,也避免重复创建图片,引起
性 能 问 题 。 建 议 使 用 Fresco ( https://github.com/facebook/fresco )、 Glide
(https://github.com/bumptech/glide)等图片库。
正例:

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

【推荐】应根据实际展示需要,压缩图片,而不是直接显示原图。手机屏幕比较小,
直接显示原图,并不会增加视觉上的收益,但是却会耗费大量宝贵的内存。
正例:
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);
}
反例:
不经压缩显示原图。

【强制】在 Activity#onPause()或 Activity#onStop()回调中,关闭当前 activity 正在执
行的的动画。
正例:
@Override
public void onPause() {
//页面退出,及时清理动画资源
mImageView.clearAnimation()
}
}

在Android3.0之前,Bitmap的内存分配分为两部分,一部分是分配在Dalvik的VM堆中。而像素数据的内存是分配在Native堆中,而到了Android3.0之后。Bitmap的内存则已经所有分配在VM堆上。这两种分配方式的差别在于,Native堆的内存不受Dalvik虚拟机的管理。我们想要释放Bitmap的内存,必须手动调用Recycle方法。而到了Android 3.0之后的平台,我们就能够将Bitmap的内存全然放心的交给虚拟机管理了,我们仅仅须要保证Bitmap对象遵守虚拟机的GC Root Tracing的回收规则就可以。

【推荐】使用 inBitmap 重复利用内存空间,避免重复开辟新内存。
正例:
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int
reqHeight, ImageCache cache) {
final BitmapFactory.Options options = new BitmapFactory.Options();

BitmapFactory.decodeFile(filename, options);

// 如果在 Honeycomb 或更新版本系统中运行,尝试使用 inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}

return BitmapFactory.decodeFile(filename, options);
}
private static void addInBitmapOptions(BitmapFactory.Options options,
ImageCache cache) {
// inBitmap 只处理可变的位图,所以强制返回可变的位图
options.inMutable = true;
if (cache != null) {
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}
}

【推荐】使用 RGB_565 代替 RGB_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_8565 :
    Config.RGB_565;
    Bitmap bitmap = Bitmap.createBitmap(w, h, config);
    反例:
    Bitmap newb = Bitmap.createBitmap(width, height, Config.ARGB_8888);

【推荐】在有强依赖 onAnimationEnd 回调的交互时,如动画播放完毕才能操作页
面 , onAnimationEnd 可 能 会 因 各 种 异 常 没 被 回 调 ( 参 考 :
https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle
d-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();
}
}
}, anim.getDuration());
v.startAnimation(anim);

【强制】将 android:allowbackup 属性必须设置为 false,阻止应用数据被导出。
说明:
android:allowBackup 原本是 Android 提供的 adb 调试功能,如果设置为 true,
可以导出应用数据备份并在任意设备上恢复。这对应用安全性和用户数据隐私构成
极大威胁,所以必须设置为 false,防止数据泄露。
正例:

【强制】确保应用发布版本的 android:debuggable 属性设置为 false。

【 强 制】所有的 Android 基本组件( Activity、Service 、 BroadcastReceiver 、
ContentProvider 等)都不应在没有严格权限控制的情况下,将 android:exported 设
置为 true。

【强制】在 SDK 支持的情况下,Android 应用必须使用 V2 签名,这将对 APK 文
件的修改做更多的保护。

【强制】本地加密秘钥不能硬编码在代码中,更不能使用 SharedPreferences 等本
地持久化机制存储。应选择 Android 自身的秘钥库(KeyStore)机制或者其他安全
性更高的安全解决方案保存。
说明:
应用程序在加解密时,使用硬编码在程序中的密钥,攻击者通过反编译拿到密钥可
以轻易解密 APP 通信数据。

【推荐】在 Android 4.2 (API Level 17)及以上,对安全性要求较高的应用可在 Activity
中,对 Activity 所关联的 Window 应用 WindowManager.LayoutParams.FLAG_
SECURE,防止被截屏、录屏。但要注意的是,一个 Activity 关联的 Window 可
能不止一个,如果使用了 Dialog / DialogFragment 等控件弹出对话框,它们本身
也会创建一个新的 Window,也一样需要保护。

JAVA:
【强制】 IDE 的 text file encoding 设置为 UTF -8 ; IDE 中文件的换行符使用 Unix 格式,
不要使用 Windows 格式。

  1. 【推荐】防止 NPE ,是程序员的基本修养,注意 NPE 产生的场景:
    1)返回类型为基本数据类型, return 包装数据类型的对象时,自动拆箱有可能产生 NPE 。
    反例: public int f() { return Integer 对象}, 如果为 null ,自动解箱抛 NPE 。
    2 ) 数据库的查询结果可能为 null 。
    3 ) 集合里的元素即使 isNotEmpty ,取出的数据元素也可能为 null 。
    4 ) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE 。
    5 ) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
    6 ) 级联调用 obj . getA() . getB() . getC(); 一连串调用,易产生 NPE 。
    正例:使用 JDK8 的 Optional 类来防止 NPE 问题。

【强制】日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值