Android N Phone进程启动流程

本流程图基于MTK平台 Android 7.0,本流程只作为沟通学习使用

整体流程图

这里写图片描述

部分关键点说明

persistent属性

以下来自官网介绍:https://developer.android.com/guide/topics/manifest/application-element.html

android:persistent
Whether or not the application should remain running at all times"true" if it should, and "false" if not. The default value is "false". Applications should not normally set this flag; persistence mode is intended only for certain system applications.

从字面上理解就是常驻进程,当这个属性为true时,该进程应该在系统启动之后由AMS启动,那这个属性值和我们的Phone进程有什么关系呢?

我们知道Phone进程的全称应该是: package=”com.android.phone” ,我们可以在 service 目录下的 Telephony 模块的 AndroidManifest 文件中看到他的定义,这个文件里面有一个名为“PhoneApp”的application,它的persistent的属性为: android:persistent=”true” ,我们通过上面的流程图可以知道,PhoneApp 通过它的 onCreate 方法,创建了PhoneGlobals,PhoneGlobals 则创建了我们的 phone,所以我们的 Phone 进程也是通过 persistent 属性来启动的。

启动流程部分关键方法

遍历所有persistent为true的进程

//ActivityManagerService.java
    private void startPersistentApps(int matchFlags) {
        if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;

        synchronized (this) {
            try {
                final List<ApplicationInfo> apps = AppGlobals.getPackageManager()
                        .getPersistentApplications(STOCK_PM_FLAGS | matchFlags).getList(); //得到所有persistent为true的进程信息
                for (ApplicationInfo app : apps) {
                    if (!"android".equals(app.packageName)) {
                        addAppLocked(app, false, null /* ABI override */);//逐一启动进程
                    }
                }
            } catch (RemoteException ex) {
            }
        }
    }

具体获取所有persistent为true的进程信息

//PackageManagerService.java
    private @NonNull List<ApplicationInfo> getPersistentApplicationsInternal(int flags) {
        final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>();

        // reader
        synchronized (mPackages) {
            final Iterator<PackageParser.Package> i = mPackages.values().iterator(); //拿到所有的package
            final int userId = UserHandle.getCallingUserId();
            while (i.hasNext()) {  //遍历所有的package
                final PackageParser.Package p = i.next();
                if (p.applicationInfo == null) continue;

                final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
                        && !p.applicationInfo.isDirectBootAware();
                final boolean matchesAware = ((flags & MATCH_DIRECT_BOOT_AWARE) != 0)  //根据传入的flags赋值
                        && p.applicationInfo.isDirectBootAware();

                if ((p.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0
                        && (!mSafeMode || isSystemApp(p))
                        && (matchesUnaware || matchesAware)) {
                    PackageSetting ps = mSettings.mPackages.get(p.packageName);
                    if (ps != null) {
                        ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags, //拿到对应package 的信息
                                ps.readUserState(userId), userId);
                        if (ai != null) {
                            /// M: Add phone package at front
                            if (p.packageName.equals("com.android.phone")) { //MTK加的把phone进程放到第一个,加快启动
                                finalList.add(0, ai);
                            } else {
                                finalList.add(ai);
                            }
                        }
                    }
                }
            }
        }
        return finalList;
    }

通过 zygote 机制创建进程

