編輯:關於Android編程
性能問題一般歸結為3類:
1.UI卡頓和穩定性,這類問題用戶可直接感知,最為重要。
2.內存問題,內存問題主要表現為內存洩漏。如果存在內存洩漏,應用會不斷消耗內存,
容易導致頻繁GC使系統出現卡頓(內存不足時會調用垃圾回收機制回收內存,GC比較消耗性能),
或者出現OOM報錯。
3.耗電問題,會影響續航,表現為不必要的自啟動,不恰當持鎖導致系統無法正常休眠,系統休眠後頻繁
喚醒系統等。
一. UI卡頓常見原因和分析方法
1.人為在UI線程中做輕微耗時操作,導致UI線程卡頓。
2.布局Layout過於復雜,無法在16ms內完成渲染。
3.同一時間動畫執行的次數過多,導致CPU或GPU負載過重。
4.View過度繪制,導致某些像素在同一幀時間內被繪制多次,從而使CPU或GPU負載過重;
5.View頻繁的觸發measure、layout,導致measure、layout累計耗時過多及整個View頻繁的重新渲染;
6.內存頻繁觸發GC過多(同一幀中頻繁創建內存),導致暫時阻塞渲染操作;
7.冗余資源及邏輯等導致加載和執行緩慢; 8.工作線程優先級未設置為 Process.THREAD_PRIORITY_BACKGROUND 導致後台線程搶占UI線程cpu時間片,阻塞渲染操作; 9.ANR;二. 卡頓分析解決的一般步驟
1)解決過度繪制問題
>在設置開發者選項調試GPU過度繪制中打開調試,看對應界面是否有過度繪制,如果有先解決掉:

> 定位過渡繪制區域
> 利用Android提供的工具進行位置確認以及修改(HierarchyView , Tracer for OpenGL ES)
> 定位到具體的視圖(xml文件或者View)
> 通過代碼和xml文件分析過渡繪制的原因
> 結合具體情況進行優化
> 使用Lint工具進一步優化
2) 檢查是否有主線程做了耗時操作:
嚴苛模式(StrictMode),是Android提供的一種運行時檢測機制,用於檢測代碼運行時的一些不規范的操作,最常見的場景是用於發現主線程的IO操作。應用程序可以利用StrictMode盡可能的發現一些編碼的疏漏。
> 開啟 StrictMode
>> 對於應用程序而言,Android 提供了一個最佳使用實踐:盡可能早的在
android.app.Application 或 android.app.Activity 的生命周期使能 StrictMode,onCreate()方法就是一個最佳的時機,越早開啟就能在更多的代碼執行路徑上發現違規操作。
>> 監控代碼
publicvoidonCreate(){
if(DEVELOPER_MODE){
StrictMode.setThreadPolicy(newStrictMode.ThreadPolicy.Builder()
.detectAll().penaltyLog().build());
StrictMode.setVmPolicy(newStrictMode.VmPolicy.Builder()
.detectAll().penaltyLog().build());
}
super.onCreate();
}
如果主線程有網絡或磁盤讀寫等操作,在logcat中會有"D/StrictMode"tag的日志輸出,從而定位到耗時操作的代碼。
3)如果主線程無耗時操作,還存在卡頓,有很大可能是必須在UI線程操作的一些邏輯有問題,比如控件measure、layout耗時過多等,此時可通過Traceview以及systrace來進行分析。
4)Traceview:Traceview主要用做熱點分析,找出最需要優化的點。
> 打開DDMS然後選擇一個進程,接著點擊上面的“Start Method Profiling”按鈕(紅色小點變為黑色即開始運行),然後操作我們的卡頓UI,然後點擊"Stop Method Profiling",會打開如下界面:

圖中展示了Trace期間各方法調用關系,調用次數以及耗時比例。通過分析可以找出可疑的耗時函數並進行優化;
5)systrace:抓取trace:
> 執行如下命令:
$cdandroid-sdk/platform-tools/systrace $pythonsystrace.py--time=10-omynewtrace.htmlschedgfxviewwm
> 操作APP,然後會生成一個mynewtrace.html 文件,用Chrome打開。
> 圖示如下:

