Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 安卓性能優化手冊

安卓性能優化手冊

編輯:關於Android編程

本手冊適合至少有初級經驗的開發者查閱或復習相關知識使用,新手可能會看不懂。

1、java代碼優化

1.1安卓如何執行代碼

dvm:.java->.class->.dex->.apk

優化斐波那契數列:

斐波那契數列的遞推公式是f(n)=f(n-1)+f(n-2),特征方程為:x2=x+1,解該方程得(1+sqrt(5))/2,(1-sqrt(5))/2.所以f(n)=Ax1n+Bx2n,帶入f(0)=0,f(1)=1得A=sqrt(5)/5,B=-sqrt(5)/5.則f(n)求出。

BigInteger:

用這個類來解決溢出問題。
斐波那契數列有如下性質:

(fn fn-1)=(fn-1 fn-2)*A,可以得出A=(1 1;1 0)

遞推可得:(fn fn-1)=(fn-1 fn-2)*A=(fn-2 fn-3)*A2=…=(f1 f0)*An-1

緩存結果:
用HashMap來充當緩存,當鍵是整數時,用SparseArray效率更高。

LruCache:
適合用來緩存圖片,它的主要算法原理是把最近使用的對象用強引用存儲在 LinkedHashMap 中,並且把最近最少使用的對象在緩存值達到預設定值之前從內存中移除。

-

private LruCache mJsonCache;
    /**
     * 緩存圖片信息
     */
    private LruCache mBitmapCache;

    public Util() {
        mJsonCache = new LruCache(1 * 1024 * 1024);
        mBitmapCache = new LruCache(2 * 1024 * 1024);
    }

數據結構:
安卓在這些數據結構中增加了自身的一些實現,一般是為了提高性能,LruCache\SparseArray\SparseBooleanArray\SparseIntArray\Pair
還有Arrays和Collections類。例如使用Arrays.sort對數組排序。

響應能力:熟悉生命周期,例如設備方向變化時,配置Android:configChanges屬性

推遲初始化:例如可以用android.view.ViewStub來推遲初始化。由於內存分配需要花時間,等到對象真正需要時才進行分配,也是一個很好的選擇。 StricMode:主線程不要用來訪問網絡等耗時操作。 SQLite語句:加快要執行的SQL語句字符串的創建速度,在這種情況下使用+運算符來連接字符串不是最有效的方法,而使用StringBuilder對象,或調用String.format可以提高性能。事務:一次性事務解決。可以使用SQLiteOpenHelper來替代手動創建數據庫。查詢:創建cursor只獲取第一列

2、NDK

主要由c/c++編寫,是安卓原生開發套件。

1、創建本地方法:

public native String encode(String text, int length);
public native String decode(String text, int length);
}

2、實現JNI粘合層:

使用jdk的javah工具自動生成。

在項目根目錄下創建jni文件夾在jni文件中創建一個c文件

在java代碼中,創建一個本地方法helloFromC

public native String helloFromC();

在jni中定義函數實現這個方法,函數名必須為

jstring Java_com_zhilinghui_helloworld1_MainActivity_helloFromC(JNIEnv* env, jobject obj)

返回一個字符串,用c定義一個字符串

char* cstr = "hello from c";

把c的字符串轉換成java的字符串

jstring jstr = (*env)->NewStringUTF(env, cstr);
return jstr;
在jni中創建Android.mk文件在c文件中添加

安卓中如何使用c/c++代碼(總結)

前面可以說的有點亂,這裡簡單概括一下:

步驟一:
在java中定義一個c方法的接口 ,相當於在java代碼中定義了一個接口 接口的實現方法是C語言實現的。
public native String hello();

步驟二:
實現C代碼
方法名 嚴格按照jni的規范

步驟三:
創建android.mk 告訴編譯器 如何把c代碼打包成函數庫
LOCAL_PATH := $(call my-dir)

步驟四:
把c代碼 打包成函數庫 用到了安裝的環境 到相應目錄下使用ndk-build打包

步驟五:
在java代碼中 引入庫函數

步驟六:
使用方法

3、NDK進階

匯編優化:安卓 NDK內置了GCC編譯器的功能。

要確保使用正確版本的objdump反編譯目標文件和庫。
ARM模式和Thumb模式。

色彩轉換:圖形程序中最常用的操作是把顏色從一種格式轉換為另一種,ARGB8888和RGB565. RAM指令

提高性能的技巧