//Process.java

    /**
     * Starts a new process via the zygote mechanism.
     *
     * @param processClass Class name whose static main() to run
     * @param niceName 'nice' process name to appear in ps
     * @param uid a POSIX uid that the new process should setuid() to
     * @param gid a POSIX gid that the new process shuold setgid() to
     * @param gids null-ok; a list of supplementary group IDs that the
     * new process should setgroup() to.
     * @param debugFlags Additional flags.
     * @param targetSdkVersion The target SDK version for the app.
     * @param seInfo null-ok SELinux information for the new process.
     * @param abi the ABI the process should use.
     * @param instructionSet null-ok the instruction set to use.
     * @param appDataDir null-ok the data directory of the app.
     * @param extraArgs Additional arguments to supply to the zygote process.
     * @return An object that describes the result of the attempt to start the process.
     * @throws ZygoteStartFailedEx if process start failed for any reason
     */
    private static ProcessStartResult startViaZygote(final String processClass,
                                  final String niceName,
                                  final int uid, final int gid,
                                  final int[] gids,
                                  int debugFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String[] extraArgs)
                                  throws ZygoteStartFailedEx {
        synchronized(Process.class) {
            ArrayList<String> argsForZygote = new ArrayList<String>();

            // --runtime-args, --setuid=, --setgid=,
            // and --setgroups= must go first
            argsForZygote.add("--runtime-args");
            argsForZygote.add("--setuid=" + uid);
            argsForZygote.add("--setgid=" + gid);
            if ((debugFlags & Zygote.DEBUG_ENABLE_JNI_LOGGING) != 0) {
                argsForZygote.add("--enable-jni-logging");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_SAFEMODE) != 0) {
                argsForZygote.add("--enable-safemode");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
                argsForZygote.add("--enable-debugger");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_CHECKJNI) != 0) {
                argsForZygote.add("--enable-checkjni");
            }
            if ((debugFlags & Zygote.DEBUG_GENERATE_DEBUG_INFO) != 0) {
                argsForZygote.add("--generate-debug-info");
            }
            if ((debugFlags & Zygote.DEBUG_ALWAYS_JIT) != 0) {
                argsForZygote.add("--always-jit");
            }
            if ((debugFlags & Zygote.DEBUG_NATIVE_DEBUGGABLE) != 0) {
                argsForZygote.add("--native-debuggable");
            }
            if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
                argsForZygote.add("--enable-assert");
            }
            if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
                argsForZygote.add("--mount-external-default");
            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
                argsForZygote.add("--mount-external-read");
            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
                argsForZygote.add("--mount-external-write");
            }
            argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

            //TODO optionally enable debuger
            //argsForZygote.add("--enable-debugger");

            // --setgroups is a comma-separated list
            if (gids != null && gids.length > 0) {
                StringBuilder sb = new StringBuilder();
                sb.append("--setgroups=");

                int sz = gids.length;
                for (int i = 0; i < sz; i++) {
                    if (i != 0) {
                        sb.append(',');
                    }
                    sb.append(gids[i]);
                }

                argsForZygote.add(sb.toString());
            }

            if (niceName != null) {
                argsForZygote.add("--nice-name=" + niceName);
            }

            if (seInfo != null) {
                argsForZygote.add("--seinfo=" + seInfo);
            }

            if (instructionSet != null) {
                argsForZygote.add("--instruction-set=" + instructionSet);
            }

            if (appDataDir != null) {
                argsForZygote.add("--app-data-dir=" + appDataDir);
            }

            argsForZygote.add(processClass);

            if (extraArgs != null) {
                for (String arg : extraArgs) {
                    argsForZygote.add(arg);
                }
            }

            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }

和AMS建立通信

//ActivityThread.java
 private void attach(boolean system) {
        sCurrentActivityThread = this;
        mSystemThread = system;
        if (!system) {
            ViewRootImpl.addFirstDrawHandler(new Runnable() {
                @Override
                public void run() {
                    ensureJitEnabled();
                }
            });
            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                    UserHandle.myUserId());
            RuntimeInit.setApplicationObject(mAppThread.asBinder());//传入当前进程主线程的binder
            final IActivityManager mgr = ActivityManagerNative.getDefault();
            try {
                mgr.attachApplication(mAppThread);//和AMS通过binder建立通信
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
            //.............省略部分代码
    }

执行对应application的OnCreate方法

//ActivityThread.java
private void handleBindApplication(AppBindData data) {
        // Register the UI Thread as a sensitive thread to the runtime.
        //......省略部分代码
            try {
                mInstrumentation.callApplicationOnCreate(app);//执行application的OnCreate方法
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }
        } finally {
            StrictMode.setThreadPolicy(savedPolicy);
        }
    }

上面主要介绍了从AMS到PhoneApp的流程,具体介绍了一下系统是如何启动persistent为true的进程的,后续会介绍Phone进程启动过程中的一些关键类和它们的作用。

