阿里Android規(guī)范-02-必須遵守整理

三系草、Android基本組件

1刻炒、【強制】Activity間的數(shù)據(jù)通信罐寨,對于數(shù)據(jù)量比較大的,避免使用Intent + Parcelable 的方式纵柿,可以考慮EventBus等替代方案蜈抓,以免造成TransactionTooLargeException。

  1. 【強制】Activity間通過隱式Intent的跳轉(zhuǎn)昂儒,在發(fā)出Intent之前必須通過resolveActivity 檢查沟使,避免找不到合適的調(diào)用組件,造成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(); 
} 
  1. 【強制】避免在Service#onStartCommand()/onBind()方法中執(zhí)行耗時操作腊嗡,如果確 實有需求着倾,應(yīng)改用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) { 
            } 
        } 
    } 
}
  1. 【強制】避免在BroadcastReceiver#onReceive()中執(zhí)行耗時操作燕少,如果有耗時工作卡者, 應(yīng)該創(chuàng)建IntentService 完成妥曲,而不應(yīng)該在BroadcastReceiver 內(nèi)創(chuàng)建子線程去做下面。
    說明:
    由于該方法是在主線程執(zhí)行,如果執(zhí)行耗時操作會導(dǎo)致 UI 不流暢祷安∠庹可以使用 IntentService 嗽桩、創(chuàng)建 HandlerThread 或者調(diào)用 Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他Wroker線程 執(zhí)行 onReceive 方法凄敢。BroadcastReceiver#onReceive()方法耗時超過 10 秒鐘碌冶,可 能會被系統(tǒng)殺死。
    正例:
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 
    } 
}; 
  1. 【強制】避免使用隱式 Intent 廣播敏感信息涝缝,信息可能被其他注冊了對應(yīng) BroadcastReceiver的App接收扑庞。
    說明:
    通過Context#sendBroadcast()發(fā)送的隱式廣播會被所有感興趣的receiver接收,惡 意應(yīng)用注冊監(jiān)聽該廣播的 receiver 可能會獲取到 Intent 中傳遞的敏感信息拒逮,并進行 其他危險操作罐氨。如果發(fā)送的廣播為使用 Context#sendOrderedBroadcast()方法發(fā)送 的有序廣播,優(yōu)先級較高的惡意 receiver 可能直接丟棄該廣播滩援,造成服務(wù)不可用栅隐, 或者向廣播結(jié)果塞入惡意數(shù)據(jù)。
    如果廣播僅限于應(yīng)用內(nèi)玩徊,則可以使用 LocalBroadcastManager#sendBroadcast()實 現(xiàn)租悄,避免敏感信息外泄和Intent攔截的風(fēng)險。

正例:

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); 

以上廣播可能被其他應(yīng)用的如下receiver接收導(dǎo)致敏感信息泄漏

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); 
            } 
        } 
    } 
} 
  1. 【強制】Activity或者Fragment中動態(tài)注冊BroadCastReceiver時恩袱,registerReceiver() 和unregisterReceiver()要成對出現(xiàn)泣棋。
    說明:
    如果registerReceiver()和unregisterReceiver()不成對出現(xiàn),則可能導(dǎo)致已經(jīng)注冊的 receiver沒有在合適的時機注銷畔塔,導(dǎo)致內(nèi)存泄漏潭辈,占用內(nèi)存空間,加重SystemService 負擔澈吨。
    部分華為的機型會對 receiver 進行資源管控把敢,單個應(yīng)用注冊過多 receiver 會觸發(fā)管 控模塊拋出異常,應(yīng)用直接崩潰棚辽。

正例:

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 的生命周期不對應(yīng)技竟,可能出現(xiàn)多次 onResume 造成 receiver 注冊多個冰肴,但 終只注銷一個屈藐,其余receiver產(chǎn)生內(nèi)存泄漏榔组。
15.【強制】Android 基礎(chǔ)組件如果使用隱式調(diào)用,應(yīng)在 AndroidManifest.xml 中使用 <intent-filter> 或在代碼中使用 IntentFilter 增加過濾联逻。
說明:
如果瀏覽器支持Intent Scheme Uri語法搓扯,如果過濾不當,那么惡意用戶可能通過瀏 覽器 js 代碼進行一些惡意行為包归,比如盜取 cookie 等锨推。如果使用了 Intent.parseUri 函數(shù),獲取的intent必須嚴格過濾公壤。