內聯函數:即直接在調用處實現替換調用。在函數定義前加上”inline“關鍵字就可以了。循環展開:展開可以積極調度(或管道化)循環以掩蓋一些延遲。如果有足夠的空閒寄存器使變量保持活動狀態,因為通過展開相關性鏈展露了關鍵路徑,這將非常有用。內存預讀取:1.GCC的——builtin_prefetch();2、在ARM匯編代碼中使用PLD和PLDW指令,還可以使用PLI指令。用LDM/STM替換LDR/STD:使用單個指令加載多個寄存器比用多個LDR指令加載寄存器快。

4、高效使用內存

java的char是16位(UTF-16),java的long是64位,而c的long是32位,long long是64位。

訪問內存:

減少數據緩存讀未命中幾率的方法:1、在有大量數據存儲在數組中時,使用盡可能小的數據類型;2、選擇順序訪問而不是隨機訪問,最大限度的重用已在緩存中的數據。

垃圾收集:

內存洩漏

只有當某個對象不再引用時,它的內存才會被回收,當該被釋放的對象引用仍然存在的時候就會發生OOM。

例如:屏幕旋轉時。Eclipse中可以在DDMS中的Heap及Allocation Tracker跟蹤。

也可以使用StricMode類來檢測潛在在內存洩漏.

引用

強引用: 程序中絕大部分是這種引用。就是“正常”引用。
BigIntegerbi=BigInteger.valueOf(n);//強引用
Integer i=new Integer(n);//強引用
i=null;//Integer對象變成可回收的垃圾。

軟、弱、虛引用:

軟可及對象,垃圾收集器自己決定想回收舊回收。弱可及對象,基本會被回收。虛引用,幾乎很少用,可以用來注冊引用隊列。

垃圾收集
可能會在不定的時間觸發,幾乎無法控制其發生,例如堆被占滿不能進行內存分配時,在分配新對象要進行內存回收。

API

ActivityManager的getMemoryInfo()、getMemoryClass()、getLargeMemoryClass(); Debug的dumHprofData()、getMemoryInfo、getNativeHeapAllocatedSize()、getNativeHeapSize().

-