<think> 我们正在处理一个系统级问题:Phone进程的binder线程耗尽导致System ANR并重启。根据引用[1],这是一个发生在Phone进程中的问题,表现为binder_thread耗尽,进而引发Watchdog检测到超时并重启系统。 引用[2]提供了一些通用优化建议,但我们需要针对binder耗尽的特定问题。 解决方案思路: 1. 分析问题原因:binder线程耗尽通常是因为有太多的binder调用同时发生,或者某些binder调用被阻塞(例如,在binder线程中执行了耗时操作),导致所有binder线程都被占用,新的binder请求无法得到处理,从而引发ANR甚至系统重启。 2. 针对Phone进程,我们需要重点关注与电话相关的服务(如Telephony服务)和广播接收器(因为广播处理通常通过binder进行IPC)。 3. 根据引用[1]中的日志线索,Watchdog报告可能是由于Phone进程中的一个或多个binder线程被长时间阻塞,导致系统无法响应。 具体解决步骤: 步骤1:优化Phone进程中的binder调用 - 避免在binder接口的实现方法中执行耗时操作(如数据库操作、网络请求、复杂计算等)。这些操作应该移出binder线程,放到后台线程中执行。 - 检查Phone进程中实现的AIDL接口,确保它们能够快速返回。如果有耗时任务,使用异步模式:立即返回一个回调接口,然后通过回调通知结果。 步骤2:减少不必要的广播和处理 - Phone进程会注册很多与电话状态相关的广播(如来电、去电、信号变化等)。检查这些广播接收器的onReceive方法,确保它们不会执行耗时操作。如果有,使用IntentService或HandlerThread来异步处理。 - 优化广播的发送频率。例如,避免在信号强度变化时过于频繁地发送广播(可以考虑使用阈值或延迟发送)。 步骤3:增加binder线程池大小(临时解决方案) - 在系统级别,可以尝试增加binder线程池的大小。但这需要修改系统属性,通常需要root权限或者定制ROM。在设备上可以通过以下命令临时修改(重启后失效): ```shell adb shell setprop debug.binder.threads 32 ``` 注意:这只是一个临时缓解措施,并不能从根本上解决问题。而且,增加线程池大小可能会增加系统开销。 步骤4:检查死锁或长时间阻塞 - 通过分析ANR日志(如traces.txt)来定位阻塞的线程。通常,在发生ANR时,系统会生成一个traces文件,其中包含所有线程的堆栈。 - 在日志中搜索“binder”线程,查看它们的状态(如BLOCKED、WAITING)和堆栈,找出阻塞的位置。特别注意Phone进程中那些持有锁或等待资源的binder线程。 步骤5:监控binder线程状态 - 在开发或测试阶段,可以使用以下命令监控binder线程: ```shell adb shell dumpsys binder --stats adb shell dumpsys binder -p <Phone进程的PID> ``` 这些命令可以显示binder线程的数量以及它们的状态(空闲、忙碌等)。 步骤6:内存优化 - 引用[2]提到内存不够用可能会导致问题。因此,需要检查Phone进程是否存在内存泄漏,导致GC频繁,进而使得线程暂停时间变长,加剧binder线程的阻塞。可以通过内存分析工具(如Android Studio Profiler)来监控并优化内存。 步骤7:异步处理 - 对于Phone进程中的关键服务(如Telecom服务),确保它们使用异步处理模型。例如,使用Handler、AsyncTask或者线程池来执行后台任务。 步骤8:避免同步跨进程调用 - 在binder线程中,避免进行跨进程的同步调用(尤其是调用其他进程的服务),因为这可能会导致死锁或长时间等待。如果必须调用,设置超时或者使用异步调用。 步骤9:更新和补丁 - 检查是否有相关的系统补丁或更新,因为binder线程耗尽问题可能是系统级别的bug,厂商可能已经提供了修复。 示例解决方案代码片段(以广播接收器为例): 假设Phone进程中有一个广播接收器用于处理来电,原始代码如下: ```java public class InCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 这是一个耗时操作,比如查询数据库 processCall(context); } private void processCall(Context context) { // 模拟耗时操作 SystemClock.sleep(10000); } } ``` 修改为异步处理: ```java public class InCallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 使用异步任务处理,避免阻塞广播接收器线程(即主线程,但这里实际是binder线程?) // 注意:广播接收器的onReceive是在主线程中执行,但如果是通过IPC发送的广播,则可能由binder线程调用。 // 为了安全,我们将其放到后台线程。 new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... voids) { processCall(context); return null; } }.execute(); } private void processCall(Context context) { // 耗时操作 } } ``` 注意事项:在Android中,广播接收器的onReceive默认在主线程执行,但如果是通过Context.sendBroadcast()发送的广播,则由主线程处理;如果是通过sendOrderedBroadcast()并且设置了优先级,则可能由binder线程处理。因此,不能阻塞onReceive。 综上所述,解决Phone进程binder耗尽问题的核心是:减少binder线程的占用时间,避免阻塞,将耗时操作移至后台线程,并优化系统资源使用。 相关问题: 1. 如何诊断Android系统中的binder线程耗尽问题? 2. Phone进程中有哪些常见的导致binder线程阻塞的场景? 3. 如何增加Android系统的binder线程池大小? </think> ### 解决Phone进程Binder耗尽问题的方法 针对Phone进程(如`com.android.phone`)的Binder线程耗尽导致System ANR并重启的问题,结合引用[1]的案例和系统特性,提供以下解决方案: --- #### 1. **定位阻塞源(首要步骤)** - **获取ANR日志**: ```shell adb pull /data/anr/traces.txt ``` 在日志中搜索 `binder_thread`、`com.android.phone` 和 `Watchdog` 关键词,定位阻塞的Binder调用栈[^1]。 - **监控实时状态**: ```shell adb shell dumpsys binder | grep "com.android.phone" -A 20 ``` 检查Phone进程的Binder线程数(如 `active threads: 16/16`)和阻塞队列长度(`pending transactions`)。 --- #### 2. **优化Phone进程关键路径** - **避免同步跨进程调用**: - Phone进程的Telephony服务(如 `ITelephonyRegistry`)需避免在Binder线程中同步调用其他进程服务(如短信/网络模块),改用异步回调机制。 - **示例改造**: ```java // 改造前:同步调用 void updateCallState() { someService.blockingCall(); // 风险点 } // 改造后:异步回调 void updateCallState() { executor.execute(() -> { SomeResult res = someService.asyncCall(); handler.post(() -> handleResult(res)); }); } ``` - **减少广播风暴**: - 优化信号强度更新(`SIGNAL_STRENGTH_CHANGED`)、通话状态(`ACTION_PHONE_STATE_CHANGED`)等高频广播: ```java // 添加阈值过滤避免频繁发送 if (Math.abs(newStrength - lastStrength) > THRESHOLD) { sendBroadcast(intent); } ``` --- #### 3. **隔离耗时操作** - **使用专用线程池**: 为Phone进程创建独立的 `ThreadPoolExecutor` 处理数据库/I/O操作(如联系人查询),防止占用Binder线程: ```java private static final ExecutorService phoneExecutor = Executors.newFixedThreadPool(4); // 隔离资源 ``` - **限制并发任务**: 使用 `Semaphore` 控制同时进行的跨进程调用数量: ```java private final Semaphore binderSemaphore = new Semaphore(8); // 最大并发数 void safeBinderCall() { if (binderSemaphore.tryAcquire()) { try { /* ... */ } finally { binderSemaphore.release(); } } } ``` --- #### 4. **系统级调优** - **临时增加Binder线程数**(需root): ```shell adb shell setprop debug.binder.threads 32 ``` 修改后重启 `com.android.phone` 进程: ```shell adb shell am restart com.android.phone ``` - **内存优化**: 在 `AndroidManifest.xml` 中为Phone进程启用大堆内存,缓解GC压力导致的阻塞: ```xml <application android:process="com.android.phone" android:largeHeap="true"> ``` --- #### 5. **防御性监控** - **添加Binder健康检查**: 在Phone进程中定期输出Binder状态: ```java new Handler().postDelayed(() -> { Log.d("BinderMonitor", "Phone Binder threads: " + Binder.getThreadPoolStats()); }, 30000); // 每30秒检测 ``` - **Watchdog超时优化**: 拦截 `Watchdog` 报告(如引用[1]的日志),缩短关键服务的超时阈值: ```java Watchdog.getInstance().addMonitor(new PhoneServiceMonitor(), 5000); // 5秒超时 ``` --- ### 验证流程 1. **复现问题**: ```shell adb shell am broadcast -a android.intent.action.PHONE_STATE -n com.android.phone/.PhoneGlobals ``` 高频触发电话状态广播模拟压力场景。 2. **监控指标**: - Binder线程利用率:`adb shell dumpsys binder | grep "active threads"` - CPU阻塞状态:`adb shell top -H -p $(pidof com.android.phone)` --- ### 关键预防策略 | 措施 | 预期效果 | 风险场景 | |---------------------|----------------------------------|-----------------------| | 异步化跨进程调用 | 避免Binder线程同步阻塞 | 电话状态同步网络数据 | | 广播发送频率控制 | 减少IPC调用次数 | 信号强度实时更新 | | 专用线程池 | 隔离非关键任务资源占用 | 联系人数据库查询 | | Binder线程扩容 | 缓解瞬时高峰压力(临时方案) | 系统启动时的注册风暴 | 通过上述组合策略,可解决90%以上的Phone进程Binder耗尽问题。若仍频繁发生,需结合 `systrace` 分析内核调度阻塞点。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值