正例:

// 將 intent scheme URL 轉(zhuǎn)換為 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); 
intent.addCategory("android.intent.category.BROWSABLE"); 
context.startActivity(intent);

四换可、UI與布局

  1. 【強制】布局中不得不使用 ViewGroup 多重嵌套時,不要使用 LinearLayout 嵌套厦幅, 改用RelativeLayout沾鳄,可以有效降低嵌套數(shù)。
    說明:
    Android 應(yīng)用頁面上任何一個 View 都需要經(jīng)過 measure确憨、layout译荞、draw 三個步驟 才能被正確的渲染。從xml layout的頂部節(jié)點開始進行measure休弃,每個子節(jié)點都需 要向自己的父節(jié)點提供自己的尺寸來決定展示的位置吞歼,在此過程中可能還會重新 measure(由此可能導(dǎo)致measure的時間消耗為原來的2-3倍)。節(jié)點所處位置越深塔猾, 嵌套帶來的 measure 越多篙骡,計算就會越費時。這就是為什么扁平的 View 結(jié)構(gòu)會性 能更好桥帆。
    同時医增,頁面擁上的View越多,measure老虫、layout叶骨、draw所花費的時間就越久。要縮 短這個時間祈匙,關(guān)鍵是保持View的樹形結(jié)構(gòu)盡量扁平忽刽,而且要移除所有不需要渲染的 View。理想情況下夺欲,總共的measure跪帝,layout,draw時間應(yīng)該被很好的控制在16ms 以內(nèi)些阅,以保證滑動屏幕時UI的流暢伞剑。
    要找到那些多余的 View(增加渲染延遲的 view),可以用 Android Studio Monitor 里的Hierarchy Viewer工具市埋,可視化的查看所有的view黎泣。
  2. 【強制】禁止在非UI線程進行View相關(guān)操作恕刘。
  3. 【強制】禁止在設(shè)計布局時多次為子 View 和父 View 設(shè)置同樣背景進而造成頁面過 度繪制,推薦將不需要顯示的布局進行及時隱藏抒倚。

正例:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical" > 
<TextView 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello" /> 
<Button  
    android:id="@+id/btn_mybuttom"
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:text="click it !" /> 
<ImageView  
    android:id="@+id/img" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" 
    android:visibility="gone" 
    android:src="@drawable/youtube" /> 
<TextView  
    android:text="it is an example!" 
    android:layout_width="match_parent" 
    android:layout_height="wrap_content" /> 
</LinearLayout> 

反例:

@Override 
protected void onDraw(Canvas canvas) { 
    super.onDraw(canvas); 
    int width = getWidth(); 
    int height = getHeight(); 
    mPaint.setColor(Color.GRAY); 
    canvas.drawRect(0, 0, width, height, mPaint); 
    mPaint.setColor(Color.CYAN); 
    canvas.drawRect(0, height/4, width, height, mPaint); 
    mPaint.setColor(Color.DKGRAY); 
    canvas.drawRect(0, height/3, width, height, mPaint); 
    mPaint.setColor(Color.LTGRAY); 
    canvas.drawRect(0, height/2, width, height, mPaint); 
}

11.【強制】不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因為這 樣會把 ListView 的所有 Item 都加載到內(nèi)存中褐着,要消耗巨大的內(nèi)存和 cpu 去繪制圖 面。
說明:
ScrollView中嵌套List或RecyclerView的做法官方明確禁止托呕。除了開發(fā)過程中遇到 的各種視覺和交互問題含蓉,這種做法對性能也有較大損耗。ListView等UI組件自身有 垂直滾動功能项郊,也沒有必要在嵌套一層ScrollView馅扣。目前為了較好的UI體驗,更貼 近Material Design的設(shè)計着降,推薦使用NestedScrollView岂嗓。

