以下內容摘自 阿里巴巴Android開發手冊
我們的目標是:
- 防患未然,提升質量意識,降低故障率和維護成本;
- 標準統一,提升協作效率;
- 追求卓越的工匠精神,打磨精品程式碼。
- 【強制】必須遵守,違反本約定或將會引起嚴重的後果;
- 【推薦】儘量遵守,長期遵守有助於系統穩定性和合作效率的提升;
- 【參考】充分理解,技術意識的引導,是個人學習、團隊溝通、專案合作的方向。
阿里Android開發規範:資原始檔命名與使用規範
阿里Android開發規範:四大基本元件
阿里Android開發規範:UI 與佈局
阿里Android開發規範:程式、執行緒與訊息通訊
阿里Android開發規範:檔案與資料庫
阿里Android開發規範:Bitmap、Drawable 與動畫
阿里Android開發規範:安全與其他
Android 基本元件指 Activity、Fragment、Service、BroadcastReceiver、ContentProvider 等等。
1、【強制】Activity 間的資料通訊,對於資料量比較大的,避免使用 Intent + Parcelable的方式,可以考慮 EventBus 等替代方案,以免造成 TransactionTooLargeException。
2、【推薦】Activity#onSaveInstanceState()方法不是 Activity 生命週期方法,也不保證一定會被呼叫。它是用來在 Activity 被意外銷燬時儲存 UI 狀態的,只能用於儲存臨時性資料,例如 UI 控制元件的屬性等,不能跟資料的持久化儲存混為一談。持久化儲存應該在Activity#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) {
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
if (Config.LOGD) {
Log.d(LOGTAG, "activity not found for " + mimeType + " over " +Uri.parse(url). getScheme(), e);
}
}
}
}
複製程式碼
反例:
Intent intent = new Intent();
intent.setAction("com.great.activity_intent.Intent_Demo1_Result3");
複製程式碼
4、【強制】避免在 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) {
}
}
}
}
複製程式碼
5、【強制】避免在 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);
}
};
複製程式碼
反例:
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
MyDatabaseHelper myDB = new MyDatabaseHelper(context);
myDB.initData();
// have more database operation here
}
};
複製程式碼
擴充套件參考:
developer.android.com/reference/a…
6、【強制】避免使用隱式 Intent 廣播敏感資訊,資訊可能被其他註冊了對應BroadcastReceiver 的 App 接收。
說明:
通過 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);
複製程式碼
反例:
Intent intent = new Intent();
intent.setAction("com.sample.action.server_running");
intent.putExtra("local_ip", v0.h);
intent.putExtra("port", v0.i);
intent.putExtra("code", v0.g);
intent.putExtra("connected", v0.s);
intent.putExtra("pwd_predefined", v0.r);
if (!TextUtils.isEmpty(v0.t)) {
intent.putExtra("connected_usr", v0.t);
}
context.sendBroadcast(intent);
複製程式碼
以上廣播可能被其他應用的如下 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、【推薦】新增 Fragment 時,確保 FragmentTransaction#commit() 在Activity#onPostResume()或者 FragmentActivity#onResumeFragments()內呼叫。不要隨意使用 FragmentTransaction#commitAllowingStateLoss()來代替,任何commitAllowingStateLoss()的使用必須經過 code review,確保無負面影響。
說明:
Activity可能因為各種原因被銷燬, Android 支援頁面被銷燬前通過Activity#onSaveInstanceState() 儲存自己的狀態。但如果FragmentTransaction.commit()發生在 Activity 狀態儲存之後,就會導致 Activity 重建、恢復狀態時無法還原頁面狀態,從而可能出錯。為了避免給使用者造成不好的體驗,系統會丟擲 IllegalStateExceptionStateLoss 異常。推薦的做法是在 Activity 的onPostResume() 或 onResumeFragments() (對 FragmentActivity) 裡執行FragmentTransaction.commit(),如有必要也可在 onCreate()裡執行。不要隨意改用FragmentTransaction.commitAllowingStateLoss()或者直接使用 try-catch 避免crash,這不是問題的根本解決之道,當且僅當你確認 Activity 重建、恢復狀態時,本次 commit 丟失不會造成影響時才可這麼做。
正例:
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);
fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
MyFragment fragment = new MyFragment();
ft.replace(R.id.fragment_container, fragment);
ft.commit();
}
}
複製程式碼
擴充套件參考:
- https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
- https://developer.android.com/reference/android/app/FragmentTransaction.html#commit()) 8、【推薦】不要在 Activity#onDestroy()內執行釋放資源的工作,例如一些工作執行緒的銷燬和停止,因為 onDestroy()執行的時機可能較晚。可根據實際需要,在Activity#onPause()/onStop()中結合 isFinishing()的判斷來執行。 9、【推薦】如非必須,避免使用巢狀的 Fragment。 說明: 巢狀 Fragment 是在 Android API 17 新增到 SDK 以及 Support 庫中的功能,Fragment 巢狀使用會有一些坑,容易出現 bug,比較常見的問題有如下幾種:
- onActivityResult()方法的處理錯亂,內嵌的 Fragment 可能收不到該方法的回撥, 需要由宿主 Fragment 進行轉發處理;
- 突變動畫效果;
- 被繼承的 setRetainInstance(),導致在 Fragment 重建時多次觸發不必要的邏輯。
非必須的場景儘可能避免使用巢狀 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();
複製程式碼
擴充套件參考:
10、【推薦】總是使用顯式 Intent 啟動或者繫結 Service,且不要為服務宣告 Intent Filter,保證應用的安全性。如果確實需要使用隱式呼叫,則可為 Service 提供 Intent Filter並從 Intent 中排除相應的元件名稱,但必須搭配使用 Intent#setPackage()方法設定Intent 的指定包名,這樣可以充分消除目標服務的不確定性。
11、【推薦】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) {
e.printStackTrace();
}
}
}
複製程式碼
反例:
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();
...
}
}
複製程式碼
擴充套件參考:
developer.android.com/training/ru…
12、【推薦】對於只用於應用內的廣播,優先使用 LocalBroadcastManager 來進行註冊和傳送,LocalBroadcastManager 安全性更好,同時擁有更高的執行效率。
說明:
對於使用 Context#sendBroadcast()等方法傳送全域性廣播的程式碼進行提示。如果該廣播僅用於應用內,則可以使用 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(savedInstanceState);
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);
複製程式碼
13、【推薦】當前Activity的onPause方法執行結束後才會執行下一個Activity的onCreate方法,所以在 onPause 方法中不適合做耗時較長的工作,這會影響到頁面之間的跳轉效率。
14、【強制】不要在 Android 的 Application 物件中快取資料。基礎元件之間的資料共享請使用 Intent 等機制,也可使用 SharedPreferences 等資料持久化機制。
反例:
class MyApplication extends Application {
String username;
String getUsername() {
return username;
}
void setUsername(String username) {
this.username = username;
}
}
class SetUsernameActivity extends Activity {
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.set_username);
MyApplication app = (MyApplication) getApplication();
app.setUsername("tester1");
startActivity(new Intent(this, GetUsernameActivity.class));
}
}
class GetUsernameActivity extends Activity {
TextView tv;
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.get_username);
tv = (TextView)findViewById(R.id.username);
}
void onResume() {
super.onResume();
MyApplication app = (MyApplication) getApplication();
tv.setText("Welcome back ! " + app.getUsername().toUpperCase());
}
}
複製程式碼
15、【推薦】使用 Toast 時,建議定義一個全域性的 Toast 物件,這樣可以避免連續顯示Toast 時不能取消上一次 Toast 訊息的情況(如果你有連續彈出 Toast 的情況,避免使用 Toast.makeText)。
16、【強制】使用 Adapter 的時候,如果你使用了 ViewHolder 做快取,在 getView()的方法中無論這項 convertView 的每個子控制元件是否需要設定屬性(比如某個 TextView設定的文字可能為 null,某個按鈕的背景色為透明,某控制元件的顏色為透明等),都需要為其顯式設定屬性(Textview 的文字為空也需要設定setText(""),背景透明也需要設定),否則在滑動的過程中,因為 adapter item 複用的原因,會出現內容的顯示錯亂。
正例:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder myViews;
if (convertView == null) {
myViews = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_item, null);
myViews.mUsername = (TextView)convertView.findViewById(R.id.username);
convertView.setTag(myViews);
} else {
myViews = (ViewHolder)convertView.getTag();
}
Info p = infoList.get(position);
String dn = p.getDisplayName;
myViews.mUsername.setText(StringUtils.isEmpty(dn) ? "" : dn);
return convertView;
}
static class ViewHolder {
private TextView mUsername;
}
複製程式碼
17、【推薦】Activity或者Fragment中動態註冊BroadCastReceiver時,registerReceiver()和 unregisterReceiver()要成對出現。
說明:
如果 registerReceiver()和 unregisterReceiver()不成對出現,則可能導致已經註冊的receiver 沒有在合適的時機登出,導致記憶體洩漏,佔用記憶體空間,加重 SystemService負擔。
部分華為的機型會對 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);
}
}
複製程式碼
Activity 的生命週期不對應,可能出現多次 onResume 造成 receiver 註冊多個,但最終只登出一個,其餘 receiver 產生記憶體洩漏。
阿里Android開發規範:資原始檔命名與使用規範
阿里Android開發規範:四大基本元件
阿里Android開發規範:UI 與佈局
阿里Android開發規範:程式、執行緒與訊息通訊
阿里Android開發規範:檔案與資料庫
阿里Android開發規範:Bitmap、Drawable 與動畫
阿里Android開發規範:安全與其他