阿里巴巴不仅仅是只关注你的钱包,也在给程序的开发世界贡献着自己的力量,为你们的代码质量操碎了心,推出了众多技术文档,抱着让自己的代码更规范的心态,下载被阅读了阿里巴巴Android开发手册v1.0.1,由于不便阅读和快速定位,决定摘抄重排版到博客上,在原文档的基础上使当地增加一些所谓的解读和理解,你也可以直接去找原版的来读,直接略过这笔记。
应用的资源文件需带模块名作为前缀,即**moudle_**xxxxxx,这是基于组件化、插件化开发考虑的,如果资源命名不规范,到时候在使用组件化和插件化的时候可能需要花费额外不必要的成本去处理资源文件冲突和合并的情况
Activity 的 layout 以 module_activity 开头
Fragment 的 layout 以 module_fragment 开头
Dialog 的 layout 以 module_dialog 开头
include 的 layout 以 module_include 开头
ListView 的行 layout 以 module_list_item 开头
RecyclerView 的 item layout 以 module_recycle_item 开头
GridView 的 item layout 以 module_grid_item 开头
根据分辨率不同存放在不同的 drawable 目录下,如果介意包大小建议只使用一套,系统去进行缩放。如:module_login_btn_pressed,module_tabs_icon_home_normal
Tween 动画(使用简单图像变换的动画,例如缩放、平移)资源——尽可能以通用的动画名称命名,如 module_fade_in , module_fade_out , module_push_down_in (动画+方向)。
Frame 动画(按帧顺序播放图像的动画)资源——尽可能以模块+功能命名+序号。如module_loading_grey_001。
#33b5e5e5
1dp
如:moudule_login_tips,module_homepage_notice_desc
控件 | 缩写 |
---|---|
LinearLayout | ll |
RelativeLayout rl | |
ConstraintLayout | cl |
ListView | lv |
ScollView | sv |
TextView | tv |
Button | btn |
ImageView | iv |
CheckBox | cb |
RadioButton | rb |
EditText | et |
其它控件的缩写推荐使用小写字母并用下划线进行分割,例如:ProgressBar 对应的缩写为 progress_bar;DatePicker 对应的缩写为 date_picker。
为了支持多种屏幕尺寸和密度,Android 提供了多种通用屏幕密度来适配,但是Android 的屏幕分辨率和密度并不存在严格的对应关系,应尽量避免直接基于分辨率来开发,而是通过适配不同的屏幕密度来保证控件和图片的显示效果。不同密度 drawable 目录中的图片分辨率设置,参考不同密度的 dpi 比例关系。
正例
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();
}
这是因为在Service中处理耗时操作超过10S就会导致ANR。
正例:
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) {
}
}
}
}
由于BroadcastReceiver#onReceive()是在主线程执行(其实四大组件都是由主线程生成的并运行在主线程中的),如果执行耗时操作会导致 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); //UserHomeService继承自IntentService
this.startService(userHomeIntent);
}
};
反例:
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//直接在onReceive方法里处理耗时操作,目的都是为了避免ANR
MyDatabaseHelper myDB = new MyDatabaseHelper(context);
myDB.initData();
// have more database operation here
}
};
如果 registerReceiver()和 unregisterReceiver()不成对出现,则可能导致已经注册的receiver 没有在合适的时机注销,导致内存泄漏,占用内存空间,加重 SystemService 负担。部分华为的机型会对 receiver 进行资源管控,单个应用注册过多 receiver 会触发管控模块抛出异常,应用直接崩溃,因为Activity 的生命周期不对应,如果在onDestroy才反注册的话,可能出现多次 onResume 造成 receiver 注册多个,但最终只注销一个,其余 receiver 产生内存泄漏。
正例
public class MainActivity extends AppCompatActivity {
private static MyReceiver myReceiver = new MyReceiver();
...
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter("com.example.myservice");
registerReceiver(myReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(myReceiver);
}
}
反例
public class MainActivity extends AppCompatActivity {
private static MyReceiver myReceiver;
@Override
protected void onResume() {
super.onResume();
myReceiver = new MyReceiver();
IntentFilter filter = new IntentFilter("com.example.myservice"); registerReceiver(myReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myReceiver);
}
}
因为广播机制类似Winows 的Event机制,也是不安全的一个全局性的事件,所有注册了的都可以监听得到,通过 Context#sendBroadcast()发送的隐式广播会被所有感兴趣的 receiver 接收,恶意应用注册监听该广播的 receiver 可能会获取到 Intent 中传递的敏感信息,并进行其他危险操作。如果发送的广播为使用Context#sendOrderedBroadcast()方法发送的有序广播,优先级较高的恶意 receiver 可能直接丢弃该广播(早期就可以通过监听来电广播来拦截来电,直到现在都还有一些流氓应用通过监听互联网巨头的APP的某些全局广播来达到保活的目的),造成服务不可用,或者向广播结果塞入恶意数据。如果广播仅限于应用内,则可以使用本地广播 LocalBroadcastManager#sendBroadcast()实现,避免敏感信息外泄和 Intent 拦截的风险,反正尽量少用普通的全局广播,进程之间的通信可以通过AIDL。
正例:
Intent intent = new Intent("my-sensitive-event");
intent.putExtra("event", "this is a test event"); LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
反例:
Intent intent = new Intent();
v1.setAction("com.sample.action.server_running");
v1.putExtra("local_ip", v0.h);
v1.putExtra("port", v0.i);
v1.putExtra("code", v0.g);
v1.putExtra("connected", v0.s);
v1.putExtra("pwd_predefined", v0.r);
if (!TextUtils.isEmpty(v0.t)) {
v1.putExtra("connected_usr", v0.t);
}
context.sendBroadcast(v1);
//以上广播可能被其他应用的如下 receiver 接收导致敏感信息泄漏
final class MyReceiver extends BroadcastReceiver {
public final void onReceive(Context context, Intent intent) {
if (intent != null && intent.getAction() != null) {
String s = intent.getAction();
if (s.equals("com.sample.action.server_running") {
String ip = intent.getStringExtra("local_ip");
String pwd = intent.getStringExtra("code");
String port = intent.getIntExtra("port", 8888);
boolean status = intent.getBooleanExtra("connected", false);
}
}
}
}
如果浏览器支持 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);
因为Activity#onSaveInstanceState()方法不是 Activity 生命周期方法,也不保证一定会被调用,它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临时性数据,例如 UI 控件的属性等,不能跟数据的持久化存储混为一谈。
因为Activity 可 能 因 为 各 种 原 因 被 销 毁 , Android 支 持 页 面 被 销 毁 前 通 过 Activity#onSaveInstanceState() 保 存 自 己 的 状 态 。 但 如 果FragmentTransaction.commit()发生在 Activity 状态保存之后,就会导致 Activity 重建、恢复状态时无法还原页面状态,从而可能出错。为了避免给用户造成不好的体
正例
public class MainActivity extends FragmentActivity {
FragmentManager fragmentManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
MyFragment fragment = new MyFragment();
ft.replace(R.id.fragment_container, fragment);
ft.commit();
}
}
反例
public class MainActivity extends FragmentActivity {
FragmentManager fragmentManager;
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState){
super.onSaveInstanceState(outState, outPersistentState);
FragmentTransaction ft = fragmentManager.beginTransaction();
MyFragment fragment = new MyFragment();
ft.replace(R.id.fragment_container, fragment);
ft.commit();
}
}
例如一些工作线程的销毁和停止,因为 onDestroy() 执行的时机可能较晚。可根据实际需要,在Activity#onPause()/onStop()中结合 isFinishing()的判断来执行。
因为嵌套 Fragment 是在 Android API 17 添加到 SDK 以及 Support 库中的功能,Fragment嵌套使用会有一些坑,容易出现 bug,非必须的场景尽可能避免使用嵌套 Fragment比较常见的问题有如下几种:
onActivityResult()方法的处理错乱,内嵌的 Fragment 可能收不到该方法的回调,需要由宿主 Fragment
进行转发处理;
突变动画效果;
被继承的 setRetainInstance(),导致在 Fragment 重建时多次触发不必要的逻辑。
正例
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG);
if (null == fragment) {
FragmentB fragmentB = new FragmentB();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_container, fragmentB, FragmentB.TAG). commit();
}
反例
Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = currentFragment.getChildFragmentManager(). beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();
Service 组件一般运行主线程,应当避免耗时操作,如果有耗时操作应该在 Worker 线程执行。可以使用 IntentService 执行后台任务。
正例
public class SingleIntentService extends IntentService {
public SingleIntentService() {
super("single-service thread");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
//耗时操作
} catch (InterruptedException e) {
}
}
}
反例
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();
...
}
}
对于使用 Context#sendBroadcast()等方法发送全局广播的代码进行提示,如果该广播仅用于应用内,则可以使用 LocalBroadcastManager 来避免广播泄漏以及广播被拦截等安全问题,同时相对全局广播LocalBroadcastManager 安全性更好,同时拥有更高的运行效率。
正例
public class MainActivity extends ActionBarActivity {
private MyReceiver receiver;
private IntentFilter filter;
private Context context;
private static final String MY_BROADCAST_TAG = "com.example.localbroadcast";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstsanceState);
context = this;
setContentView(R.layout.activity_main);
receiver = new MyReceiver();
filter = new IntentFilter();
filter.addAction(MY_BROADCAST_TAG);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
intent.setAction(MY_BROADCAST_TAG);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
});
}
@Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter);
}
@Override
protected void onPause() {
super.onPause();
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
}
class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context arg0, Intent arg1) {
// message received
}
}
}
反例
//In activity, sending broadcast所有广播都使用全局广播
Intent intent = new Intent("com.example.broadcastreceiver.SOME_ACTION");
sendBroadcast(intent);