Android中的多程式、多執行緒

流浪_歸家發表於2020-05-28

前面幾篇總結了程式、執行緒相關的知識。這裡總結下關於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

 

相關文章