android:process 的坑,你懂吗?

smgd9170 8年前
   <p>许多知识知其然而不知其所以然,这也许就是大神与菜鸟的区别吧。</p>    <p>最近排查问题时发现一个问题: 一个在 Application 中启动的定时任务在运行时会被调用多次,诡异的很,最后发现是一个前人留下的坑,原因就是对 android:process 不知其所以然造成的。</p>    <h2>android:process 属性</h2>    <p>关于 android:process 属性,相信大家都不陌生,android 官网是这样说明的 :</p>    <blockquote>     <p>默认情况下,同一应用的所有组件均在相同的进程中运行,且大多数应用都不会改变这一点。 但是,如果您发现需要控制某个组件所属的进程,则可在清单文件中执行此操作。</p>    </blockquote>    <blockquote>     <p>各类组件元素的清单文件条目—<activity>、<service>、<receiver> 和 <provider>—均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。您可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,您还可以设置 android:process,使不同应用的组件在相同的进程中运行,但前提是这些应用共享相同的 Linux 用户 ID 并使用相同的证书进行签署。</p>    </blockquote>    <blockquote>     <p>此外, 元素还支持 android:process 属性,以设置适用于所有组件的默认值。</p>    </blockquote>    <blockquote>     <p>如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android 可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 当这些组件需要再次运行时,系统将为它们重启进程。</p>    </blockquote>    <blockquote>     <p>决定终止哪个进程时,Android 系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 进程。 因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 下面,我们介绍决定终止进程所用的规则。</p>    </blockquote>    <p>在需要使用到新进程时,可以使用 android:process 属性,如果被设置的进程名是以一个冒号开头的,则这个新的进程对于这个应用来说是私有的,当它被需要或者这个服务需要在新进程中运行的时候,这个新进程将会被创建。如果这个进程的名字是以字符开头,并且符合 android 包名规范(如 com.roger 等),则这个服务将运行在一个以这个名字命名的全局的进程中,当然前提是它有相应的权限。若以数字开头(如 1Remote.com ),或不符合 android 包名规范(如 Remote),则在编译时将会报错 ( INSTALL_PARSE_FAILED_MANIFEST_MALFORMED )。新建进程将允许在不同应用中的各种组件可以共享一个进程,从而减少资源的占用。具体可以参考博客:<a href="/misc/goto?guid=4959671239420249064">apk,task,android:process与android:sharedUserId的区别</a></p>    <p>重点来了,因为设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个线程中也将新建一个 Application 的实例。<strong>因此,每新建一个进程 Application 的 onCreate 都将被调用一次。</strong> 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。</p>    <p>让我们到 Framework 中看看新建进程的逻辑,请打开老罗的博客 : <a href="/misc/goto?guid=4959671239491475883">Android系统在新进程中启动自定义服务过程(startService)的原理分析</a></p>    <p>详细介绍了新进程启动的过程,其中我们重点看到 <code>Step 17. ActivityThread.handleCreateService</code>中</p>    <pre>  <code class="language-java">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);                }            }        }          ......      }    </code></pre>    <p>看到这行 <code>Application app = packageInfo.makeApplication(false, mInstrumentation);</code> 在这里创建了 Application 。</p>    <h2>解决方案</h2>    <p>获取当前运行进程的名称:</p>    <h3>方案1</h3>    <pre>  <code class="language-java">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;    }    </code></pre>    <p>目前网上主流的方法,但效率没有方案2高,感谢由王燚同学提供的方案2</p>    <h3>方案2</h3>    <pre>  <code class="language-java">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;    }  }  </code></pre>    <p>然后在 Application 的 onCreate 中获取进程名称并进行相应的判断,例如:</p>    <pre>  <code class="language-java">String processName = getProcessName(this, android.os.Process.myPid());    if (!TextUtils.isEmpty(processName) && processName.equals(this.getPackageName())) {//判断进程名,保证只有主进程运行   //主进程初始化逻辑   ....  }  </code></pre>    <h2>总结</h2>    <p>知其然还需知其所以然,这才是总结并提高的法宝。希望能帮到有需要的同学 :)</p>    <p>Have a good day ~</p>    <h3>参考</h3>    <p><a href="/misc/goto?guid=4959671239578163250">http://blog.csdn.net/jason0539/article/details/45555671</a></p>    <p><a href="/misc/goto?guid=4959671239491475883">Android系统在新进程中启动自定义服务过程(startService)的原理分析</a></p>    <p><a href="/misc/goto?guid=4959671239420249064">apk,task,android:process与android:sharedUserId的区别</a></p>    <p>来源:https://www.rogerblog.cn/2016/03/17/android-proess/</p>