正例:

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout> 
    <android.support.v4.widget.NestedScrollView> 
        <LinearLayout> 
            <ImageView/> 
            ... 
            <android.support.v7.widget.RecyclerView/> 
        </LinearLayout> 
    </android.support.v4.widget.NestedScrollView> 
</LinearLayout> 

反例:

<ScrollView> 
    <LinearLayout> 
        <TextView/> 
        ... 
        <ListView/> 
        <TextView /> 
    </LinearLayout> 
</ScrollView> 

12.【強制】不要在 Android 的 Application 對象中緩存數(shù)據(jù)∪蛋基礎(chǔ)組件之間的數(shù)據(jù)共享 請使用Intent等機制厌殉,也可使用SharedPreferences等數(shù)據(jù)持久化機制。

反例:

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; 
 
@Override 
    void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.get_username); 
        tv = (TextView)findViewById(R.id.username); 
} 
 
@Override 
    void onResume() { 
        super.onResume(); 
        MyApplication app = (MyApplication) getApplication(); 
        tv.setText("Welcome back ! " + app.getUsername().toUpperCase()); 
    } 
}

14.【強制】使用 Adapter 的時候侈咕,如果你使用了 ViewHolder 做緩存公罕,在 getView()的 方法中無論這項 convertView 的每個子控件是否需要設(shè)置屬性(比如某個 TextView 設(shè)置的文本可能為 null,某個按鈕的背景色為透明耀销,某控件的顏色為透明等)楼眷,都需 要為其顯式設(shè)置屬性(Textview的文本為空也需要設(shè)置setText(""),背景透明也需要 設(shè)置)熊尉,否則在滑動的過程中罐柳,因為adapter item復(fù)用的原因,會出現(xiàn)內(nèi)容的顯示錯 亂狰住。

正例:

@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; 
}

五张吉、進程、線程與消息通信

  1. 【強制】不要通過 Intent 在 Android 基礎(chǔ)組件之間傳遞大數(shù)據(jù)(binder transaction 緩存為1MB)催植,可能導(dǎo)致OOM肮蛹。
  2. 【強制】在 Application 的業(yè)務(wù)初始化代碼加入進程判斷,確保只在自己需要的進程 初始化创南。特別是后臺進程減少不必要的業(yè)務(wù)初始化伦忠。

正例:

public class MyApplication extends Application { 
    @Override 
    public void onCreate() { 
        //在所有進程中初始化 
        .... 
        //僅在主進程中初始化 
        if (mainProcess) { 
        ... 
        }  
        //僅在后臺進程中初始化 
        if (bgProcess) { 
            ... 
        } 
    } 
}
  1. 【強制】新建線程時,必須通過線程池提供(AsyncTask 或者 ThreadPoolExecutor 或者其他形式自定義的線程池)稿辙,不允許在應(yīng)用中自行顯式創(chuàng)建線程昆码。
    說明:
    使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷,解 決資源不足的問題。如果不使用線程池赋咽,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題笔刹。另外創(chuàng)建匿名線程不便于后續(xù)的資源使用分析, 對性能分析等會造成困擾冬耿。

正例:

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()); 
//執(zhí)行任務(wù) 
executorService.execute(new Runnnable() { 
    ... 
}); 

反例:

new Thread(new Runnable() { 
    @Override 
    public void run() { 
        //操作語句 
        ... 
    } 
}).start(); 
  1. 【強制】線程池不允許使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方 式萌壳,這樣的處理方式讓寫的同學(xué)更加明確線程池的運行規(guī)則亦镶,規(guī)避資源耗盡的風(fēng)險。
    說明:
    Executors返回的線程池對象的弊端如下:
  1. FixedThreadPool 和 SingleThreadPool : 允許的請求隊列長度Integer.MAX_VALUE袱瓮,可能會堆積大量的請求缤骨,從而導(dǎo)致OOM;
  2. CachedThreadPool 和 ScheduledThreadPool:允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE尺借,可能會創(chuàng)建大量的線程绊起,從而導(dǎo)致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()); 

反例:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
  1. 【強制】子線程中不能更新界面燎斩,更新界面必須在主線程中進行虱歪,網(wǎng)絡(luò)操作不能在 主線程中調(diào)用。