通過分析上面的圖,可以找出明顯存在的layout,measure,draw的超時問題。
6)導入如下插件,可通過在方法上添加@DebugLog來打印方法的耗時:
build.gradle:
buildscript{
dependencies{
//用於方便調試性能問題的打印插件。給訪法加上@DebugLog,就能輸出該方法的調用參數,以及執行時間;
class path com.jakewharton.hugo:hugo-plugin:1.2.1 } }
//用於方便調試性能問題的打印插件。給訪法加上@DebugLog,就能輸出該方法的調用參數,以及執行時間;
apply plugin:com.jakewharton.hugo
java:
@Debug Log
public void test(int a){
int b=a*a;
}
三.內存性能優化分析(內存洩漏問題)
Java是垃圾回收語言的一種,其優點是開發者無需特意管理內存分配,降低了應用由於局部故障(segmentation fault)導致崩潰,同時防止未釋放的內存把堆棧(heap)擠爆的可能,所以寫出來的代碼更為安全。
不幸的是,在Java中仍存在很多容易導致內存洩漏的邏輯可能(logical leak)。如果不小心,你的Android應用很容易浪費掉未釋放的內存,最終導致內存用光的錯誤拋出(out-of-memory,OOM)。
一般內存洩漏(traditional memory leak)的原因是:當該對象的所有引用都已經釋放了,對象仍未被釋放。(譯者注:Cursor忘記關閉等)
邏輯內存洩漏(logical memory leak)的原因是:當應用不再需要這個對象,當仍未釋放該對象的所有引用。
如果持有對象的強引用,垃圾回收器是無法在內存中回收這個對象。
在Android開發中,最容易引發的內存洩漏問題的是Context。比如Activity的Context,就包含大量的內存引用,例如View Hierarchies和其他資源。一旦洩漏了Context,也意味洩漏它指向的所有對象。Android機器內存有限,太多的內存洩漏容易導致OOM。
檢測邏輯內存洩漏需要主觀判斷,特別是對象的生命周期並不清晰。幸運的是,Activity有著明確的生命周期,很容易發現洩漏的原因。Activity.onDestroy()被視為Activity生命的結束,程序上來看,它應該被銷毀了,或者Android系統需要回收這些內存(譯者注:當內存不夠時,Android會回收看不見的Activity)。
如果這個方法執行完,在堆棧中仍存在持有該Activity的強引用,垃圾回收器就無法把它標記成已回收的內存,而我們本來目的就是要回收它!
結果就是Activity存活在它的生命周期之外。
Activity是重量級對象,應該讓Android系統來處理它。然而,邏輯內存洩漏總是在不經意間發生。(譯者注:曾經試過一個Activity導致20M內存洩漏)。在Android中,導致潛在內存洩漏的陷阱不外乎兩種:
檢查一下你有沒有遇到下列的情況。
在類中定義了靜態Activity變量,把當前運行的Activity實例賦值於這個靜態變量。
如果這個靜態變量在Activity生命周期結束後沒有清空,就導致內存洩漏。因為static變量是貫穿這個應用的生命周期的,所以被洩漏的Activity就會一直存在於應用的進程中,不會被垃圾回收器回收。
static Activity activity;
void setStaticActivity() {
activity = this;
}
View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticActivity();
nextActivity();
}
});

類似的情況會發生在單例模式中,如果Activity經常被用到,那麼在內存中保存一個實例是很實用的。正如之前所述,強制延長Activity的生命周期是相當危險而且不必要的,無論如何都不能這樣做。
特殊情況:如果一個View初始化耗費大量資源,而且在一個Activity生命周期內保持不變,那可以把它變成static,加載到視圖樹上(View Hierachy),像這樣,當Activity被銷毀時,應當釋放資源。(譯者注:示例代碼中並沒有釋放內存,把這個static view置null即可,但是還是不建議用這個static view的方法)
static view;
void setStaticView() {
view = findViewById(R.id.sv_button);
}
View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
setStaticView();
nextActivity();
}
});

繼續,假設Activity中有個內部類,這樣做可以提高可讀性和封裝性。將如我們創建一個內部類,而且持有一個靜態變量的引用,恭喜,內存洩漏就離你不遠了(譯者注:銷毀的時候置空,嗯)。
private static Object inner;
void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}
View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createInnerClass();
nextActivity();
}
});

內部類的優勢之一就是可以訪問外部類,不幸的是,導致內存洩漏的原因,就是內部類持有外部類實例的強引用。
相似地,匿名類也維護了外部類的引用。所以內存洩漏很容易發生,當你在Activity中定義了匿名的AsyncTsk
。當異步任務在後台執行耗時任務期間,Activity不幸被銷毀了(譯者注:用戶退出,系統回收),這個被AsyncTask持有的Activity實例就不會被垃圾回收器回收,直到異步任務結束。
void startAsyncTask() {
new AsyncTask() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
startAsyncTask();
nextActivity();
}
});

同樣道理,定義匿名的Runnable,用匿名類Handler執行。Runnable內部類會持有外部類的隱式引用,被傳遞到Handler的消息隊列MessageQueue中,在Message消息沒有被處理之前,Activity實例不會被銷毀了,於是導致內存洩漏。
void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createHandler();
nextActivity();
}
});

我們再次通過Thread和TimerTask來展現內存洩漏。
void spawnThread() {
new Thread() {
@Override public void run() {
while(true);
}
}.start();
}
View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
spawnThread();
nextActivity();
}
});

只要是匿名類的實例,不管是不是在工作線程,都會持有Activity的引用,導致內存洩漏。
void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}
View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
scheduleTimer();
nextActivity();
}
});

最後,通過Context.getSystemService(int name)可以獲取系統服務。這些服務工作在各自的進程中,幫助應用處理後台任務,處理硬件交互。如果需要使用這些服務,可以注冊監聽器,這會導致服務持有了Context的引用,如果在Activity銷毀的時候沒有注銷這些監聽器,會導致內存洩漏。
void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
registerListener();
nextActivity();
}
});

四、耗電量優化建議
電量優化主要是注意盡量不要影響手機進入休眠,也就是正確申請和釋放WakeLock,另外就是不要頻繁喚醒手機,主要就是正確使用Alarm。
五、一些好的代碼實踐
1. 節制地使用Service
2. 當界面不可見時釋放內存
3. 當內存緊張時釋放內存
4. 避免在Bitmap上浪費內存
對大圖片,先獲取圖片的大小信息,根據實際需要展示大小計算inSampleSize,最後decode;
public static Bitmap decodeSampledBitmapFromFile(String filename,
int reqWidth,int reqHeight){
//First decodewithinJustDecodeBounds=truetocheckdimensions
final BitmapFactory.Optionsoptions=new BitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeFile(filename,options);
//CalculateinSampleSize
options.inSampleSize=
reqHeight);
calculateInSampleSize(options,
reqWidth,reHeight);
//DecodebitmapwithinSampleSizeset
options.inJustDecodeBounds=false;
return BitmapFactory.decodeFile(filename,options);
}
public static int calculateInSampleSize(BitmapFactory.Optionsoptions,
intreqWidth,intreqHeight){
//Rawheightandwidthofimage
final int height=options.outHeight;
final int width=options.outWidth;
int inSampleSize=1;
if(height>reqHeight||width>reqWidth){
if(width>height){
inSampleSize=Math.round((float)height/(float)reqHeight);
}else{
inSampleSize=Math.round((float)width/(float)reqWidth);
}
}
return inSampleSize;
}
5. 使用優化過的數據集合
6. 謹慎使用抽象編程
7. 盡量避免使用依賴注入框架
很多依賴注入框架是基於反射的原理,雖然可以讓代碼看起來簡潔,但是是有礙性能的。
8. 謹慎使用external libraries
9. 優化整體性能
10. 使用ProGuard來剔除不需要的代碼
android{
buildTypes{
release{
minifyEnabledtrue
shrinkResources true
proguardFilesgetDefaultProguardFile(proguard-android.txt),src/main/proguard-project.txt
signingConfigsigningConfigs.debug
}
}
11. 慎用異常,異常對性能不利
拋出異常首先要創建一個新的對象。Throwable 接口的構造函數用名為
fillInStackTrace() 的本地方法,fillInStackTrace() 方法檢查棧,收集調用跟蹤信
息。只要有異常被拋出,VM 就必要調整調用棧,因為在處理過程中創建了一
個新對象。
異常只能用於錯誤處理,不應該用來控制程序流程。
以下例子不好:
try{
startActivity(intentA);
}catch(){
startActivity(intentB);
}
應該用下面的語句判斷:
if(getPackageManager().resolveActivity(intentA,0)!=null)
不要再循環中使用 try/catch 語句,應把其放在最外層,使用 System.arraycopy()代替 for 循環復制。
UI碎片控件之Fragment——底部導航欄的實現(方法1)
(一)概述在上一節中我們對Fragment進行了一個初步的了解,學習了概念,生命周期,Fragment管理與 Fragment事務,以及動態與靜態加載Fragment。從
Android開發登陸案例
layout<?xml version=1.0?>-<LinearLayout android:paddingTop=@dimen/ac
Android Studio導入項目的幾種方法
本篇教程中使用到的Android Studio版本為1.0, Eclipse ADT版本23.0.4。請嘗試更新到該版本。Android Studio默認使用 Gradl
Android之Async-http
簡介:Async-http是一款國外的開源框架,作者是loopj。是基於Apache HttpClient庫的。可以方便快速高效的進行網絡數據請求和發送,文件下載和上傳。