ActivityManager am=(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo  memInfo=new ActivityManager.MemoryInfo();
am.gettMemoryInfo(memInfo);

5、多線程和同步

線程

Thread對象。run()方法可以被復寫,Runnable對象傳遞給Thread構造函數。記得調用start()。

線程優先級,一般是1-10(最高),默認是5.而Linux的優先級是從-20到19(最低).

AsyncTask

AsyncTask啟動任務棧執行的輸入參數,後台任務執行的百分比,後台任務執行返回iud結果,匿名類。
例如:後台下載文件時刷新前台進度條。

Handler和Looper

他們是多線程應用線程間通信的基石。

Handler

Handler:處理者,負責Message的發送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。
Handler在run()方法中創建,因為它需要被綁定到指定的Looper,這Looper就是在run()方法中調用Looper.prepare(0創建的。在線程產生之前,調用getHandler()返回null.

Looper

有消息循環的線程。

Looper:消息泵,不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper。

Message:消息,其中包含了消息ID,消息處理對象以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。

MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message以鏈表的方式串聯起來的,等待Looper的抽取。

Thread:線程,負責調度整個消息循環,即消息循環的執行場所

Activity生命周期

7個生命周期
調用AsyncTask.cancel()會在doInbackground()返回後觸發onCancelled(),而不是onPoseExecute().

6、性能評測和剖析

DDMS中的Traceview

查看跟蹤代碼的執行時間,分析哪些是耗時操作 ;可以用於跟蹤方法的調用,尤其是Android Framework層的方法調用關系

TraceView羅列出了是所有監聽到的方法,當然也包括Android系統很多方法的耗時,如何在這麼多方法裡面查找到自己關心的? 可以通過TraceView 底部的find 來查找,通常Android app都是有包名的,可以先針對某些關心的列排序後,在通過包名進行一個個查找,這些就省去自己篩選出自己app 方法耗時排行的時間。

7、延長電池續航時間

一般屏幕和wifi很耗電。

測量電池用量

可以通過檢索固定的Intent。在應用啟動時就獲取電池當前電量,運行一段時間,在退出時再次獲取電池電量。但是這個值並不准確,因為還有其他應用運行。

禁用廣播接收器

只有在需要時才啟用廣播接收器。

因為BatteryManager廣播的是一個sticky的intent實體,這就意味著你不用非得注冊一個廣播接收者來讓你的程序接受這個廣播,你可以僅僅就是通過調用registerReceiver這個方法,在需要添加廣播接收者位置的參數上傳入null,當然你也可以新建一個廣播接收者,並在注冊廣播接收者的時候傳入。

網絡

最終解決問題,AT&T與來自密歇根大學的同事們的工作人員進行了深入的端到端數據傳輸路徑的綜合調查,最終發現是在設備和蜂窩網絡之間復雜的相互作用的問題的根源,相互作用,是很難看到,給出分層性網絡架構,故意隱藏低層協議的開發者在應用層的工作。

位置

使用

WakeLock

例如,在用戶觀看視頻或電影時,cpu需要做視頻解碼,同時保持屏幕開啟,讓用戶能夠觀看。

PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); WakeLock sCpuWakeLock = pm.newWakeLock( 
                PowerManager.FULL_WAKE_LOCK | 
                PowerManager.ACQUIRE_CAUSES_WAKEUP,"okTag"); 
 if (sCpuWakeLock!= null) {          
 sCpuWakeLock.release(); 
          sCpuWakeLock = null; 
}
PARTIAL_WAKE_LOCK:保持CPU 運轉,屏幕和鍵盤燈有可能是關閉的。

SCREEN_DIM_WAKE_LOCK:保持CPU 運轉,允許保持屏幕顯示但有可能是灰的,允許關閉鍵盤燈

SCREEN_BRIGHT_WAKE_LOCK:保持CPU 運轉,保持屏幕高亮顯示,允許關閉鍵盤燈

FULL_WAKE_LOCK:保持CPU 運轉,保持屏幕高亮顯示,鍵盤燈也保持亮度

ACQUIRE_CAUSES_WAKEUP:不會喚醒設備,強制屏幕馬上高亮顯示,鍵盤燈開啟。有一個例外,如果有notification彈出的話,會喚醒設備。

ON_AFTER_RELEASE:WakeLock 被釋放後,維持屏幕亮度一小段時間,減少WakeLock 循環時的閃爍情況
如果申請了partial wakelock,那麼即使按Power鍵,系統也不會進Sleep,如Music播放時 如果申請了其它的wakelocks,按Power鍵,系統還是會進Sleep
但如果不領會Android設計者的意圖而濫用Wake Lock API,為了自身程序在後台的正常工作而長時間阻止AP進入休眠狀態,就會成為待機電池殺手。

提醒

AlarmManage有一個AlarmManagerService,該服務程序主要維護app注冊下來的各類Alarm,並且一直監聽Alarm設備,一旦有Alarm觸發,或者是Alarm事件發生,AlarmManagerService就會遍歷Alarm列表,找到相應的注冊Alarm並發出廣播。

Alarm Manager會維持一個cpu的wake lock。這樣能保證電話休眠時,也能處理alarm的廣播。一旦alarm receiver的onReceive() 方法執行完,wake lock會迅速被釋放。如果在receiver中開啟一個service,有可能service還沒啟動,wake lock已經被釋放了。所以此時要實現單獨的wake lock策略。

有4種Alarm類型:
1)RTC_WAKEUP
在指定的時刻(設置Alarm的時候),喚醒設備來觸發Intent。
2)RTC
在一個顯式的時間觸發Intent,但不喚醒設備。
3)ELAPSED_REALTIME
從設備啟動後,如果流逝的時間達到總時間,那麼觸發Intent,但不喚醒設備。流逝的時間包括設備睡眠的任何時間。注意一點的是,時間流逝的計算點是自從它最後一次啟動算起。
4)ELAPSED_REALTIME_WAKEUP
從設備啟動後,達到流逝的總時間後,如果需要將喚醒設備並觸發Intent。

傳感器、圖形

這裡不做詳細的介紹了

8、圖形

布局優化

調用setContentView()。xml布局的到時候要減少創建對象數量,可以用不同的布局達到同樣的視覺效果,消除不必要的對象,或者推遲創建對象。

嵌套線性布局會深化布局層次,從而導致布局和按鍵處理變慢。當顯示10個及以上的項目的時候就需要相對布局。

標簽來合並布局

-



* 使用標簽重用布局

ViewStub:

推遲加載


布局工具

在sdk->tools目錄下,有hierarchyviewer和layoutopt,可以查看和分析布局

OpenGL ES

