View
自定義View中在onDraw()方法中可以設置padding嗎?
答案是不能,設置padding后,View的布局改變,會重新進行measure,layout流程,然后draw,從而陷入死循環,導致內存溢出或泄漏;
自定義View
Android緩存策略
Android 之 Bitmap
一、加載Bitmap
BitmapFactory類提供了四類方法用來加載Bitmap:
1、
decodeFile(...)
通過圖片路徑加載,同時可以選擇是否設置options,不設置則采用默認options。
例子:
Bitmap bm = BitmapFactory.decodeFile(sd_path)
采用默認options
Bitmap bm = BitmapFactory.decodeFile(sd_path,options)
2、
decodeResource(...)
通過傳入Resource對象和R.drawable.xxx
形式加載。
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa);
默認options
3、
decodeStream(...)
通過輸入流加載
Bitmap bm = BitmapFactory.decodeStream(stream)
,這是一個耗時操作,要在子線程中執行
4、
decodeByteArray(...)
從字節數組中加載。通過講輸入流inputstream轉換成byte[]字節數組加載。
Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);
** 注:**decodeFile和decodeResource間接調用decodeStream方法。
二、高效加載bitmap
如果圖片過大,直接通過BitmapFactory
加載,容易出現內存溢出。這樣就需要采取一定策略來加載所需的圖片。主要就是通過BitmapFactory
內部的一個內部類Options
來實現。
尺寸壓縮 是壓縮圖片的像素,一張圖片所占內存的大小 圖片類型*寬*高,通過改變三個值減小圖片所占的內存,防止OOM,當然這種方式可能會使圖片失真 。這是必然的取舍。
設置Options,主要是設置圖片色彩模式,采樣率來實現。
** Android圖片色彩模式分類:**
**Bitmap.Config.ALPHA_8*
:**每個像素占用1byte內存。顏色信息只由透明度組成,占8位。
Bitmap.Config.ARGB_4444
:每個像素占用2byte內存。顏色信息由透明度與R(Red),G(Green),B(Blue)四部分組成,每個部分都占4位,總共占16位。
Bitmap.Config.ARGB_8888
:每個像素占用4byte內存。顏色信息由透明度與R(Red),G(Green),B(Blue)四部分組成,每個部分都占8位,總共占32位。是Bitmap默認的顏色配置信息,也是最占空間的一種配置。
Bitmap.Config.RGB_565
:每個像素占用2byte內存。顏色信息由R(Red),G(Green),B(Blue)三部分組成,R占5位,G占6位,B占5位,總共占16位。Android默認的色彩模式為ARGB_8888,這個色彩模式色彩最細膩,顯示質量最高。但同樣的,占用的內存也最大。
BitmapFactory.Options
的inPreferredConfig
參數可以 指定decode到內存中,手機中所采用的編碼,可選值定義在Bitmap.Config
中。缺省值是ARGB_8888。
采樣率:inSampleSize的值必須大于1時才會有效果,且采樣率同時作用于寬和高;當inSampleSize=1時,采樣后的圖片為圖片的原始大小。當inSampleSize=2時,采樣后的圖片的寬高均為原始圖片寬高的1/2,這時像素為原始圖片的1/(2x2),占用內存也為原始圖片的1/(2x2);inSampleSize的取值應該總為2的整數倍,否則會向下取整,取一個最接近2的整數倍,比如inSampleSize=3時,系統會取inSampleSize=2。
假設一張1024x1024,模式為ARGB_8888的圖片,inSampleSize=2,原始占用內存大小是4MB,采樣后的圖片占用內存大小就是(1024/2) x (1024/2 )x4 = 1MB。具體的采樣步驟可以參考Android 之 Bitmap
** 優點:** 效率較高,解析速度快
缺點:采樣率inSampleSize的取值只能是2的次方數(例如:inSampleSize=15,實際取值為8;inSampleSize=17,實際取值為16;實際取值會往2的次方結算),因此該方法不能精確的指定圖片的大小
三、Bitmap 注意事項
1、不用的bitmap即使釋放
if (!bmp.isRecycled()) {
bmp.recycle(); //回收圖片所占的內存
bitmap = null;
system.gc(); //提醒系統及時回收
}
2、捕獲OutOfMemoryError
bitmap在實例化的過程中是很耗內存的。很容易出現OutOfMemery內存溢出的情況。而且一出現程序就會crash。所以,需要對bitmap的實例化的時做OutOfMemoryError捕獲,需要注意的是OutOfMemoryError并不是異常而是錯誤。一般情況下java中異常是可以捕獲的。而錯誤是不可以的,因為Error的出現一般情況下程序就會終止。OutOfMemoryError比較特殊。
Bitmap bitmap = null;
try {
// 實例化Bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
// 如果實例化失敗 返回默認的Bitmap對象
return defaultBitmapMap;
}
**3、緩存通用的bitmap對象** 在加載用戶頭像的時候,如果用戶沒有上傳的頭像,一般會加載一個默認頭像。而用戶的默認頭像又是一樣的,所以,對于相同頭像的bitmap應該做緩存處理。`如果不進行緩存,盡管看到的是同一張圖片文件,但是使用BitmapFactory類的方法來實例化出來的Bitmap,是不同的Bitmap對象。緩存可以避免新建多個Bitmap對象,避免內存的浪費。`
4、圖片質量壓縮
上述用inSampleSize壓縮是尺寸壓縮,Android中還有一種壓縮方式叫質量壓縮。質量壓縮是在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,經過它壓縮的圖片文件大小(kb)會有改變,但是導入成bitmap后占得內存是不變的,寬高也不會改變。因為要保持像素不變,所以它就無法無限壓縮,到達一個值之后就不會繼續變小了。顯然這個方法并不適用與縮略圖,其實也不適用于想通過壓縮圖片減少內存的適用,僅僅適用于想在保證圖片質量的同時減少文件大小的情況而已
。
private void compressImage(Bitmap image, int reqSize) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 質量壓縮方法,這里100表示不壓縮,
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
int options = 100;
>
// 循環判斷壓縮后的圖片是否大于reqSize,大于則繼續壓縮
while (baos.toByteArray().length / 1024 > reqSize) {
baos.reset();//清空baos
// 這里壓縮options,把壓縮后的數據放到baos中
image.compress(Bitmap.CompressFormat.JPEG, options, baos);
options -= 10;
}
// 把壓縮后的baos放到ByteArrayInputStream中
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());
//decode圖片
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null); }
**?四、Android加載大量圖片內存溢出解決方案 **
①、在加載圖片的時候,盡量不要直接使用
setImageBitmap
或setImageResource
或BitmapFactory.decodeResource
來設置一張大圖,因為這些函數在完成decode后,最終都是通過java層的createBitmap來完成的,需要消耗更多內存,可以通過BitmapFactory.decodeStream
方法,創建出一個bitmap,再將其設為ImageView的 source
②、使用BitmapFactory.Options
對圖片進行壓縮
③、運用Java軟引用,進行圖片緩存,將需要經常加載的圖片放進緩存里,避免反復加載
**五、其他 **
圖片的旋轉,合成,圓角,縮放,裁剪,bitmap與drawable互相轉換,以及保存到sd卡的相關操作參考Android 之 Bitmap
WebView
WebView的實現主要依靠WebView和WebSettings這兩個類來實現。WebView提供容器,WebSetting設置WebView支持的屬性。
WebView使用過程中需要注意的地方:
1、在實例化WebView的時候盡量不要使用當前Activity的引用。用代碼New一個WebView而不是在XML中靜態寫入。有人利用LeakCanary檢測過傳入當前Activity引用時是否會出現內存泄露,結果是沒有。接著換成Application傳入,與之前傳入的Activity引用進行對比發現,雖然兩者都不會造成內存泄露,但是使用Application要使用Activity時所消耗的內存少20~30MB。所以,建議直接使用Application。
即WebView實例化的時候不要采用這種方式WebView webView = new WebView(?this);
應該采用這種方式WebView webView = new WebView(App.getContext());
而是采用動態加載的方式:
private WebView webView;
webView = new WebView(App.getContext());
//一定要設置WebView的LayoutParams,并且值MATCH_PARENT。否則的話就會出現有的網頁無法加載的情況
webView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
binding.webviewContainer.addView(webView);
**2、WebView資源的釋放**。頁面銷毀之前勿忘釋放WebView資源。具體釋放規則可以看下面這段代碼
private void clearWebViewResource(){
if (webView != null){
webView.removeAllViews();
//在5.1上如果不加上這句話就會出現內存泄露。這是5.1的bug
// mComponentCallbacks導致的內存泄漏
((ViewGroup)webView.getParent()).removeView(webView);
webView.setTag(null);
webView.clearHistory();
webView.destroy();
webView = null;
}
}
基于上述寫的Demo示例: [Demo](http://blog.csdn.net/loveyaozu/article/details/52933897)
Android res目錄下的raw和assets
**assets **資源目錄或者叫資產目錄,里面存放的是無法直接訪問的原生資源。與res屬于同級目錄。應用程序需要通過AssetsManager以二進制流的形式讀取文件。應用程序編譯的時候不會在R類中為assets目錄下的文件創建索引。
raw在res目錄下(res/raw)也是用于存放一些資源文件的。應用程序編譯的時候raw目錄下的資源文件會在R類中生成索引
res/raw與assets的比較
相同點:
都是用于存放資源文件的。兩者目錄下的文件在打包后會原封不動的保存在apk包中,不會被編譯成二進制。
不同點:
1、編譯的時候res/raw目錄下的文件會在R類中生成索引,訪問的時候直接使用資源ID即R.id.filename;assets目錄下的文件則不會,訪問的時候需要通過AssetManager。
2、res/raw下不能再有下級目錄;而assets則可以有。res/raw與assets目錄下文件的讀取
1、res/raw目錄下文件訪問方式:
InputStream is = getResources().openRawResource(R.raw.filename);
2、assets目錄下文件的讀取方式:
AssetManager am = getResources().getAssets();
InputStream is = am.open("filename.xxx");
或者直接這樣
InputStream is = getResources().getAssets().open("filename.xxx");
Android對assets和raw目錄下文件大小的限制
android 2.3之前在讀取這兩個資源文件夾中的文件時會有一定的限制,即單個文件大小不能超過1M ,如果讀取超過1M的文件會報 "Data exceeds UNCOMPRESS_DATA_MAX (1314625 vs 1048576)" 的IOException。
Android文件路徑
SD卡讀寫需要權限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
// SD卡是否可用
boolean isSDCardAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if (isSDCardAvailable){
//目錄 /storage/emulated/0
String path = Environment.getExternalStorageDirectory().getPath();
//SD卡上面的緩存目錄 /cache
String cachePath = Environment.getDownloadCacheDirectory().getPath();
}
//目錄 /data/data/packageName/filesString
fileDir = getFilesDir().getPath();
//目錄 /data/data/packageName/cacheString
cachePath = getCacheDir().getPath();
MVC,MVP 和 MVVM 設計模式
Sqlite數據庫
1.面試題
** 注意**:數據庫的增刪改操作最好加上事務,根據測試發現開啟事務比不開啟事務的時候數據插入的效率會高很多,特別是批量插入的時候。
關于Sqlite的一些細節:
1、對于數據的批量插入,數據的修改與刪除建議加上事務。有人做過測試,批量插入1000條數據的時候,使用事務和不使用事務的差別很大。使用事務的批量插入效率比不使用事務的批量插入要高的多。使用事務還有一點好處就是保證每次事務操作的原子性。出現錯誤自動回滾。
2、索引的使用。如果應用中查詢操作量級較大,業務對要求查詢要求較高的可以使用索引。
其他情況不要輕易使用。因為事物都有兩面。
- ①、對于數據的增刪,索引會?降低增刪的效率,使用了索引會變慢,比如你想要刪除字典中的一個字,那么你同時也需要刪除這個字在拼音索引和部首索引中的信息。
- ②、建立索引會增加數據庫的大小,比如字典中的拼音索引和部首索引實際上是會增加字典的頁數,讓字典變厚的。
3、大批量數據的插入或者刪除的時候盡量開啟新的線程。數據量過大,在插入或者查詢的時候耗時就會比較大。如果在UI線程中執行容易出現ANR。
4、SQLiteDatabase的引用。在多線程中只使用一個SQLiteDatabase引用,在用SQLiteDataBase.close()的時需要注意調是否還有別的線程在使用這個實例。如果一個線程操作完成后就直接close了,別一個正在使用這個數據庫的線程就會異常。
解決辦法:
- ①、將SQLiteDatabase實例放在Application中,使其生命周期和App一致。
- ②、采用計數器的方式,在Application中添加一個線程安全的計數器,每次數據庫操作完成后檢查一下計數是否為0。為0就關閉SQLiteDatabase,不為0就不關閉。
5、數據查詢時對cursor的遍歷。
?遍歷cursor時,我們通常的做法是這樣:
private void badQueryWithLoop(SQLiteDatabase db) {
Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
while (cursor.moveToNext()) {
long insertTime = cursor.getLong(cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME));
}
}
如果我們將獲取ColumnIndex的操作提到循環之外,效果會更好一些
private void goodQueryWithLoop(SQLiteDatabase db) {
Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
int insertTimeColumnIndex = cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME);
while (cursor.moveToNext()) {
long insertTime = cursor.getLong(insertTimeColumnIndex);
}
cursor.close();
}
**6、ContentValues容量調整** ContentValues內部采用了HashMap來存儲Key-Value數據,ContentValues的初始容量是8,如果當添加的數據超過8之前,則會進行雙倍擴容操作,因此建議對ContentValues填入的內容進行估量,設置合理的初始化容量,減少不必要的內部擴容操作。
事務事例:
public void insertStudent(Student student){
if (student == null){
return;
}
SQLiteDatabase database = null;
try {
database = dbHelper.getReadableDatabase();
database.beginTransaction();//開啟事務
ContentValues cv = new ContentValues();
cv.put(StudentColumn.NAME,student.getName());
cv.put(StudentColumn.SEX,student.getSex());
cv.put(StudentColumn.STUDENT_NUM,student.getStudentNum());
database.insertOrThrow(TableHelpter.TB_STUDENT,null,cv);
database.setTransactionSuccessful(); //操作執行完成將事務設置成功,這一句必須要有。否則的話,在關閉事務的時候會回滾結果不提交。
}catch (Exception e){
e.printStackTrace();
} finally {
database.endTransaction();//數據庫關閉前關閉事務
if (database != null){
database.close();
}
}
}
補充:數據庫中事務的四大特性
1、原子性
原子性是指事務包含的所有操作要么全部成功,要么全部失敗回滾。因此事務的操作如果成功就必須要完全應用到數據庫,如果操作失敗則不能對數據庫有任何影響。
2、一致性
一致性是指事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之后都必須處于一致性狀態。
拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那么不管A和B之間如何轉賬,轉幾次賬,事務結束后兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
3、隔離性
隔離性是當多個用戶并發訪問數據庫時,比如操作同一張表時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作所干擾,多個并發事務之間要相互隔離。
即要達到這么一種效果:對于任意兩個并發的事務T1和T2,在事務T1看來,T2要么在T1開始之前就已經結束,要么在T1結束之后才開始,這樣每個事務都感覺不到有其他事務在并發地執行。
4、持久性
持久性是指一個事務一旦被提交了,那么對數據庫中的數據的改變就是永久性的,即便是在數據庫系統遇到故障的情況下也不會丟失提交事務的操作。
數據庫并發帶來的問題
由于事務具有隔離性,即事務之間互不影響。當數據庫出現并發訪問的時候就會帶來一些列的問題。
1、臟讀
臟讀是指在一個事務處理過程里讀取了另一個未提交的事務中的數據。
2、不可重復讀
不可重復讀是指在對于數據庫中的某個數據,一個事務范圍內多次查詢卻返回了不同的數據值,這是由于在查詢間隔,被另一個事務修改并提交了。
例如事務T1在讀取某一數據,而事務T2立馬修改了這個數據并且提交事務給數據庫,事務T1再次讀取該數據就得到了不同的結果,發送了不可重復讀。
不可重復讀和臟讀的區別是,臟讀是某一事務讀取了另一個事務未提交的臟數據,而不可重復讀則是讀取了前一事務提交的數據。
3、幻讀
幻讀是事務非獨立執行時發生的一種現象。例如事務T1對一個表中所有的行的某個數據項做了從“1”修改為“2”的操作,這時事務T2又對這個表中插入了一行數據項,而這個數據項的數值還是為“1”并且提交給數據庫。而操作事務T1的用戶如果再查看剛剛修改的數據,會發現還有一行沒有修改,其實這行是從事務T2中添加的,就好像產生幻覺一樣,這就是發生了幻讀。
幻讀和不可重復讀都是讀取了另一條已經提交的事務(這點就臟讀不同),所不同的是不可重復讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。
事務的隔離級別
現在來看看MySQL數據庫為我們提供的四種隔離級別:
① Serializable (串行化):可避免臟讀、不可重復讀、幻讀的發生。
② Repeatable read (可重復讀):可避免臟讀、不可重復讀的發生。
③ Read committed (讀已提交):可避免臟讀的發生。
④ Read uncommitted (讀未提交):最低級別,任何情況都無法保證。
以上四種隔離級別最高的是Serializable級別,最低的是Read uncommitted級別,當然級別越高,執行效率就越低。像Serializable這樣的級別,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程只能在鎖外等待,所以平時選用何種隔離級別應該根據實際情況。在MySQL數據庫中默認的隔離級別為Repeatable read (可重復讀)。
在MySQL數據庫中,支持上面四種隔離級別,默認的為Repeatable read (可重復讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的為Read committed級別。
2.Sqlite詳解
3.Sqlite性能優化
RecyclerView,ListView,GridView
**1.ListView **
ListView 面試相關:
1.如何加載不同類別的item?
BaseAdapter中有兩個方法,一個是
public int getItemViewType(int position)
返回當前ItemView的類型,默認返回0。另一個方法是public int getViewTypeCount()
返回item view的類型個數,默認返回1。可以通過復寫這兩個方法,達到加載不同itemview的需求。假設,當前有兩個類型的viewitem,一個是文本類型,一個是圖片類型。我們就可以復寫getViewTypeCount
的時候返回2,然后通過復寫getItemViewType
,根據position通過getItem得到當前的object對象,然后根據這個object所對應的類型(文本,圖片)返回對應類型的int值。
2.listview的優化
- 重用ConvertView(?getView在首次調用的時候,convertView是null,所以這個時候就需要創建ItemView的layout。后續調用的時候就不需要了,因為此時的convertView已經不為null了。所以,convertView可以復用。?不用每次都用inflate一下ItemView的layout布局,如果每次inflate,這樣會非常消耗性能,尤其是item有成百上千個的時候。歸根結底就是為了
避免重復inflate ItemView的layout布局
) - 采用View Holder模式(目的是緩存ItemView的layout中子控件,不用每次都通過findViewById的方式去加載,而是從ViewHolder的緩存中獲取,速度就會快很多。)
關于上面兩個原理,可以查看ListView常用知識點歸納
- 使用異步線程加載圖片(一般都是直接使用圖片庫加載,如Glide, Picasso);
- 在adapter的getView方法中盡可能的減少邏輯判斷,特別是耗時的判斷;
- 避免GC(可以從LOGCAT查看有無GC的LOG);
- 在快速滑動時不要加載圖片;ListView 快速滑動不加載圖片
- 將ListView的scrollingCache和animateCache這兩個屬性設置為false(默認是true);
- 盡可能減少List Item的Layout層次(如可以使用RelativeLayout替換LinearLayout,或使用自定的View代替組合嵌套使用的Layout);
3.其他
ListView的item點擊事件和item中button點擊事件發送沖突
解決辦法:方法一,在ItemView配置的xml文件中的根節點添加屬性android:descendantFocusability="blocksDescendants"
?方法二,在要添加事件的控件上添加android:focusable="false"
**2.GridView **
** 3.RecyclerView**
** RecyclerView與ListView對比:**
1、ListView只支持線性顯示規則;RecyclerView支持多種顯示規則LinearLayoutManager
、GridLayoutManager
、StaggeredGridLayoutManager
。分別是線性規則,網格和瀑布流。可以通過setLayoutManager設置。所以RecyclerView的更加靈活多變。
2、ListView可以添加頭和腳;RecyclerView不可以。
3、ListView的adapter需要繼承重寫 BaseAdapter 類,自定義 ViewHolder 和 convertView 一起完成復用優化工作。RecyclerView則繼承重寫 RecyclerView.Adapter 和 RecyclerView.ViewHolder,設置布局管理器,控制布局效果。
4、ListView只支持縱向滾動,RecyclerView支持橫向和縱向滾動
5、ListView不支持局部刷新,
notifyDataSetChanged()
是整體刷新。RecyclerView可以通過notifyItemChanged(...)
刷新單個item。
6、RecyclerView不支持
OnItemClickListener
和OnItemLongClickListener
事件。需要自己實現。
7、RecyclerView的動畫效果比ListView更加豐富。
RecyclerView與ListView對比分析
RecyclerView 和 ListView 使用對比分析
ListView/RecyclerView通用adapter(實際開發中可以借鑒)
這兩篇博客是對上面通用adapter的實現介紹
CoordinatorLayout布局
- CoordinatorLayout的使用
- CoordinatorLayout——小試牛刀
- CoordinatorLayout——源碼分析
- CoordinatorLayout自定義Bahavior特效及其源碼分析
- Android 一步一步分析CoordinatorLayout.Behavior
Android內存泄露與性能優化
內存泄露檢測工具
Android性能優化方案
1.布局優化
盡量減少layout層級,減少界面繪制的工作量。
采用<include>,<merge>標簽
2.自定義View的繪制
onDraw中不要創建大量的局部對象。因為onDraw方法會被頻繁調用,這樣就會在一瞬間產生大量的臨時對象,不僅會占用過多內存還會導致系統頻繁GC,降低程序執行效率。
onDraw中不要做太多耗時才操作。
3.內存優化
靜態變量導致的內存泄露
示例:一個外部的靜態Context變量引用了當前的Activity實例,當Activity銷毀的時候無法被銷毀。或者一個Activity的一個靜態Context變量,引用了當前Activity,銷毀之前沒有釋放,導致Activity無法被銷毀。
解決方法:不要將Activty作為Context傳給外部Context變量。如果外部需要傳入Context,可以使用Application Context,因為Application Context的生命周期與APP的生命周期一致。如果Activity內部有靜態Context變量持有當前Activity實例,在onDestroy的時候要將該變量持有Activity釋放。單例模式導致的內存泄露
示例:單例模式內部的Context變量持有外部存入的Activity實例,在Activity銷毀之前沒有釋放操作,導致Activity無法被銷毀,內存無法回收。
解決辦法:如果單例模式需要Context的時候,可以通過調用Context.getApplicationContext()方法或者Activity.getApplication()方法來傳入Application對象。或者直接在Application 創建的時候,即onCreate的時候傳入Application Context。屬性動畫導致的內存泄露
示例:屬性動畫中有一類無限循環的動畫,如果在Activity播放了此類動畫并且沒有在onDestroy中去停止動畫,那么動畫會一直播放下去,并且這個時候Activity的View會被動畫持有,而View又持有了Activity,最終導致Activity無法釋放。
解決辦法:在Activity的onDrstroy中調用animator.cancel()來停止動畫。自定義Handler導致的內存泄露
示例:如果我們在Activity中定義一個非靜態的Handler內部類,這樣這個內部類就默認持有了當前Activity實例的引用。Handler常常伴隨著一個執行耗時操作的異步線程(如下載多張圖片),如果在完成耗時操作之前,Activity退出,異步線程持有handler的引用,handler持有Activity的引用,Activity的實例無法被銷毀,從而導致內存泄漏
解決辦法:用一個靜態的內部類的來代替,同時Weakference的方式傳入外部Activity的引用。同時在Activity的onDestroy方法里面調用mHandler.removeCallbacksAndMessages(null);移除該Handler相關的消息隊列中所有消息和所有的Runnable。AsyncTask導致的內存泄露
原因和Handler內存泄露原因相似,解決辦法也一樣,采用靜態內部類的方式。
4.其他
Bitmap對象使用完后,忘記了調用recycle()方法銷毀;
Android內存泄露與性能優化相關博客
- Android開發藝術探索 第15章 Android性能優化 讀書筆記
- Android 內存優化
- Android 內存泄漏總結
- Android 性能優化
- Android 性能優化
- android開發 之 優化篇
Android IPC(進程間通信)
進程間的通信方式:
1、Bundle,封裝數據通過intent進行傳輸。優點是簡單易用。缺點是只能傳輸bundle支持的數據類型。適用場景在四大組件之間?通信。
2、** 文件共享,各個進程通過讀寫同一份文件共享數據。優點是簡單易用。缺點是不適合高并發的場景,并且無法做到即時通信。適用場景,不存在?并發訪問的情況,對數據實時性沒有太高的要求。
3、 AIDL,Android接口定義語言。優點是功能強大,支持一對多的并發通信,支持實時通信。缺點是適用相對復雜,需要處理好線程同步。適用場景,存在一對多的通信,且有RPC(遠程調用)需求。
4、Messenger,基于消息的進程間通信。優點是功能簡單,支持一對多串行通信。缺點,不能很好處理的高并發情形,不支持RPC,數據通過Message進行傳輸,因此只能傳輸Bundle支持的數據類型。適用場景,低并發一對多即時通信,無RPC需求,或者無需返回結果的RPC需求。
5、ContentProvider,內容提供者,專門用于不同應用間的數據共享。優點是在數據源訪問方面功能強大,支持一對多并發的數據共享。可以通過call方法擴展其他操作。缺點,可以理解其為受約束的AIDL,主要提供數據源的CRUD操作。適用場景,一對多的進程間的數據共享。
6、Socket **,適用TCP或UDP協議進行網絡通信。優點是功能強大,可以通過網絡傳輸字節流,支持一對多并發實時通信。缺點是實現細節比較繁瑣和復雜,不支持直接的RPC。適用場景,網絡數據交換。
Android 多進程模式
積極作用:android系統為每個應用程序分配的內存是有限的。雖然隨著硬件技術的發展,android手機內存越來越大。但是可供app使用的內存有時還會覺得有點捉襟見肘,不夠用。所以我們可以通過給四大組件指定android:process屬性的方式,讓比較耗內存的組件運行在單獨的進程中。
但是多進程模式也帶來了諸多問題:
1.單例模式和靜態成員完全失效。原因很簡單,單例和靜態成員只在進程作用域內有效。一個進程相當于一個應用程序。所以,每個進程都會有一份單例對象和靜態成員。
2.同樣線程同步也會失效。原因與上述相同,線程同步只在進程作用域內有效。
3.SharedPreferences的可靠性下降。雖然SharedPreferences有Context.MODE_MULTI_PROCESS
多進程模式,但是可靠性任然很低。這個模式在6.0的時候被標記為deprecated過時的。
4.Applicaion會被多次創建。原因是一個進程相當于一個應用程序,所以Application自然而然的就會被創建多次。
- Android面試題(二)——IPC機制
- Android開發藝術探索 第2章 IPC機制 讀書筆記
- Android IPC - Binder 學習總結
- android IPC通信機制梳理
- android AIDL
- Android IPC機制2-AIDL的使用
- Android IPC機制
- 使用Messenger進行跨進程通信
- Android IPC機制(二)——利用Messenger實現跨進程通信
注:幾種IPC的代碼示例集錦
IPC幾種實現方式樣例
媒體相關
Android Data Binding(MVVM模式)
Data Binding 使用方法
準備
新建一個 Project,確保 Android 的 Gradle 插件版本不低于 1.5.0-alpha1:
classpath 'com.android.tools.build:gradle:1.5.0'
然后修改對應模塊(Module)的 build.gradle:
android {
....
dataBinding {
enabled = true
}
}
基礎
工程創建完成后,我們通過一個最簡單的例子來說明 Data Binding 的基本用法。
布局文件
使用 Data Binding 之后,xml 的布局文件就不再用于單純地展示 UI 元素,還需要定義 UI 元素用到的變量。所以,它的根節點不再是一個 ViewGroup,而是變成了 layout,并且新增了一個節點 data。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data> </data>
<!--原先的根節點(Root Element)-->
<LinearLayout>
....
</LinearLayout>
</layout>
要實現 MVVM 的 ViewModel就需要把數據(Model)與 UI(View) 進行綁定,data節點的作用就像一個橋梁,搭建了 View
和 Model
之間的通路。我們先在 xml 布局文件的 data節點中聲明一個 variable
,這個變量會為 UI 元素提供數據(例如 TextView
的android:text
),然后在 Java 代碼中把『后臺』數據與這個 variable
進行綁定。下面我們使用 Data Binding 創建一個展示用戶信息的表格。
數據對象
添加一個 POJO 類 - User,非常簡單,兩個屬性以及他們的getter
和 setter
。
public class User {
private final String firstName;
private final String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
稍后,我們會新建一個 User類型的變量,然后把它跟布局文件中聲明的變量進行綁定。
定義 Variable
回到布局文件,在data
節點中聲明一個 User
類型的變量 user
。
<data>
<variable name="user"type="com.liangfeizc.databindingsamples.basic.User" />
</data>
其中type
屬性就是我們在 Java 文件中定義的 User
類。當然,data
節點也支持 import
,所以上面的代碼可以換一種形式來寫。
<data>
<import type="com.liangfeizc.databindingsamples.basic.User" />
<variable name="user" type="User" />
</data>
然后我們剛才在 build.gradle
中添加的那個件- com.android.databinding
會根據 xml
文件的名稱 Generate 一個繼承自 ViewDataBinding
的類。 當然,IDE 中看不到這個文件,需要手動去 build
目錄下找。例如,這里 xml
的文件名叫 activity_basic.xml
,那么生成的類就是 ActivityBasicBinding
。
注意
java.lang.*
包中的類會被自動導入,可以直接使用,例如要定義一個 String類型的變量:
<variable name="firstName" type="String" />
綁定 Variable
修改 BasicActivity 的 onCreate
方法,用 DatabindingUtil.setContentView()
來替換掉 setContentView()
,然后創建一個 user
對象,通過binding.setUser(user)
與 variable
進行綁定。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityBasicBinding binding = DataBindingUtil.setContentView( this, R.layout.activity_basic);
User user = new User("fei", "Liang");
binding.setUser(user);
}
除了使用框架自動生成的 ActivityBasicBinding
,我們也可以通過如下方式自定義類名。
<data class="com.example.CustomBinding"></data>
注意ActivityBasicBinding
類是自動生成的,所有的 set
方法也是根據 variable
名稱生成的。例如,我們定義了兩個變量。
<data>
<variable name="firstName" type="String" />
<variable name="lastName" type="String" />
</data>
那么就會生成對應的兩個 set
方法。
setFirstName(String firstName);
setLastName(String lastName);
使用 Variable
數據與 Variable 綁定之后,xml 的 UI 元素就可以直接使用了。
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}" />
至此,一個簡單的數據綁定就完成了,可參考完整代碼
高級用法
使用類方法
首先定義一個靜態方法
public class MyStringUtils {
public static String capitalize(final String word) {
if (word.length() > 1) {
return String.valueOf(word.charAt(0)).toUpperCase() + word.substring(1);
}
return word;
}
}
然后在 xml
的 data
節點中導入:
<import type="com.liangfeizc.databindingsamples.utils.MyStringUtils" />
使用方法與 Java 語法一樣:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{MyStringUtils.capitalize(user.firstName)}" />
類型別名
如果我們在data
節點了導入了兩個同名的類怎么辦?
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" />
<variable name="user" type="User" />
這樣一來出現了兩個 User
類,那user
變量要用哪一個呢?不用擔心,import
還有一個 alias屬性。
<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
Null Coalescing 運算符
android:text="@{user.displayName ?? user.lastName}"
就等價于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
屬性值
通過 @{}
可以直接把 Java 中定義的屬性值賦值給 xml 屬性。
<TextView android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
使用資源
這個例子,官方教程有錯誤,可以參考Android Data Binder 的一個bug,完整代碼在此
<TextView android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
android:background="@android:color/black"
android:textColor="@android:color/white"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
Observable Binding
本來這一節的標題應該叫雙向綁定,但是很遺憾,現在的 Data Binding 暫時支持單向綁定,還沒有達到 Angular.js 的威力。要實現 Observable Binding
,首先得有一個implement
了接口 android.databinding.Observable
的類,為了方便,Android 原生提供了已經封裝好的一個類 - BaseObservable
,并且實現了監聽器的注冊機制。我們可以直接繼承 BaseObservable
。
public class ObservableUser extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return firstName;
}
@Bindable
public String getLastName() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
BR是編譯階段生成的一個類,功能與R.java
類似,用 @Bindable
標記過getter
方法會在 BR中生成一個 entry。通過代碼可以看出,當數據發生變化時還是需要手動發出通知。 通過調用notifyPropertyChanged(BR.firstName)
可以通知系統 BR.firstName
這個 entry
的數據已經發生變化,需要更新 UI。
除此之外,還有一種更細粒度的綁定方式,可以具體到成員變量,這種方式無需繼承 BaseObservable
,一個簡單的 POJO就可以實現。
public class PlainUser {
public final ObservableField<String> firstName = new ObservableField<>();
public final ObservableField<String> lastName = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
系統為我們提供了所有的 primitive type 所對應的 Observable類,例如ObservableInt
、ObservableFloat
、ObservableBoolean
等等,還有一個 ObservableField
對應著 reference type。剩下的數據綁定與前面介紹的方式一樣,具體可參考ObservableActivity。
帶 ID 的 View
Data Binding 有效降低了代碼的冗余性,甚至完全沒有必要再去獲取一個View
實例,但是情況不是絕對的,萬一我們真的就需要了呢?不用擔心,只要給 View 定義一個 ID,Data Binding 就會為我們生成一個對應的 final
變量。
<TextView android:id="@+id/firstName"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
上面代碼中定義了一個 ID 為 firstName
的 TextView
,那么它對應的變量就是public final TextView firstName;
具體代碼可參考 ViewWithIDsActivity.java
ViewStubs
xml
中的 ViewStub
經過 binding 之后會轉換成 ViewStubProxy, 具體代碼可參考 ViewStubActivity.java簡單用代碼說明一下,xml 文件與之前的代碼一樣,根節點改為 layout
,在 LinearLayout
中添加一個ViewStub
,添加ID。
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout ...>
<ViewStub
android:id="@+id/view_stub"
android:layout="@layout/view_stub" ... />
</LinearLayout>
</layout>
在 Java 代碼中獲取 binding
實例,為ViewStubProy
注冊 ViewStub.OnInflateListener
事件:
binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
ViewStubBinding binding = DataBindingUtil.bind(inflated);
User user = new User("fee", "lang");
binding.setUser(user);
}
});
Dynamic Variables
完整代碼可以參考 dynamic以 RecyclerView
為例,Adapter
的 DataBinding 需要動態生成,因此我們可以在onCreateViewHolder
的時候創建這個DataBinding,然后在onBindViewHolder
中獲取這個 DataBinding。
public static class BindingHolder extends RecyclerView.ViewHolder {
private ViewDataBinding binding;
public BindingHolder(View itemView) {
super(itemView);
}
public ViewDataBinding getBinding() {
return binding;
}
public void setBinding(ViewDataBinding binding) {
this.binding = binding;
}
}
@Override
public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
ViewDataBinding binding = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.getContext()), R.layout.list_item, viewGroup, false);
BindingHolder holder = new BindingHolder(binding.getRoot());
holder.setBinding(binding);
return holder;
}
@Override
public void onBindViewHolder(BindingHolder holder, int position) {
User user = users.get(position);
holder.getBinding().setVariable(BR.user, user);
holder.getBinding().executePendingBindings();
}
注意此處 DataBindingUtil
的用法:
ViewDataBinding binding = DataBindingUtil.inflate( LayoutInflater.from(viewGroup.getContext()), R.layout.list_item, viewGroup, false);
還有另外一種比較簡潔的方式,直接在構造 Holder 時把 View與自動生成的 XXXBinding
進行綁定。
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private static final int USER_COUNT = 10;
@NonNull
private List<User> mUsers;
public UserAdapter() {
mUsers = new ArrayList<>(10);
for (int i = 0; i < USER_COUNT; i ++) {
User user = new User(RandomNames.nextFirstName(), RandomNames.nextLastName());
mUsers.add(user);
}
}
public static class UserHolder extends RecyclerView.ViewHolder {
private UserItemBinding mBinding;
public UserHolder(View itemView) {
super(itemView);
mBinding = DataBindingUtil.bind(itemView);
}
public void bind(@NonNull User user) {
mBinding.setUser(user);
}
}
@Override
public UserHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View itemView = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.user_item, viewGroup, false);
return new UserHolder(itemView);
}
@Override
public void onBindViewHolder(UserHolder holder, int position) {
holder.bind(mUsers.get(position));
}
@Override
public int getItemCount() {
return mUsers.size();
}
}
Attribute setters
有了 Data Binding,即使屬性沒有在 declare-styleable中定義,我們也可以通過 xml 進行賦值操作。 為了演示這個功能,我自定義了一個 View - NameCard,屬性資源 R.styleable.NameCard 中只定義了一個 age
屬性,其中 firstName
和 lastName
只有對應的兩個 setter
方法。只要有setter
方法就可以像下面代碼一樣賦值:
<com.liangfeizc.databindingsamples.attributesetters.UserView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/largePadding"
app:onClickListener="@{activity.clickListener}"
app:firstName="@{@string/firstName}"
app:lastName="@{@string/lastName}" app:age="27" />
onClickListener
也是同樣道理,只不過我們是在 Activity
中定義了一個Listener
。
轉換器 (Converters)
非常重要
使用 Converter 一定要保證它不會影響到其他的屬性,例如這個 @BindingConversion- convertColorToString 就會影響到android:visibility, 因為他們都是都符合從 int 到 int 的轉換。
在 xml 中為屬性賦值時,如果變量的類型與屬性不一致,通過 DataBinding 可以進行轉換。例如,下面代碼中如果要為屬性 android:background
賦值一個int
型的 color
變量:
<View
android:background="@{isError.get() ? @color/red : @color/white}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_height="@{height}" />
只需要定義一個標記了@BindingConversion
的靜態方法即可(方法的定義位置可以隨意):
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
具體代碼可參考 ConversionsActivity.java。
其他
Android應用程序安裝目錄:
/system/app系統自帶的應用程序,無法刪除。
/data/app 用戶程序安裝的目錄,有刪除權限。安裝時把apk文件復制到此目錄。
/data/data 存放應用程序的數據。
data/dalvik-cache 將apk中的dex文件安裝到dalvik-cache目錄下(dex文件是dalvik虛擬機的可執行文件,其大小約為原始apk文件大小的四分之一)。
APP安裝過程:復制APK安裝包到data/app目錄下,解壓并掃描安裝包,把dex文件(Dalvik字節碼)保存到dalvik-cache目錄,并data/data目錄下創建對應的應用數據目錄。
Android 如何查找so文件所在目錄,安裝APK時so安裝到哪個目錄