六栅表、文件與數(shù)據(jù)庫

  1. 【強制】任何時候不要硬編碼文件路徑笋鄙,請使用Android文件系統(tǒng)API訪問。
    說明:
    Android 應(yīng)用提供內(nèi)部和外部存儲怪瓶,分別用于存放應(yīng)用自身數(shù)據(jù)以及應(yīng)用產(chǎn)生的用 戶數(shù)據(jù)萧落。可以通過相關(guān)API接口獲取對應(yīng)的目錄洗贰,進行文件操作找岖。
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更容易出現(xiàn)適配問題  
    File file = new File("/mnt/sdcard/Download/Album", alName); 
    if (!file.mkdirs()) { 
        Log.e(LOG_TAG, "Directory not created"); 
    } 
    return file; 
}
  1. 【強制】當使用外部存儲時许布,必須檢查外部存儲的可用性。

正例:

// 讀/寫檢查  
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; 
}
  1. 【強制】應(yīng)用間共享文件時绎晃,不要通過放寬文件系統(tǒng)權(quán)限的方式去實現(xiàn)爹脾,而應(yīng)使用 FileProvider。

正例:

<!-- AndroidManifest.xml --> 
<manifest> 
    ... 
    <application> 
    ... 
    <provider 
        android:name="android.support.v4.content.FileProvider" 
       android:authorities="com.example.fileprovider" 
       android:exported="false" 
       android:grantUriPermissions="true"> 
       <meta-data 
            android:name="android.support.FILE_PROVIDER_PATHS" 
            android:resource="@xml/provider_paths" /> 
    </provider> 
    ... 
    </application> 
</manifest> 
<!-- res/xml/provider_paths.xml --> 
<paths> 
    <files-path path="album/" name="myimages" /> 
</paths> 
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 分享文件給別的應(yīng)用箕昭,包括但不限于 Intent  
    getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image)); 
    startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE); 
}
  1. 【強制】數(shù)據(jù)庫Cursor必須確保使用完后關(guān)閉灵妨,以免內(nèi)存泄漏。
    說明:
    Cursor 是對數(shù)據(jù)庫查詢結(jié)果集管理的一個類落竹,當查詢的結(jié)果集較小時泌霍,消耗內(nèi)存不 易察覺。但是當結(jié)果集較大,長時間重復(fù)操作會導(dǎo)致內(nèi)存消耗過大朱转,需要開發(fā)者在 操作完成后手動關(guān)閉Cursor蟹地。
    數(shù)據(jù)庫Cursor在創(chuàng)建及使用時,可能發(fā)生各種異常藤为,無論程序是否正常結(jié)束怪与,必須 在后確保 Cursor 正確關(guān)閉,以避免內(nèi)存泄漏缅疟。同時分别,如果 Cursor 的使用還牽涉 多線程場景,那么需要自行保證操作同步存淫。

正例:

public void handlePhotos(SQLiteDatabase db, String userId) { 
    Cursor cursor; 
    try { 
        cursor = db.query(TUserPhoto, new String[] { "userId", "content" }, "userId=?", new String[] { userId }, null, null, null); 
        while (cursor.moveToNext()) { 
            // TODO  
        } 
    } catch (Exception e) { 
        // TODO  
    } finally { 
        if (cursor != null) { 
            cursor.close(); 
        } 
    } 
}

反例:

public void handlePhotos(SQLiteDatabase db, String userId) { 
    Cursor cursor = db.query(TUserPhoto, new String[] { "userId", "content" }, "userId=?", new String[] { userId }, null, null, null); 
    while (cursor.moveToNext()) { 
        // TODO  
    } 
    // 不能放任 cursor 不關(guān)閉  
}
  1. 【強制】多線程操作寫入數(shù)據(jù)庫時耘斩,需要使用事務(wù),以免出現(xiàn)同步問題桅咆。
    說明:
    通過 SQLiteOpenHelper 獲取數(shù)據(jù)庫 SQLiteDatabase 實例括授,Helper 中會自動緩存已經(jīng)打開的 SQLiteDatabase 實例,單個 App 中應(yīng)使用 SQLiteOpenHelper 的單例 模式確保數(shù)據(jù)庫連接唯一岩饼。由于SQLite自身是數(shù)據(jù)庫級鎖荚虚,單個數(shù)據(jù)庫操作是保證 線程安全的(不能同時寫入),transaction是一次原子操作籍茧,因此處于事務(wù)中的操作 是線程安全的曲管。
    若同時打開多個數(shù)據(jù)庫連接,并通過多線程寫入數(shù)據(jù)庫硕糊,會導(dǎo)致數(shù)據(jù)庫異常院水,提示 數(shù)據(jù)庫已被鎖住。

正例:

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); 
} 
  1. 【強制】執(zhí)行 SQL 語句時简十,應(yīng)使用 SQLiteDatabase#insert()檬某、update()、delete()螟蝙, 不要使用SQLiteDatabase#execSQL()恢恼,以免SQL注入風(fēng)險。

正例:

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); 
    //請?zhí)岣甙踩庾R胰默,不要直接執(zhí)行字符串作為SQL 語句  
    db.execSQL(sqlStmt); 
} 

10.【強制】如果 ContentProvider 管理的數(shù)據(jù)存儲在 SQL 數(shù)據(jù)庫中场斑,應(yīng)該避免將不受 信任的外部數(shù)據(jù)直接拼接在原始SQL語句中。

正例:

// 使用一個可替換參數(shù) 
String mSelectionClause =  "var = ?"; 
String[] selectionArgs = {""}; 
selectionArgs[0] = mUserInput;

反例:

// 拼接用戶輸入內(nèi)容和列名 
String mSelectionClause =  "var = " + mUserInput; 

七牵署、Bitmap漏隐、Drawable與動畫

  1. 【強制】加載大圖片或者一次性加載多張圖片,應(yīng)該在異步線程中進行奴迅。圖片的加 載青责,涉及到IO操作,以及CPU密集操作,很可能引起卡頓脖隶。

正例:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    ... 
    // 在后臺進行圖片解碼 
    @Override 
    protected Bitmap doInBackground(Integer... params) { 
        final Bitmap bitmap = BitmapFactory.decodeFile("some path"); 
        return bitmap; 
    } 
    ... 
}

反例:

Button btnLoadImage = (Button) findViewById(R.id.btn); 
btnLoadImage.setOnClickListener(new OnClickListener(){ 
    public void onClick(View v) { 
        Bitmap bitmap = BitmapFactory.decodeFile("some path"); 
    } 
}); 
  1. 【強制】在ListView扁耐,ViewPager,RecyclerView产阱,GirdView等組件中使用圖片時婉称, 應(yīng)做好圖片的緩存,避免始終持有圖片導(dǎo)致內(nèi)存溢出构蹬,也避免重復(fù)創(chuàng)建圖片王暗,引起 性能問題。建議使用 Fresco(https://github.com/facebook/fresco )怎燥、Glide (https://github.com/bumptech/glide)等圖片庫。

正例:
例如使用系統(tǒng)LruCache緩存蜜暑,參考: https://developer.android.com/topic/performance/graphics/cache-bitmap.html

private LruCache<String, Bitmap> mMemoryCache; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
    ... 
    // 獲取可用內(nèi)存的大值铐姚,使用內(nèi)存超出這個值將拋出 OutOfMemory異常。LruCache 通 過構(gòu)造函數(shù)傳入緩存值肛捍,以 KB 為單位隐绵。  
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
    // 把大可用內(nèi)存的1/8 作為緩存空間 
    final int cacheSize = maxMemory / 8; 
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
        @Override 
        protected int sizeOf(String key, Bitmap bitmap) { 
            return bitmap.getByteCount() / 1024; 
        } 
    }; 
    ... 
} 
 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
    if (getBitmapFromMemCache(key) == null) { 
        mMemoryCache.put(key, bitmap); 
    } 
} 
 
public Bitmap getBitmapFromMemCache(String key) { 
    return mMemoryCache.get(key); 
} 

public void loadBitmap(int resId, ImageView imageView) { 
    final String imageKey = String.valueOf(resId); 
    final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
    if (bitmap != null) { 
        mImageView.setImageBitmap(bitmap); 
    } else { 
        mImageView.setImageResource(R.drawable.image_placeholder); 
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView); 
        task.execute(resId); 
    } 
} 
 
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    ... 
    // 在后臺進行圖片解碼  
    @Override 
    protected Bitmap doInBackground(Integer... params) { 
        final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100)); 
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
        return bitmap; 
        } 
    ... 
} 

反例:
沒有存儲,每次都需要解碼拙毫,或者有緩存但是沒有合適的淘汰機制依许,導(dǎo)致緩存效果 很差,依然經(jīng)常需要重新解碼缀蹄。

  1. 【強制】png圖片使用TinyPNG或者類似工具壓縮處理峭跳,減少包體積。

  2. 【強制】使用完畢的圖片缺前,應(yīng)該及時回收蛀醉,釋放寶貴的內(nèi)存。

正例:

Bitmap bitmap = null; 
loadBitmapAsync(new OnResult(result){ 
    bitmap = result; 
}); 
...使用該 bitmap... 
// 使用結(jié)束衅码,在 2.3.3及以下需要調(diào)用 recycle()函數(shù)拯刁,在 2.3.3以上 GC 會自動管理,除非你明 確不需要再用逝段。 
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) { 
    bitmap.recycle(); 
} 
bitmap = null; 

反例:
使用完成圖片垛玻,始終不釋放資源。

  1. 【強制】在Activity#onPause()或Activity#onStop()回調(diào)中奶躯,關(guān)閉當前activity正在執(zhí) 行的的動畫帚桩。

正例:

public class MyActivity extends Activity { 
    ImageView mImageView; 
    Animation mAnimation; 
    Button mBtn; 
 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
        mImageView = (ImageView)findViewById(R.id.ImageView01); 
        mAnimation = AnimationUtils.loadAnimation(this, R.anim.anim); 
        mBtn= (Button)findViewById(R.id.Button01); 
        mBtn.setOnClickListener(new View.OnClickListener() { 
            @Override 
            public void onClick(View v) { 
                mImageView.startAnimation(mAnimation); 
            } 
        }); 
} 

 
@Override 
    public void onPause() { 
        //頁面退出,及時清理動畫資源  
        mImageView.clearAnimation() 
    } 
}

反例:
頁面退出時嘹黔,不關(guān)閉該頁面相關(guān)的動畫朗儒。

八、安全

  1. 【強制】禁止使用常量初始化矢量參數(shù)構(gòu)建 IvParameterSpec,建議 IV 通過隨機方 式產(chǎn)生醉锄。
    說明:
    使用常量初始化向量乏悄,密碼文本的可預(yù)測性會高得多,容易受到字典式攻擊恳不。iv 的 作用主要是用于產(chǎn)生密文的第一個block檩小,以使終生成的密文產(chǎn)生差異(明文相同 的情況下),使密碼攻擊變得更為困難烟勋。

正例:

byte[] rand = new byte[16]; 
SecureRandom r = new SecureRandom(); 
r.nextBytes(rand); 
IvParameterSpec iv = new IvParameterSpec(rand); 

反例:

IvParameterSpec iv_ = new IvParameterSpec("1234567890".getBytes()); 
System.out.println(iv.getIV()); 
  1. 【強制】將android:allowbackup屬性必須設(shè)置為false规求,阻止應(yīng)用數(shù)據(jù)被導(dǎo)出。
    說明:
    android:allowBackup 原本是 Android 提供的 adb 調(diào)試功能卵惦,如果設(shè)置為 true阻肿, 可以導(dǎo)出應(yīng)用數(shù)據(jù)備份并在任意設(shè)備上恢復(fù)。這對應(yīng)用安全性和用戶數(shù)據(jù)隱私構(gòu)成 極大威脅沮尿,所以必須設(shè)置為 false丛塌,防止數(shù)據(jù)泄露。

正例:

<application  
    android:allowBackup="false" 
    android:largeHeap="true" 
    android:icon="@drawable/test_launcher" 
    android:label="@string/app_name" 
    android:theme="@style/AppTheme" > 
  1. 【強制】如果使用自定義HostnameVerifier實現(xiàn)類畜疾,必須在verify()方法中校驗服務(wù) 器主機名的合法性赴邻,否則可能受到中間人攻擊。
    說明:
    在與服務(wù)器建立 https 連接時啡捶,如果 URL 的主機名和服務(wù)器的主機名不匹配姥敛,則 可通過該回調(diào)接口來判斷是否應(yīng)該允許建立連接。如果回調(diào)內(nèi)實現(xiàn)不恰當瞎暑,沒有有 效校驗主機名彤敛,甚至默認接受所有主機名,會大大增加安全風(fēng)險了赌。

反例:

HostnameVerifier hnv = new HostnameVerifier() { 
@Override 
public boolean verify(String hostname, SSLSession session) { 
    // 不做校驗臊泌,接受任意域名服務(wù)器  
    return true; 
    } 
}; 
HttpsURLConnection.setDefaultHostnameVerifier(hnv); 
  1. 【強制】如果使用自定義X509TrustManager實現(xiàn)類,必須在checkServerTrusted() 方法中校驗服務(wù)端證書的合法性揍拆,否則可能受到中間人攻擊渠概。
    說明:
    常見誤區(qū)是checkServerTrusted()方法根本沒有實現(xiàn),這將導(dǎo)致 X509TrustManager 形同虛設(shè)嫂拴。該方法中需要實現(xiàn)完備的校驗邏輯播揪,對于證書錯誤拋出
    CertificateException 。

正例:

HostnameVerifier hnv = new HostnameVerifier() { 
    @Override 
    public boolean verify(String hostname, SSLSession session) { 
        if("yourhostname".equals(hostname)){   
        return true;   
        } else {   
            HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); 
            return hv.verify(hostname, session); 
        } 
    } 
}; 

反例:

TrustManager tm = new X509TrustManager() { 
    public void checkClientTrusted(X509Certificate[] chain, String authType) 
    throws CertificateException { 
        //do nothing筒狠,接受任意客戶端證書  
} 
 
    public void checkServerTrusted(X509Certificate[] chain, String authType) 
    throws CertificateException { 
        //do nothing猪狈,接受任意服務(wù)端證書  
} 
 
    public X509Certificate[] getAcceptedIssuers() { 
        return null; 
    } 
}; 
sslContext.init(null, new TrustManager[] { tm }, null); 
  1. 【強制】在 SDK 支持的情況下,Android 應(yīng)用必須使用 V2 簽名辩恼,這將對 APK 文 件的修改做更多的保護雇庙。
  2. 【強制】所有的 Android 基本組件(Activity谓形、Service、BroadcastReceiver疆前、ContentProvider等)都不應(yīng)在沒有嚴格權(quán)限控制的情況下寒跳,將 android:exported 設(shè) 置為 true。
  3. 【強制】WebView 應(yīng)設(shè)置 WebView#getSettings()#setAllowFileAccess(false)竹椒、 WebView#getSettings()#setAllowFileAccessFromFileURLs(false) 童太、 WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false),阻止 file scheme URL 的訪問胸完。

8.【強制】不要把敏感信息打印到log中书释。
說明:
在開發(fā)過程中,為了方便調(diào)試赊窥,通常會使用 log 函數(shù)輸出一些關(guān)鍵流程的信息爆惧,這 些信息中通常會包含敏感內(nèi)容,讓攻擊者更加容易了解APP內(nèi)部結(jié)構(gòu)锨能,方便破解和 攻擊扯再,甚至直接獲取到有價值的敏感信息。

反例:

String username = "log_leak"; 
String password = "log_leak_pwd"; 
Log.d("MY_APP", "usesname" + username); 
Log.v("MY_APP", "send message to server "); 

以上代碼使用Log.d Log.v打印程序的執(zhí)行過程的username等調(diào)試信息腹侣,日志沒有 關(guān)閉叔收,攻擊者可以直接從Logcat中讀取這些敏感信息齿穗。所以在產(chǎn)品的線上版本中關(guān) 閉調(diào)試接口傲隶,不要輸出敏感信息。
9.【強制】確保應(yīng)用發(fā)布版本的android:debuggable屬性設(shè)置為false窃页。
10.【強制】本地加密秘鑰不能硬編碼在代碼中跺株,更不能使用 SharedPreferences 等本 地持久化機制存儲。應(yīng)選擇 Android 自身的秘鑰庫(KeyStore)機制或者其他安全 性更高的安全解決方案保存脖卖。
說明:
應(yīng)用程序在加解密時乒省,使用硬編碼在程序中的密鑰,攻擊者通過反編譯拿到密鑰可 以輕易解密APP通信數(shù)據(jù)畦木。
12.【強制】使用Android的AES/DES/DESede加密算法時袖扛,不要使用ECB加密模式, 應(yīng)使用CBC或CFB加密模式十籍。
說明:
加密模式有 ECB蛆封、CBC、CFB勾栗、OFB 等惨篱,其中 ECB 的安全性較弱,如果使用固 定的密鑰围俘,相同的明文將會生成相同的密文砸讳,容易受到字典攻擊琢融,建議使用 CBC、 CFB 或OFB 等模式簿寂。

  1. ECB:Electronic codebook漾抬,電子密碼本模式
  2. CBC:Cipher-block chaining,密碼分組鏈接模式
  3. CFB:Cipher feedback陶耍,密文反饋模式
  4. OFB:Output feedback奋蔚,輸出反饋模式

13.【強制】Android APP在HTTPS通信中,驗證策略需要改成嚴格模式烈钞。
說明:
Android APP在HTTPS通信中泊碑,使用ALLOW_ALL_HOSTNAME_VERIFIER,表 示允許和所有的HOST建立SSL通信毯欣,這會存在中間人攻擊的風(fēng)險馒过,終導(dǎo)致敏感 信息可能會被劫持,以及其他形式的攻擊酗钞。

反例:

SSLSocketFactory sf = new MySSLSocketFactory(trustStore); 
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 

ALLOW_ALL_HOSTNAME_VERIFIER 關(guān)閉 host 驗證腹忽,允許和所有的 host 建立 SSL 通信,BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 和瀏覽器兼容的 驗證策略砚作,即通配符能夠匹配所有子域名 窘奏,STRICT_HOSTNAME_VERIFIER 嚴 格匹配模式,hostname 必須匹配第一個 CN 或者任何一個 subject-alts葫录,以上例子 使用了 ALLOW_ALL_HOSTNAME_VERIFIER着裹,需要改成 STRICT_HOSTNAME_ VERIFIER。

九米同、其他

  1. 【強制】不能使用System.out.println打印log骇扇。

正例:

Log.d(TAG, "Some Android Debug info ..."); 

反例:

System.out.println("System out println ..."); 
  1. 【強制】Log的tag不能是" "。
    說明:
    日志的tag是空字符串沒有任何意義面粮,也不利于過濾日志少孝。

正例:

private static String TAG = "LoginActivity"; 
Log.e(TAG, "Login failed!"); 

反例:

Log.e("", "Login failed!"); 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熬苍,隨后出現(xiàn)的幾起案子稍走,更是在濱河造成了極大的恐慌,老刑警劉巖柴底,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婿脸,死亡現(xiàn)場離奇詭異,居然都是意外死亡似枕,警方通過查閱死者的電腦和手機盖淡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凿歼,“玉大人褪迟,你說我怎么就攤上這事冗恨。” “怎么了味赃?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵掀抹,是天一觀的道長。 經(jīng)常有香客問我心俗,道長傲武,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任城榛,我火速辦了婚禮揪利,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狠持。我一直安慰自己疟位,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布喘垂。 她就那樣靜靜地躺著甜刻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪正勒。 梳的紋絲不亂的頭發(fā)上得院,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死畔规,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的就谜。 我是一名探鬼主播怪蔑,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼里覆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缆瓣?” 一聲冷哼從身側(cè)響起喧枷,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弓坞,沒想到半個月后隧甚,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡渡冻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年戚扳,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片族吻。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡帽借,死狀恐怖珠增,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砍艾,我是刑警寧澤蒂教,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站脆荷,受9級特大地震影響凝垛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜓谋,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一梦皮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桃焕,春花似錦届氢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至型将,卻和暖如春寂祥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背七兜。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工丸凭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腕铸。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓惜犀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狠裹。 傳聞我的和親對象是個殘疾皇子虽界,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容