⑴ android多核,多線程該如何用
在程序開發的實踐當中,為了讓程序表現得更加流暢,我們肯定會需要使用到多線程來提升程序的並發執行性能。但是編寫多線程並發的代碼一直以來都是一個相對棘手的問題,所以想要獲得更佳的程序性能,我們非常有必要掌握多線程並發編程的基礎技能。
眾所周知,Android 程序的大多數代碼操作都必須執行在主線程,例如系統事件(例如設備屏幕發生旋轉),輸入事件(例如用戶點擊滑動等),程序回調服務,UI 繪制以及鬧鍾事件等等。那麼我們在上述事件或者方法中插入的代碼也將執行在主線程。
一旦我們在主線程裡面添加了操作復雜的代碼,這些代碼就很可能阻礙主線程去響應點擊/滑動事件,阻礙主線程的 UI 繪制等等。我們知道,為了讓屏幕的刷新幀率達到 60fps,我們需要確保 16ms 內完成單次刷新的操作。一旦我們在主線程裡面執行的任務過於繁重就可能導致接收到刷新信號的時候因為資源被佔用而無法完成這次刷新操作,這樣就會產生掉幀的現象,刷新幀率自然也就跟著下降了(一旦刷新幀率降到 20fps 左右,用戶就可以明顯感知到卡頓不流暢了)。
為了避免上面提到的掉幀問題,我們需要使用多線程的技術方案,把那些操作復雜的任務移動到其他線程當中執行,這樣就不容易阻塞主線程的操作,也就減小了出現掉幀的可能性。
那麼問題來了,為主線程減輕負的多線程方案有哪些呢?這些方案分別適合在什麼場景下使用?Android 系統為我們提供了若干組工具類來幫助解決這個問題。
AsyncTask: 為 UI 線程與工作線程之間進行快速的切換提供一種簡單便捷的機制。適用於當下立即需要啟動,但是非同步執行的生命周期短暫的使用場景。
HandlerThread: 為某些回調方法或者等待某些任務的執行設置一個專屬的線程,並提供線程任務的調度機制。
ThreadPool: 把任務分解成不同的單元,分發到各個不同的線程上,進行同時並發處理。
IntentService: 適合於執行由 UI 觸發的後台 Service 任務,並可以把後台任務執行的情況通過一定的機制反饋給 UI。
了解這些系統提供的多線程工具類分別適合在什麼場景下,可以幫助我們選擇合適的解決方案,避免出現不可預期的麻煩。雖然使用多線程可以提高程序的並發量,但是我們需要特別注意因為引入多線程而可能伴隨而來的內存問題。舉個例子,在 Activity 內部定義的一個 AsyncTask,它屬於一個內部類,該類本身和外面的 Activity 是有引用關系的,如果 Activity 要銷毀的時候,AsyncTask 還仍然在運行,這會導致 Activity 沒有辦法完全釋放,從而引發內存泄漏。所以說,多線程是提升程序性能的有效手段之一,但是使用多線程卻需要十分謹慎小心,如果不了解背後的執行機制以及使用的注意事項,很可能引起嚴重的問題。
⑵ android AsyncTask的方法在哪幾個線程中調用
在開發Android移動客戶端的時候往往要使用多線程來進行操作,我們通常會將耗時的操作放在單獨的線程執行,避免其佔用主線程而給用戶帶來不好的用戶體驗。但是在子線程中無法去操作主線程(UI 線程),在子線程中操作UI線程會出現錯誤。因此android提供了一個類Handler來在子線程中來更新UI線程,用發消息的機制更新UI界面,呈現給用戶。這樣就解決了子線程更新UI的問題。但是費時的任務操作總會啟動一些匿名的子線程,太多的子線程給系統帶來巨大的負擔,隨之帶來一些性能問題。因此android提供了一個工具類AsyncTask,顧名思義非同步執行任務。這個AsyncTask生來就是處理一些後台的比較耗時的任務,給用戶帶來良好用戶體驗的,從編程的語法上顯得優雅了許多,不再需要子線程和Handler就可以完成非同步操作並且刷新用戶界面。
先大概認識下Android.os.AsyncTask類:
* android的類AsyncTask對線程間通訊進行了包裝,提供了簡易的編程方式來使後台線程和UI線程進行通訊:後台線程執行非同步任務,並把操作結果通知UI線程。
* AsyncTask是抽象類.AsyncTask定義了三種泛型類型 Params,Progress和Result。
* Params 啟動任務執行的輸入參數,比如HTTP請求的URL。
* Progress 後台任務執行的百分比。
* Result 後台執行任務最終返回的結果,比如String,Integer等。
* AsyncTask的執行分為四個步驟,每一步都對應一個回調方法,開發者需要實現這些方法。
* 1) 繼承AsyncTask
* 2) 實現AsyncTask中定義的下面一個或幾個方法
* onPreExecute(), 該方法將在執行實際的後台操作前被UI 線程調用。可以在該方法中做一些准備工作,如在界面上顯示一個進度條,或者一些控制項的實例化,這個方法可以不用實現。
* doInBackground(Params...), 將在onPreExecute 方法執行後馬上執行,該方法運行在後台線程中。這里將主要負責執行那些很耗時的後台處理工作。可以調用 publishProgress方法來更新實時的任務進度。該方法是抽象方法,子類必須實現。
* onProgressUpdate(Progress...),在publishProgress方法被調用後,UI 線程將調用這個方法從而在界面上展示任務的進展情況,例如通過一個進度條進行展示。
* onPostExecute(Result), 在doInBackground 執行完成後,onPostExecute 方法將被UI 線程調用,後台的計算結果將通過該方法傳遞到UI 線程,並且在界面上展示給用戶.
* onCancelled(),在用戶取消線程操作的時候調用。在主線程中調用onCancelled()的時候調用。
為了正確的使用AsyncTask類,以下是幾條必須遵守的准則:
1) Task的實例必須在UI 線程中創建
2) execute方法必須在UI 線程中調用
3) 不要手動的調用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)這幾個方法,需要在UI線程中實例化這個task來調用。
4) 該task只能被執行一次,否則多次調用時將會出現異常
doInBackground方法和onPostExecute的參數必須對應,這兩個參數在AsyncTask聲明的泛型參數列表中指定,第一個為doInBackground接受的參數,第二個為顯示進度的參數,第第三個為doInBackground返回和onPostExecute傳入的參數。
⑶ 如何在Android上編寫高效的java代碼
Java平台一般有三個版本:Java ME(微型版,用於某些手機)、Java SE(標准版,用於台式電腦)、Java EE(企業版,用於伺服器端應用)。在談到Java時,我們通常是指Java SE,因為只有這個版本包含虛擬機和編譯器。
首先,Java代碼會被編譯成稱為位元組碼的中間格式。當位元組碼在目標電腦上運行時,虛擬機會快速將它解析成目標電腦硬體和操作系統所需要的本機格式。
除了為開發者提供「一次編寫,到處運行」的優勢,Java還能通過垃圾回收器(GC)實現自動內存管理,開發者可免去手動在代碼中釋放無用對象的內存。雖然這個功能非常有用,且大大降低了在代碼中引入內存問題的風險,但是它會增加運行時的開銷,因為需要不停地執行垃圾回收進程。
本文開頭將比較Java SE和用於Android開發的Java之間的差異。首先我會介紹開發者習慣的Java
SE語言結構以及它們是如何在Android上運行的。其次,我會介紹如何優化Android中的Java代碼,如何優化內存分配,以及如何恰當地處理多線程。
比較Android上的Dalvik Java和Java SE
雖然遠在Android出現之前,開發者就能用Java編程語言為移動設備編寫應用程序,但它只是Java中功能極為有限的一個版本,稱為Java
ME(微型版)。不同的移動設備還需編寫不同的代碼,因此,寫一個應用程序就能在支持Java
ME的任何手機上運行是幾乎不可能的。此外,由於當時不存在很好的在線商店,應用發布過程極其復雜。
Android的問世為開發者提供了構建智能手機強大應用的機會,開發者只需用Java編程語言以及他們熟知的標准Java
API編寫代碼。然而,盡管Android開發者仍使用Java SE編譯器來編譯應用程序,你會發現,James
Gosling開發的Java和Android設備上的Java存在許多不同之處。
在Android設備上運行的VM(虛擬機)稱為Dalvik。它最初由谷歌的Dan
Bornstein開發,適用於CPU和內存受限的移動設備。Java SE和Dalvik Java存在一些差異,主要體現在虛擬機上。Java
SE使用了棧機設計,而Dalvik被設計成了基於寄存器的機器。Android SDK中有一個dx工具,它會把Java
SE棧機器的位元組碼轉換成基於寄存器的Dalvik機器位元組碼,該轉換步驟由IDE自動完成。
基於棧的虛擬機和基於寄存器的虛擬機的定義以及差異將不列入我們的討論范圍。由於歷史原因,Android使用基於寄存器的虛擬機。雖然基於寄存器的虛擬機最多可以比基於棧的虛擬機快32%,但這只限於執行時解釋位元組碼的虛擬機(也就是說,解釋型虛擬機)。在Android
2.2版本(也稱為Froyo)之前,Dalvik虛擬機都是純解釋型的。Froyo版本引入了JIT編譯器(即時編譯),這是Java
SE很早就有的一個優勢。
JIT編譯,也稱為動態翻譯。它在執行前把位元組碼翻譯成本機代碼(如圖1所示),這樣主要有兩個好處。首先,它消除了那些純解釋型虛擬機的開銷;其次,它能對本機代碼執行優化,這通常是靜態編譯代碼無法做到的。例如,JIT編譯器可以在它運行的CPU上選擇最合適的優化,也可以根據應用程序的輸入來分析代碼是如何運行的,以便進行下一步的優化。
圖1Android Java和Java SE翻譯步驟
雖然Android的Dalvik JIT編譯器有很大的發展前景,但要達到如Java SE的JIT編譯器般穩定、成熟度尚需很長一段時間。不過,Dalvik JIT的出現為Android提供了巨大的性能優勢,而且它也在不斷得以改善。
JAVA
SE虛擬機和Dalvik虛擬機的另一個區別是,後者進行了優化,可運行在同一個機器上的多個實例中。它在開機時會啟動一個叫做zygote的進程,該進程會創建第一個Dalvik實例,由這個實例創建所有其他的實例。當應用程序啟動時,zygote進程會收到一個創建新虛擬機實例的請求,並給該應用程序創建一個新進程(如圖2所示)。如果開發者已習慣於Java
SE開發,這樣的設計可能看起來不切實際,但它有一個很大的優勢,可以避免由一個應用程序運行失敗導致Dalvik虛擬機崩潰,繼而引發多應用程序崩潰。
圖2在Android中啟動新Dalvik虛擬機實例
Android和Java
SE除了運行的虛擬機不同之外,它們實現API的方式也不一樣。Android中屬於java和javax包中的API都來自Apache
Harmony(這是一個開源項目,旨在重新實現Java SE軟體棧,該項目從2011年11月不再維護)。在開發方面,這些API和Java
SE包中的類似,但也存在一些差別。例如,谷歌對HttpUrlConnection類進行了Java SE版本中所沒有的重大升級。
此外,Android平台移除了Java
SE中無關的API。例如,Swing/AWT包被完全移除,因為Android使用不同的UI框架。其他被移除的API還有RMI、CORBA、ImageIO和JMX。它們或者被替換為特定的Android版本(在android包空間內),或者因為一些實際原因根本不存在。
優化Android上的Java代碼
經過多年的改進,Java
SE具備了一些簡化編寫復雜代碼結構的新特性。其中的一些特性會讓整個流程變得更簡單,但開發者需要了解何時以及如何正確地使用它們。另外,由於Java
SE大多用於伺服器端開發(使用Java企業版的API),因而開發人員專門對伺服器端Java代碼進行了優化。註解和Java虛擬機對腳本語言的支持就是對伺服器端開發進行優化的例證。雖然這些工具在構建後端開發時很強大,但在開發Android客戶端代碼時,這些特性的作用很小,甚至起反作用。Java開發者已經習慣於無限量的RAM和CPU,而Android開發需要密切關注性能和內存分配。簡單地說,開發者需要使用稍微不同的方法對待Android和後端的開發。
然而,隨著Android的首次發布,情況有所改變。曾經一些在Android上盡量不用的Java規范重新被推薦,這主要因為Android目前的JIT編譯器解決了這些規范導致的性能問題。
本文將討論編寫Android應用程序需要了解的Java代碼。我們不會深究Java編程語言的細節,而是重點關注對Android開發重要的東西。不過,開發者仍需了解,大多數適用於Java SE的規則和建議同樣適用於Android和Dalvik虛擬機。
Android上的類型安全枚舉
Java SE 5.0新增了許多方便開發者的新特性。其中最值得期待的是引入了類型安全枚舉。枚舉在代碼中用來表示屬於某一組的幾個選擇。在早期版本的Java中,可以用多個整型常量解決這個問題。雖然這在技術上可行,但是很容易出錯。請看下面的代碼:
public class Machine {
public static final int STOPPED = 10;
public static final int INITIALIZING = 20;
public static final int STARTING = 30;
public static final int RUNNING = 40;
public static final int STOPPING = 50;
public static final int CRASHED = 60;
private int mState;
public Machine() {
mState = STOPPED;
}
public int getState() {
return mState;
}
public void setState(int state) {
mState = state;
}
}
問題是,雖然這些常量是期望的,但是沒有機制保證setState()方法接收不同的值。如果要在設置方法中添加檢查,那麼一旦得到的是非預期值,開發者就需要處理錯誤。開發者所需要的是在編譯時檢查非法賦值。類型安全的枚舉解決了這個問題,如下所示:
public class Machine {
public enum State {
STOPPED, INITIALIZING, STARTING, RUNNING, STOPPING, CRASHED
}
private State mState;
public Machine() {
mState = State.STOPPED;
}
public State getState() {
return mState;
}
public void setState(State state) {
mState = state;
}
}
注意在聲明不同類型安全值的地方新加的內部枚舉類。這在編譯時就會解決非法賦值的問題,所以代碼更不容易出錯。
如果Dalvik虛擬機還沒有JIT編譯器優化代碼,不建議在Android平台上使用枚舉類型,因為和使用整型常量相比,這種設計帶來的內存和性能損失更大。這就是為什麼在一些老版本的Android
API中還存在如此多的整型常量的原因。如今有了更強的JIT編譯器以及一個不斷改進的Dalvik虛擬機,開發者不必再擔心這個問題,放心大膽地使用類型安全枚舉即可。
然而,仍然存在一些情況使用整型常量是更好的選擇。像int這樣的Java基本類型,不會增加GC的開銷。此外,Android SDK中許多已有的API仍然依賴基本類型,比如Handler類——在這種情況下,你沒有太多的選擇。
Android中增強版的for循環
Java SE 5.0還引入了增強版的for循環,提供了一個通用的縮寫表達式來遍歷集合和數組。首先,比較以下五種方法:
void loopOne(String[] names) {
int size = names.length;
for (int i = 0; i < size; i++) {
printName(names[i]);
}
}
void loopTwo(String[] names) {
for (String name : names) {
printName(name);
}
}
void loopThree(Collection<String> names) {
for (String name : names) {
printName(name);
}
}
void loopFour(Collection<String> names) {
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
printName(iterator.next());
}
}
// 不要在ArrayList上使用增強版的for循環
void loopFive(ArrayList<String> names) {
int size = names.size();
for (int i = 0; i < size; i++) {
printName(names.get(i));
}
}
上面顯示了四種不同遍歷集合和數組的方式。前面兩種有著相同的性能,所以如果只是讀取元素的話,可以放心地對數組使用增強版for循環。對Collection對象來說,增強版for循環和使用迭代器遍歷元素有著相同的性能。ArrayList對象應避免使用增強版for循環。
如果不僅需要遍歷元素,而且需要元素的位置,就一定要使用數組或者ArrayList,因為所有其他Collection類在這些情況下會更慢。
一般情況下,如果在讀取元素幾乎不變的數據集時對性能要求很高,建議使用常規數組。然而,數組的大小固定,添加數據會影響性能,所以編寫代碼時要考慮所有因素。
隊列、同步和鎖
通常情況下,應用程序會在一個線程中生產數據,在另一個線程中使用它們。常見的例子是在一個線程中獲取網路上的數據,在另一個線程(操作UI的主線程)中把這些數據展現給用戶。這種模式稱為生產者/消費者模式,在面向對象編程課程中,開發者用演算法來實現該模式可能要花上幾個小時。下面會介紹一些簡化生產者/消費者模式實現的現成類。
1. 更智能的隊列
雖然已有現成的類並能用更少的代碼實現該功能,但許多Java開發者仍然選擇使用LinkedList以及同步塊實現隊列功能。開發者可在java.util.concurrent包中找到同步相關的類。此外,本包還包含信號量、鎖以及對單個變數進行原子操作的類。考慮下面使用標準的LinkedList實現線程安全隊列的代碼。
public class ThreadSafeQueue {
private LinkedList<String> mList = new LinkedList<String>();
private final Object mLock = new Object();
public void offer(String value) {
synchronized (mLock) {
mList.offer(value);
mLock.notifyAll();
}
}
public synchronized String poll() {
synchronized (mLock) {
while (mList.isEmpty()) {
try {
mLock.wait();
} catch (InterruptedException e) {
//簡潔起見忽略異常處理
}
}
return mList.poll();
}
}
}
雖然這段代碼是正確的,並有可能在考試中得滿分,但實現和測試這樣一段代碼只是在浪費時間。實際上,所有前面的代碼可用下面一行代替。
LinkedBlockingQueue<String> blockingQueue =
new LinkedBlockingQueue<String>();
上面的一行代碼能像前面的例子一樣提供相同類型的阻塞隊列,甚至能提供額外的線程安全操作。java.util.concurrent包含許多可選的隊列以及並發映射類,所以,一般情況下,建議使用它們,而不是像之前的示例那樣使用更多代碼。
2. 更智能的鎖
Java提供的synchronized關鍵字允許開發者創建線程安全的方法和代碼塊。synchronized關鍵字易於使用,也很容易濫用,對性能造成負面影響。當需要區分讀數據和寫數據時,synchronized關鍵字並不是最有效的。幸好,java.util.concurrent.locks包中的工具類對這種情況提供了很好的支持。
public class ReadWriteLockDemo {
private final ReentrantReadWriteLock mLock;
private String mName;
private int mAge;
private String mAddress;
public ReadWriteLockDemo() {
mLock = new ReentrantReadWriteLock();
}
public void setPersonData(String name, int age, String address) {
ReentrantReadWriteLock.WriteLock writeLock = mLock.writeLock();
try {
writeLock.lock();
mName = name;
mAge = age;
mAddress = address;
} finally {
writeLock.unlock();
}
}
public String getName() {
ReentrantReadWriteLock.ReadLock readLock = mLock.readLock();
try {
readLock.lock();
return mName;
} finally {
readLock.unlock();
}
}
// 重復代碼不再贅述
}
上面的代碼展示了在什麼地方使用ReentrantReadWriteLock,它允許多個並發線程對數據進行只讀訪問,並確保同一時間只有一個線程寫入相同的數據。
在代碼中使用synchronized關鍵字仍然是處理鎖問題的有效方法,但無論何種情況下,都要考慮ReentrantReadWriteLock是否是
⑷ 深入分析Android運行時環境ART:原理、特點與優化策略
ART運行時環境的深入分析如下:
原理: AOT編譯:ART採用AOT編譯方式,即在應用安裝時將位元組碼轉換為機器碼,相較於Dalvik的JIT編譯,AOT能夠顯著提高應用的啟動速度和運行效率。
特點: 高性能:AOT編譯方式使得應用執行速度更快。 優化內存管理:高效的垃圾回收器自動管理內存,降低了內存泄漏的風險。 並發能力提升:多線程和並發能力的增強提高了應用的響應速度。 本地代碼支持:開發者可以利用C/C++代碼進行開發,增加了應用的靈活性。 安全設計:每個應用獨立運行,有效保護了系統的安全。 系統服務與廣播支持:支持事件通知和動態調度,增強了應用的交互性。 Intent與組件機制:支持應用的交互與動態調度,是Android架構的基石。 依賴注入與響應式編程:提高了代碼的可維護性與簡潔度。
優化策略: 冷熱啟動優化:通過優化初始化任務,減少資源消耗,提升啟動速度。 內存優化:精簡應用資源,避免內存泄漏,提高內存使用效率。 代碼混淆與瘦身:通過代碼混淆減小APK體積,同時增強應用的安全性。 增強兼容性:針對不同設備和系統進行調試和優化,確保應用的廣泛兼容性。 性能監控:通過性能監控工具識別和解決性能瓶頸,提升應用性能。 代碼分割:按需載入代碼,節省資源,提高應用的響應速度和用戶體驗。
綜上所述,ART運行時環境通過AOT編譯、高效的內存管理、並發能力提升等特點,顯著提升了Android應用程序的性能和用戶體驗。同時,通過合理的優化策略,可以進一步發揮ART的優勢,滿足高性能、大內存和低延遲需求的應用場景。