OpenGL ES (為OpenGL for Embedded System的縮寫) 為適用於嵌入式系統的一個免費二維和三維圖形庫。

任何復雜的2D或是3D圖形都是通過這三種幾何圖形構造而成的。 OpenGL ES提供了兩類方法來繪制一個空間幾何圖形:

public abstract void glDrawArrays(int mode, int first, int count) 使用VetexBuffer 來繪制,頂點的順序由vertexBuffer中的順序指定。
public abstract void glDrawElements(int mode, int count, int type, Buffer indices) ,可以重新定義頂點的順序,頂點的順序由indices Buffer 指定。
mode列表:GL_POINTS 繪制獨立的點、GL_LINE_STRIP繪制一條線段、GL_LINE_LOOP繪制一條封閉線段(首位相連)、GL_LINES繪制多條線段、GL_TRIANGLES繪制多個三角形(兩兩不相鄰)、GL_TRIANGLE_STRIP繪制多個三角形(兩兩相鄰)、GL_TRIANGLE_FAN以一個點為頂點繪制多個相鄰的三角形 對應頂點除了可以為其定義坐標外,還可以指定顏色,材質,法線(用於光照處理)等。 glEnableClientState 和 glDisableClientState 可以控制的pipeline開關可以有:GL_COLOR_ARRAY (顏色) ,GL_NORMAL_ARRAY (法線), GL_TEXTURE_COORD_ARRAY (材質), GL_VERTEX_ARRAY(頂點), GL_POINT_SIZE_ARRAY_OES等。 對應的傳入顏色,頂點,材質,法線的方法如下: glColorPointer(int size,int type,int stride,Buffer pointer) glVertexPointer(int size, int type, int stride, Buffer pointer) glTexCoordPointer(int size, int type, int stride, Buffer pointer) glNormalPointer(int type, int stride, Buffer pointer) OpenGL ES 內部存放圖形數據的Buffer有COLOR ,DEPTH (深度信息)等,在繪制圖形只前一般需要清空COLOR 和 DEPTH Buffer。

通用的矩陣變換指令 這裡介紹對應指定的坐標系(比如viewmodel, projection或是viewport) Android OpenGL ES支持的一些矩陣運算及操作。 矩陣本身可以支持加減乘除,對角線全為1的4X4 矩陣成為單位矩陣Identity Matrix 。

將當前矩陣設為單位矩陣的指令 為glLoadIdentity().
矩陣相乘的指令glMultMatrix*() 允許指定任意矩陣和當前矩陣相乘。
選擇當前矩陣種類glMatrixMode(). OpenGL ES 可以運行指定GL_PROJECTION,GL_MODELVIEW等坐標系,後續的矩陣操作將針對選定的坐標。
將當前矩陣設置成任意指定矩陣glLoadMatrix*()
在棧中保存當前矩陣和從棧中恢復所存矩陣,可以使用glPushMatrix()和glPopMatrix()
特定的矩陣變換平移glTranslatef(),旋轉glRotatef() 和縮放glScalef()

紋理壓縮

具體可以在官網看:http://developer.android.com/training/graphics/opengl/index.html

著色、場景復雜性、消隱、渲染、

只有當場景變化才渲染幀。

9、RenderScript

RenderScript 是一種低級的高性能編程語言,用於3D渲染和處理密集型計算(3D播放等和關於CPU密集型的計算)。一直以來Android 在繪圖性能的表現一直差強人意,引入NDK之後才有所改善,而在Honeycomb 中發布了RenderScript 這一殺手級在Framework 後,大大的增加了Android 本地語言的執行能力和計算能力。

RenderScript 在機器上進行第一遍編譯,然後在目標設備上進行最後一遍編譯(Just-In-Time Compiling),因而帶來更高效的原生二進制代碼。這也就是意味著,凡是支持RenderScript 的設備都可以運行你的代碼。不用管什麼架構。

目前 ,RenderScript 帶來的代碼只能在主處理器上運行,它會自動生成可利用多個核心的代碼(如果設備上有多個核心)。就因此,編譯出來的程序是針對該機器的最佳優化,這解決了Device Fragmentation,也就是說開發者再也不必擔心使用者的手機、平板夠不夠好、有沒有GPU…等等問題,全都交給RenderScript 去擔心就好。沒有GPU,RenderScript 寫好的程序就交由CPU來處理(背後的編譯技術其實是使用的LLVM)。

   
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved