前面幾篇總結了進程、線程相關的知識。這里總結下關於Android中的多進程、多線程及其使用。
這里總結的Android中的多進程、多線程也是一個基礎,可擴展的很多。
Android中多進程
常見的幾種使用
Runtime.getRuntime().exec("xxx")
這個方法,調用程序外的 腳本或命令程序,它會生成一個新的進程去調用 返回一個Process對象。
如:windows下,調用記事本。
Runtime.getRuntime().exec("notepad.exe");
linux下(Android)下,調用系統本身的ps命令后,通過返回的Process對象的輸入流 讀取了調用內容。
try { String[] commandStr = new String[] {"/bin/sh","-c", "ps -ef"}; //String commandStr = "/bin/sh -c ps"; Process process1 = Runtime.getRuntime().exec( commandStr ); Log.d( TAG, "onCreate: process1=" + process1 ); byte [] data = new byte[1024]; int len = 0; while( -1 != (len = process1.getInputStream().read(data)) ) { String str = new String(data, 0, len , "UTF-8"); Log.d( TAG, "onCreate: \n" + str ); } } catch (IOException e) { e.printStackTrace(); }
執行后,通過log,很容易看出,Runtime.getRuntime().exec("xxx")調用就是新建的進程。
ProcessBuilder("xxx").start()
這個方法同樣可以調用程序外的 腳本或命令程序。
類似Runtime.getRuntime().exec(),如:window下打開計算器。
new ProcessBuilder("calc.exe").start();
llinux:
try { String[] commandStr = new String[] {"/bin/sh","-c", "ps -ef"}; ProcessBuilder processBuilder = new ProcessBuilder(commandStr); processBuilder.redirectErrorStream( true ); Process process2 = processBuilder.start(); Log.d( TAG, "onCreate: process2=" + process2 + ";processBuilder.directory="+processBuilder.directory()); byte [] data = new byte[1024]; int len = 0; while( -1 != (len = process2.getInputStream().read(data)) ) { String str = new String(data, 0, len , "UTF-8"); Log.d( TAG, "onCreate: \n" + str ); } } catch (IOException e) { e.printStackTrace(); }
注:
Runtime.getRuntime().exec(param)和 ProcessBuilder(param).start()關聯
通過源碼跟蹤,很容易看到Runtime.getRuntime().exec()最終也是調用的ProcessBuilder().start()來實現的。
所以它們很多類似,都是創建一個新的進程來執行程序外的腳本或命令程序,並返回Process實例,通過實例可以獲取進程信息及控制進程狀態。
傳遞參數有所不同,Runtime.getRuntime().exec()可以是單獨字符串(用空格分隔可執行命令程序和參數,如例子中注釋的那條),也可以是字符串數組。ProcessBuilder().start()只能是字符串數組或集合,同樣第一個參數是可執行命令程序。
android:process標簽
應用的AndroidManifest.xml的清單文件中,可以通過設置android:process標簽 實現多進程。
默認,同一應用所有組件運行相同進程中,進程名即包名。
支持
四大組件(Activity,Service,ContentProvider,BroadcastReceive)都支持android:process標簽,組件元素-(<activity>、<service>、<receiver> 和 <provider>),通過此屬性指定組件在哪個進程中運行。
<application>也支持android:process標簽,它只設置所有組件默認運行進程的默認值。
進程名(屬性值)
如果以冒號(“:”)開頭,創建的 則是應用的私有進程。
如配置android:process=":myprocess1",包名是com.android.test,則實際進程名即com.android.test:myprocess1。
如果不是以冒號而是以小寫字母開頭,則是全局進程,只要有權限即可訪問。不同應用的組件可以共享該進程。
其他
進程內存限制
如下兩個重要的值,如果超過,則會出現OOM。通過 adb shell getprop | grep xxx(如adb shell getprop | grep dalvik可以看到很多配置,不僅僅下面兩個 )可以查詢到相應手機中的配置,不同手機可能是不同的。
dalvik.vm.heapsize ---單個dalvik虛擬機最大內存
dalvik.vm.heapgrowthlimit ---單個進程的最大內存
Android中進程間通信
進程間通信總結中提到比較全面,可以參考下。//TODO
系統中也存在很多包括Activity,Service,Broadcast,ContentProvider都有這樣的實現。
比較常用,需要了解和掌握的:Bundle(序列化,四大組件常用),AIDL,Messenger,ContentProvider,Socket
Android中多線程
主線程
當應用啟動后,系統即會為應用創建一個線程---“main”,即主線程。主線程,也稱為UI線程,它負責事件的分派,包括繪制事件。
一般所有組件都在主線程中實例化。
當耗時操作(如網絡訪問或數據庫操作)時就會阻塞主線程,會導致事件無法分派,包括繪制事件,甚至5s ANR。
工作線程
保證應用界面的響應能力,關鍵是不能阻塞界面線程。如果執行的操作不能即時完成,則應確保它們在單獨的線程中運行。這個單獨的線程即工作線程。
注意:
1.不要阻塞主線程,即耗時操作不要放在主線程中。
2.只有主線程可以更新UI,其他所有線程都無法更新UI。
從其他線程進入主線
由於第二點,系統提供了從其他線程進入主線程的幾種方法:
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
例如
public void onClick(View v) { new Thread(new Runnable() { public void run() { // a potentially time consuming task final Bitmap bitmap = processBitMap("image.png"); imageView.post(new Runnable() { public void run() { imageView.setImageBitmap(bitmap); } }); } }).start(); }
但是,隨着操作日趨復雜,這類代碼也會變得復雜且難以維護。
解決://TODO
1.若通過工作線程完成復雜交互,考慮在工作線程中使用Handler處理來自主線程的消息。
2.擴展AsyncTask類。AsyncTask通過異步通信和消息傳遞,將工作線程中的結果傳遞到主線程,更新相關UI操作。
線程安全
先看下面的例子,onCreate中開啟了10個線程,調用plusOne方法,對num進行加1處理。
private static final String TAG = "ProcessThread"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate( savedInstanceState ); for (int i = 0; i < 5; i++) { new Thread_thread().start(); new Thread( runnable ).start(); } } int num = 0; //繼承Thread類 private class Thread_thread extends Thread { @Override public void run() { plusOne(); } } //實現Runnable接口 private Runnable runnable = new Runnable() { @Override public void run() { plusOne(); } }; private void plusOne() { num++; Log.d( TAG, "num=" + num ); }
打印的log如下:
2020-05-28 10:58:12.731 9531-9567/com.flx.process_thread D/ProcessThread: num=1 2020-05-28 10:58:12.741 9531-9568/com.flx.process_thread D/ProcessThread: num=3 2020-05-28 10:58:12.741 9531-9570/com.flx.process_thread D/ProcessThread: num=3 2020-05-28 10:58:12.744 9531-9569/com.flx.process_thread D/ProcessThread: num=4 2020-05-28 10:58:12.761 9531-9571/com.flx.process_thread D/ProcessThread: num=5 2020-05-28 10:58:12.762 9531-9572/com.flx.process_thread D/ProcessThread: num=6 2020-05-28 10:58:12.787 9531-9574/com.flx.process_thread D/ProcessThread: num=7 2020-05-28 10:58:12.791 9531-9573/com.flx.process_thread D/ProcessThread: num=8 2020-05-28 10:58:12.801 9531-9575/com.flx.process_thread D/ProcessThread: num=9 2020-05-28 10:58:12.802 9531-9576/com.flx.process_thread D/ProcessThread: num=10
從log看到num值存在重復。像上述的情況,可能是兩個線程同時操作了num,操作時num都是一樣的。這種情況就是線程不安全的。
線程安全就是,多個線程訪問同一個對象時,如果不用考慮這些線程在運行時環境下的調度和交替執行,也不需要進行額外的同步,或者在調用方進行任何其他操作,調用這個對象的行為都可以獲得正確的結果,那么這個對象就是線程安全的。
上述例子中,分別使用了創建線程常用的兩種方法:
繼承Thread類 和 實現Runnable接口
實現線程安全常用方法
synchronized關鍵字
如下,應該都很熟悉,不做解釋。
private synchronized void plusOne() { num++; Log.d( TAG, "num=" + num ); }
Lock鎖
ReentrantLock是Lock的一個子類。
如下示例,一般使用,unlock放在finally中,執行方法體放在try中。
private final ReentrantLock lock = new ReentrantLock(); private void plusOne() { lock.lock(); try { num++; Log.d( TAG, "num=" + num ); }finally { lock.unlock(); } }
注:
volatile關鍵字
volatile保證了不同線程對某個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
volatile不能保證原子性,因此不能保證線程安全。
常用進程線程信息獲取
//當前進程ID,用戶ID Log.d( TAG, "onCreate: currPID=" + android.os.Process.myPid() + ";currUID=" + android.os.Process.myUid() ); //當前線程ID。下面兩種方法獲取的值是不一樣的。 //第一個是系統級的,系統分配管理;第二個是java級的,Thread管理的。由於java跨平台 Log.d( TAG, "onCreate: currTid=" + android.os.Process.myTid() + ";currTid2=" + Thread.currentThread().getId() ); //主線程ID Log.d( TAG, "onCreate: mainTid=" + Looper.getMainLooper().getThread().getId() );
打印情況:
2020-05-28 10:58:12.703 9531-9531/com.flx.process_thread D/ProcessThread: onCreate: currPID=9531;currUID=10133 2020-05-28 10:58:12.704 9531-9531/com.flx.process_thread D/ProcessThread: onCreate: currTid=9531;currTid2=2 2020-05-28 10:58:12.704 9531-9531/com.flx.process_thread D/ProcessThread: onCreate: mainTid=2