1. 程式人生 > >android:process 的坑,你懂嗎?

android:process 的坑,你懂嗎?

許多知識知其然而不知其所以然,這也許就是大神與菜鳥的區別吧。

最近排查問題時發現一個問題: 一個在 Application 中啟動的定時任務在執行時會被呼叫多次,詭異的很,最後發現是一個前人留下的坑,原因就是對 android:process 不知其所以然造成的。

android:process 屬性

關於 android:process 屬性,相信大家都不陌生,android 官網是這樣說明的 :

預設情況下,同一應用的所有元件均在相同的程序中執行,且大多數應用都不會改變這一點。 但是,如果您發現需要控制某個元件所屬的程序,則可在清單檔案中執行此操作。

各類元件元素的清單檔案條目—<activity>、<service>、<receiver> 和 <provider>—均支援 android:process 屬性,此屬性可以指定該元件應在哪個程序執行。您可以設定此屬性,使每個元件均在各自的程序中執行,或者使一些元件共享一個程序,而其他元件則不共享。 此外,您還可以設定 android:process,使不同應用的元件在相同的程序中執行,但前提是這些應用共享相同的 Linux 使用者 ID 並使用相同的證書進行簽署。

此外, 元素還支援 android:process 屬性,以設定適用於所有元件的預設值。

如果記憶體不足,而其他為使用者提供更緊急服務的程序又需要記憶體時,Android 可能會決定在某一時刻關閉某一程序。在被終止程序中執行的應用元件也會隨之銷燬。 當這些元件需要再次執行時,系統將為它們重啟程序。

決定終止哪個程序時,Android 系統將權衡它們對使用者的相對重要程度。例如,相對於託管可見 Activity 的程序而言,它更有可能關閉託管螢幕上不再可見的 Activity 程序。 因此,是否終止某個程序的決定取決於該程序中所執行元件的狀態。 下面,我們介紹決定終止程序所用的規則。

在需要使用到新程序時,可以使用 android:process 屬性,如果被設定的程序名是以一個冒號開頭的,則這個新的程序對於這個應用來說是私有的,當它被需要或者這個服務需要在新程序中執行的時候,這個新程序將會被建立。如果這個程序的名字是以字元開頭,並且符合 android 包名規範(如 com.roger 等),則這個服務將執行在一個以這個名字命名的全域性的程序中,當然前提是它有相應的許可權。若以數字開頭(如 1Remote.com ),或不符合 android 包名規範(如 Remote),則在編譯時將會報錯 ( INSTALL_PARSE_FAILED_MANIFEST_MALFORMED )。新建程序將允許在不同應用中的各種元件可以共享一個程序,從而減少資源的佔用。具體可以參考部落格:

apk,task,android:process與android:sharedUserId的區別

重點來了,因為設定了 android:process 屬性將元件執行到另一個程序,相當於另一個應用程式,所以在另一個執行緒中也將新建一個 Application 的例項。因此,每新建一個程序 Application 的 onCreate 都將被呼叫一次。 如果在 Application 的 onCreate 中有許多初始化工作並且需要根據程序來區分的,那就需要特別注意了。

詳細介紹了新程序啟動的過程,其中我們重點看到 Step 17. ActivityThread.handleCreateService

public final class ActivityThread {  

    ......  

    private final void handleCreateService(CreateServiceData data) {  
        // If we are getting ready to gc after going to the background, well  
        // we are back active so skip it.  
        unscheduleGcIdler();  

        LoadedApk packageInfo = getPackageInfoNoCheck(  
            data.info.applicationInfo);  
        Service service = null;  
        try {  
            java.lang.ClassLoader cl = packageInfo.getClassLoader();  
            service = (Service) cl.loadClass(data.info.name).newInstance();  
        } catch (Exception e) {  
            if (!mInstrumentation.onException(service, e)) {  
                throw new RuntimeException(  
                    "Unable to instantiate service " + data.info.name  
                    + ": " + e.toString(), e);  
            }  
        }  

        try {  
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);  

            ContextImpl context = new ContextImpl();  
            context.init(packageInfo, null, this);  

            Application app = packageInfo.makeApplication(false, mInstrumentation);  
            context.setOuterContext(service);  
            service.attach(context, this, data.info.name, data.token, app,  
                ActivityManagerNative.getDefault());  
            service.onCreate();  
            mServices.put(data.token, service);  
            try {  
                ActivityManagerNative.getDefault().serviceDoneExecuting(  
                    data.token, 0, 0, 0);  
            } catch (RemoteException e) {  
                // nothing to do.  
            }  

        } catch (Exception e) {  
            if (!mInstrumentation.onException(service, e)) {  
                throw new RuntimeException(  
                    "Unable to create service " + data.info.name  
                   	 + ": " + e.toString(), e);  
            }  
        }  
    }  

    ......  

}  

看到這行 Application app = packageInfo.makeApplication(false, mInstrumentation); 在這裡建立了 Application 。

解決方案

獲取當前執行程序的名稱:

方案1

public static String getProcessName(Context cxt, int pid) {  
    ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);  
    List<RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();  
    if (runningApps == null) {  
        return null;  
    }  
    for (RunningAppProcessInfo procInfo : runningApps) {  
        if (procInfo.pid == pid) {  
            return procInfo.processName;  
        }  
    }  
    return null;  
}  

目前網上主流的方法,但效率沒有方案2高,感謝由王燚同學提供的方案2

方案2

public static String getProcessName() {
  try {
    File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
    BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
    String processName = mBufferedReader.readLine().trim();
    mBufferedReader.close();
    return processName;
  } catch (Exception e) {
    e.printStackTrace();
    return null;
  }
}

然後在 Application 的 onCreate 中獲取程序名稱並進行相應的判斷,例如:

String processName = getProcessName(this, android.os.Process.myPid());

if (!TextUtils.isEmpty(processName) && processName.equals(this.getPackageName())) {//判斷程序名,保證只有主程序執行
	//主程序初始化邏輯
	....
}

總結

知其然還需知其所以然,這才是總結並提高的法寶。希望能幫到有需要的同學 :)

Have a good day ~

參考

來源:https://www.rogerblog.cn/2016/03/17/android-proess/