Android进阶——阿里Android开发手册学习笔记(一)

引言

阿里巴巴不仅仅是只关注你的钱包,也在给程序的开发世界贡献着自己的力量,为你们的代码质量操碎了心,推出了众多技术文档,抱着让自己的代码更规范的心态,下载被阅读了阿里巴巴Android开发手册v1.0.1,由于不便阅读和快速定位,决定摘抄重排版到博客上,在原文档的基础上使当地增加一些所谓的解读和理解,你也可以直接去找原版的来读,直接略过这笔记。

一、Android 资源文件命名与使用

应用的资源文件需带模块名作为前缀,即**moudle_**xxxxxx,这是基于组件化、插件化开发考虑的,如果资源命名不规范,到时候在使用组件化和插件化的时候可能需要花费额外不必要的成本去处理资源文件冲突和合并的情况

1、layout文件的以“模块_类别_”为前缀的命名方式

  • 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 开头

2、drawable 资源名称以小写单词+下划线的方式命名

2.1、drawable 图片资源以 “模块名_业务功能描述_控件描述_控件状态限定词” 命名

根据分辨率不同存放在不同的 drawable 目录下,如果介意包大小建议只使用一套,系统去进行缩放。如:module_login_btn_pressed,module_tabs_icon_home_normal

2.2、anim 动画资源以 “模块名_逻辑名称_[方向|序号]” 命名

  • Tween 动画(使用简单图像变换的动画,例如缩放、平移)资源——尽可能以通用的动画名称命名,如 module_fade_in , module_fade_out , module_push_down_in (动画+方向)。

  • Frame 动画(按帧顺序播放图像的动画)资源——尽可能以模块+功能命名+序号。如module_loading_grey_001。

2.3、color 资源使用#AARRGGBB 格式,写入对应module下的colors.xml 文件中,以 “模块名_逻辑名称_颜色” 命名

#33b5e5e5

2.4、dimen 资源写入对应module下dimens.xml 文件中,以 “模块名_描述信息” 命名,

1dp

2.5、 style 资源写入对应module的styles.xml 文件中,采用 “ 父 style 名称 . 当前 style 名称 ” 方式命名,首字母大写。


2.6、string 资源文件或者文本用到字符需要全部写入对应module下的strings.xml 文件中,以小写单词+下划线的方式 “模块名_逻辑名称” 命名

如:moudule_login_tips,module_homepage_notice_desc

3、采用驼峰命名法,以 “View的缩写_功能描述” 给View 的id命名

控件 缩写
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。

4、图片根据其分辨率,放在不同屏幕密度的 drawable 目录下管理,否则可能在低密度设备上导致内存占用增加,又可能在高密度设备上导致图片显示不够清晰。

为了支持多种屏幕尺寸和密度,Android 提供了多种通用屏幕密度来适配,但是Android 的屏幕分辨率和密度并不存在严格的对应关系,应尽量避免直接基于分辨率来开发,而是通过适配不同的屏幕密度来保证控件和图片的显示效果。不同密度 drawable 目录中的图片分辨率设置,参考不同密度的 dpi 比例关系。

二、Android基本组件使用规范

1、 【强制】Activity 间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable的方式,可以考虑 EventBus、回调接口等替代方案,以免造成 TransactionTooLargeException

2、【强制】使用隐式 Intent 的跳转来实现Activity的跳转时,在发出 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();
}

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

这是因为在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) {
			}
		}
	}
}

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

由于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
	}
};

5、【强制】Activity 或者 Fragment 中动态注册 BroadCastReceiver 时,registerReceiver()和 unregisterReceiver()要成对出现。

如果 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);
	}
}

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

因为广播机制类似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);
	}
}
}
}

7、【强制】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);

8、持久化存储应该在 Activity#onPause()/onStop()中实行

因为Activity#onSaveInstanceState()方法不是 Activity 生命周期方法,也不保证一定会被调用,它是用来在 Activity 被意外销毁时保存 UI 状态的,只能用于保存临时性数据,例如 UI 控件的属性等,不能跟数据的持久化存储混为一谈。

9、 添 加 Fragment 时 , 确 保 FragmentTransaction#commit() 在Activity#onPostResume()或者 FragmentActivity#onResumeFragments()内调用。不要随意使用 FragmentTransaction#commitAllowingStateLoss() 来代替,任何commitAllowingStateLoss()的使用必须经过 code review,确保无负面影响。

因为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();
		}
}

10、不要在 Activity#onDestroy()内执行释放资源的工作

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

11、尽量避免使用嵌套的 Fragment。

因为嵌套 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();

12、总是使用显式 Intent 启动或者绑定 Service,且不要为服务声明 Intent Filter,保证应用的安全性。如果确实需要使用隐式调用,则可为 Service 提供 Intent Filter 并从 Intent 中排除相应的组件名称,但必须搭配使用 Intent#setPackage()方法设置 Intent 的指定包名,这样可以充分消除目标服务的不确定性。

13、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) {
		}
	}
}

反例

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();
	...
	}
}

14、对于只用于应用内的广播,优先使用本地广播 LocalBroadcastManager 来进行注册和发送

对于使用 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);

15、当前 Activity 的 onPause 方法执行结束后才会创建(onCreate)或恢复(onRestart)别的 Activity,所以在 onPause 方法中不适合做耗时较长的工作,这会影响到页面之间的跳转效率

你可能感兴趣的:(Android,入门)