一篇文章带你读懂Service为什么会ANR?

一、前言

    在Service组件StartService()方式启动流程分析文章中,针对Context#startService()启动Service流程分析了源码,其实关于Service启动还有一个比较重要的点是Service启动的ANR,因为因为线上出现了上百例的"executing service " + service.shortName的异常。
    本文中所有源码都只粘贴关键代码,无关紧要的代码已忽略。

二、Service-ANR原理

2.1 Service启动ANR原理简述

    Service的ANR触发原理,是在启动Service前使用Handler发送一个延时的Message(埋炸弹过程),然后在Service启动完成后remove掉这个Message(拆炸弹过程)。如果在指定的延迟时间内没有remove掉这个Message,那么就会触发ANR(没有在炸弹爆炸前拆掉就会爆炸),弹出AppNotResponding的弹窗。
Android-应用程序无响应
    其实这个机制跟Windows/MacOS的应用程序无响应,是类似的交互设计。
Windows-应用程序无响应
Mac-应用程序无响应

2.2 前台Service VS 后台Service的区别

2.2.1 前台Service

    前台Service是一种在通知栏中显示持续通知的服务,它通常用于执行用户明确知晓的任务,比如音乐播放器、定位服务等。前台Service在系统内部被视为用户正在主动使用的组件,因此它具有更高的优先级和较低的系统资源限制。在使用前台Service时,必须在通知栏中显示一个通知,以告知用户有一个正在运行的Service,并且通常还应该提供一些与该Service相关的有用信息。

2.2.3 后台Service

    后台Service是一种不会在通知栏中显示通知的服务。它用于执行一些不需要用户直接交互或注意的任务,例如数据同步、网络请求等。后台Service具有较低的系统优先级,系统可能会在资源紧张的情况下终止这些服务,以释放资源。

    系统默认service都是后台的,可以通过startForeground()把该service提升到foreground优先级,那么adj便会成为PERCEPTIBLE_APP_ADJ(可感知的级别),这个级别的app一般不会轻易被杀。

  • 从Android O开始提供startForegroundService开启前台Service:
Intent intent = new Intent(MainActivity.this, ANRService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}
startService(intent);

2.3 Service启动ANR源码执行过程

    ps: 还是基于Android SDK28源码分析
    基于文章:Service组件StartService()方式启动流程分析的总结,我们已经很清楚,通过startService的方式启动Service的源码过程。因此,本文直接从com.android.server.am.ActiveServices#bringUpServiceLocked方法的源码开始分析,如有不清楚前置的启动流程的同学,可以参考我之前的文章,然后打开AS对照看下这部分的代码。

2.3.1 ActiveServices#bringUpServiceLocked
    private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
            boolean whileRestarting, boolean permissionsReviewRequired)
            throws TransactionTooLargeException {
        if (r.app != null && r.app.thread != null) {
            sendServiceArgsLocked(r, execInFg, false);
            return null;
        }
        realStartServiceLocked(r, app, execInFg);
    }
2.3.2 ActiveServices#realStartServiceLocked
private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    // ...
    // 会转调到scheduleServiceTimeoutLocked(r.app)方法进行埋炸弹操作
	bumpServiceExecutingLocked(r, execInFg, "create");
	// 炸弹埋下了,开始引爆计时
	mAm.updateLruProcessLocked(app, false, null);
	updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
	// 调整oomAdj优先级
	mAm.updateOomAdjLocked();
	boolean created = false;
	try {
		mAm.notifyPackageUse(r.serviceInfo.packageName,
                                 PackageManager.NOTIFY_PACKAGE_USE_SERVICE);
        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
		app.thread.scheduleCreateService(r, r.serviceInfo,
        mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
        app.repProcState);
        r.postNotification();
        created = true;
    } catch (DeadObjectException e) {
    	// 进程已经kill了,或者binder通信失败了,直接kill掉进程,
    	// 通过app.thread向ActivityThread.H发消息报告程序奔溃了
    	mAm.appDiedLocked(app);
    	throw e;
    } finally {
    	if (!created) {
           // Keep the executeNesting count accurate.
           final boolean inDestroying = mDestroyingServices.contains(r);
           // serviceDoneExecutingLocked方法内会拆除炸弹
           serviceDoneExecutingLocked(r, inDestroying, inDestroying);

           // Cleanup.
           if (newService) {
               app.services.remove(r);
               r.app = null;
           }

           // Retry.
           if (!inDestroying) {
              scheduleServiceRestartLocked(r, false);
           }
       }
    	// ...
    }
}
  • 从如上realStartSreviceLocked()实现逻辑看,如果是新创建(非首次启动的Serive,冷启动)Service,ANR超时时间的计算是从创建Service实例->回调Service#onCreate结束;
  • app.thread.scheduleCreateService:切到主线程执行创建Service实例,并回调Service的attach、onCreate方法。这里面我们能控制耗时的只有onCreate方法,所以发现Service ANR了,都会去看主线程是否有Blocked、Service的onCreate方法是否有耗时操作。
  • 实际验证,在Serice的onCreate方法中Thread.sleep(20_000),查看确实会ANR
2.3.3 埋炸弹过程:ActiveServices#bumpServiceExecutingLocked
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
	// ...
	scheduleServiceTimeoutLocked(r.app);
	// ...
}
  • com.android.server.am.ActiveServices#scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
  • 这里需要知道,炸弹的爆炸时间在ActiveServices中定义了三个:
// 前台Service的超时时间是20s
static final int SERVICE_TIMEOUT = 20*1000;
// 后台Service的超时时间是200s,是前台超时时间的10倍
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
// How long the startForegroundService() grace period is to get around to
// calling startForeground() before we ANR + stop it.
static final int SERVICE_START_FOREGROUND_TIMEOUT = 10*1000;
2.3.4 拆炸弹过程:ActiveServices#serviceDoneExecutingLocked
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
        boolean finishing) {
    // ...
	mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); 
	// ...       
}
  • 通过以上源码分析过程,我们可以简单总结成如下时序图,供理解整体过程:
    Service-ANR过程源码时序图
2.3.5 炸弹爆炸出发ANR弹窗过程

    如上文分析的,ANR弹窗其实就是一个sendMessageDelayed()方式发送的一个Message,想要了解ANR炸弹这么爆炸的,其实检索这个what值为ActivityManagerService.SERVICE_TIMEOUT_MSG的消息处理过程即可。
这个消息的Handler对应的handleMessage方法实现代码在AMS.java中。

  • com.android.server.am.ActivityManagerService.MainHandler#handleMessage
final class MainHandler extends Handler {
    public MainHandler(Looper looper) {
        super(looper, null, true);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            // ...
        	case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
            case SERVICE_FOREGROUND_TIMEOUT_MSG: {
                mServices.serviceForegroundTimeout((ServiceRecord)msg.obj);
            } break;
            case SERVICE_FOREGROUND_CRASH_MSG: {
                mServices.serviceForegroundCrash(
                    (ProcessRecord) msg.obj, msg.getData().getCharSequence(SERVICE_RECORD_KEY));
            } break;
            // ...
        }
    }
  • com.android.server.am.ActiveServices#serviceTimeout
void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;
    synchronized(mAm) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        final long now = SystemClock.uptimeMillis();
        final long maxTime =  now -
                (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        ServiceRecord timeout = null;
        long nextTime = 0;
        for (int i=proc.executingServices.size()-1; i>=0; i--) {
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, "    ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
            // 这一句对于我们分析问题比较关键,如果有Service的ANR,
            // 就会在log中有这样的前缀打印:executing service Service.shortName
            anrMessage = "executing service " + timeout.shortName;
        } else {
            Message msg = mAm.mHandler.obtainMessage(
                    ActivityManagerService.SERVICE_TIMEOUT_MSG);
            msg.obj = proc;
            mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
                    ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
        }
    }
    // 真正触发ANR弹窗的位置
    if (anrMessage != null) {
        mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
    }
}

这里记住anrMessage的格式是:executing service Service.shortName,代表的是Service的启动超时。

  • com.android.server.am.AppErrors#appNotResponding
final void appNotResponding(ProcessRecord app, ActivityRecord activity, ActivityRecord parent, boolean aboveSystem, final String annotation) {
	if (mService.mController != null) {
    try {
        // 0 == continue, -1 = kill process immediately
        // !!关键:mService.mController的实现类是:
       // com.android.server.am.ActivityManagerShellCommand.MyActivityController
        int res = mService.mController.appEarlyNotResponding(
                app.processName, app.pid, annotation);
        if (res < 0 && app.pid != MY_PID) {
            app.kill("anr", true);
        }
    } catch (RemoteException e) {
        mService.mController = null;
        Watchdog.getInstance().setActivityController(null);
    }
    long anrTime = SystemClock.uptimeMillis();
    if (ActivityManagerService.MONITOR_CPU_USAGE) {
       mService.updateCpuStatsNow();
    }
    // Unless configured otherwise, swallow ANRs in background processes 
    // & kill the process.
    // 读取开发者选项中的“显示后台ANR”开关
    boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
    Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
    boolean isSilentANR;
    // Don't dump other PIDs if it's a background ANR
    isSilentANR = !showBackground && !isInterestingForBackgroundTraces(app);
    // Log the ANR to the main log.
	StringBuilder info = new StringBuilder();
	info.setLength(0);
	// 这里可以看出写入到日志文件中的格式是:ANR in processName,
	// 比如:我们应用包名是com.techmix.myapp,
	// 那可在搜索时,直接输入ANR in com.techminx.myapp搜索,可更高效定位到ANR的trace位置
	info.append("ANR in ").append(app.processName);
	if (activity != null && activity.shortComponentName != null) {
    	info.append(" (").append(activity.shortComponentName).append(")");
	}
    info.append("\n");
    info.append("PID: ").append(app.pid).append("\n");
    if (annotation != null) {
      // 这里的reason还是serviceTimeout中定义的Service启动的anrMessage字符串:
      // "executing service Service.shortName",没有多余的更明细的分类了,具体是哪一步ANR了。
      info.append("Reason: ").append(annotation).append("\n");
    }
    if (parent != null && parent != activity) {
      info.append("Parent: ").append(parent.shortComponentName).append("\n");
    }
    ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
    // For background ANRs, don't pass the ProcessCpuTracker to
    // avoid spending 1/2 second collecting stats to rank lastPids.
    File tracesFile = ActivityManagerService.dumpStackTraces(true, firstPids,
    (isSilentANR) ? null : processCpuTracker, 
    (isSilentANR) ? null : lastPids, nativePids);
    
    // 写入cpu占用信息到anr log中
    String cpuInfo = null;
    if (ActivityManagerService.MONITOR_CPU_USAGE) {
       mService.updateCpuStatsNow();
       synchronized (mService.mProcessCpuTracker) {
           cpuInfo = mService.mProcessCpuTracker.printCurrentState(anrTime);
       }
       info.append(processCpuTracker.printCurrentLoad());
       info.append(cpuInfo);
    }
    info.append(processCpuTracker.printCurrentState(anrTime));
    // ANR log写入到dropbox文件夹中,annotation变量就是
    // com.android.server.am.ActiveServices#serviceTimeout中传入的anrMessage变量
    mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
                cpuInfo, tracesFile, null);
    synchronized (mService) {
    	// 静默ANR的定义?
    	if (isSilentANR) {
           app.kill("bg anr", true);
           return;
        }
    	// 通过Handler发送ANR弹窗的dialog,这里直接跟进
    	// ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG这个消息的handleMessage处理逻辑即可
    	Message msg = Message.obtain();
    	msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
    	msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
    	// 注意这里的mService是AMS
    	mService.mUiHandler.sendMessage(msg);
    }
}
  • 静默ANR的定义?
  • 弹出ANR弹窗的逻辑代码:com.android.server.am.ActivityManagerService.UiHandler#handleMessage
case SHOW_NOT_RESPONDING_UI_MSG: {
	// 还是转调到AppErrors中的方法去实现了,所以这里只是用AMS中的UiHandler切换了一下线程而已
	// 最开始的startService方法,从应用主线程
	// ContextImpl#startService->AMS#startService(),后者其实是执行在binder线程池的线程里
	// 面的,是子线程。所以这里通过消息的方式切到主线程
    mAppErrors.handleShowAnrUi(msg);
    ensureBootCompleted();
   } break;
  • com.android.server.am.AppErrors#handleShowAnrUi
	// 跟ANR相关的变量直接存储在了ProcessRecord.java类中,每个进程单独维护一个
	boolean notResponding;      // does the app have a not responding dialog?
	Dialog anrDialog;           // dialog being displayed due to app not resp.

    void handleShowAnrUi(Message msg) {
        Dialog dialogToShow = null;
        synchronized (mService) {
            AppNotRespondingDialog.Data data = (AppNotRespondingDialog.Data) msg.obj;
            final ProcessRecord proc = data.proc;
            if (proc == null) {
                Slog.e(TAG, "handleShowAnrUi: proc is null");
                return;
            }
            if (proc.anrDialog != null) {
                Slog.e(TAG, "App already has anr dialog: " + proc);
                MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                        AppNotRespondingDialog.ALREADY_SHOWING);
                return;
            }
			// 这个ANR的广播,应用进程如果注册了能接收到吗?
            Intent intent = new Intent("android.intent.action.ANR");
            if (!mService.mProcessesReady) {
                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                        | Intent.FLAG_RECEIVER_FOREGROUND);
            }
            mService.broadcastIntentLocked(null, null, intent,
                    null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                    null, false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);

            boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                    Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
            if (mService.canShowErrorDialogs() || showBackground) {
                dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
                proc.anrDialog = dialogToShow;
            } else {
                MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_APP_ANR,
                        AppNotRespondingDialog.CANT_SHOW);
                // 如果ANR弹窗是关闭状态下,直接kill当前应用进程了
                // 跟ANR弹窗中点关闭应用一样,多是调用AMS#killAppAtUsersRequest方法
                // 关闭当前进程
                mService.killAppAtUsersRequest(proc, null);
            }
        }
        // If we've created a crash dialog, show it without the lock held
        if (dialogToShow != null) {
            dialogToShow.show();
        }
    }
  • com.android.server.am.AppErrors#killAppAtUserRequestLocked
  void killAppAtUserRequestLocked(ProcessRecord app, Dialog fromDialog) {
        app.crashing = false;
        app.crashingReport = null;
        app.notResponding = false;
        app.notRespondingReport = null;
        if (app.anrDialog == fromDialog) {
            app.anrDialog = null;
        }
        if (app.waitDialog == fromDialog) {
            app.waitDialog = null;
        }
        // 这里的MY_PID是定义在AMS中的:static final int MY_PID = myPid();
        // 所以这里只要是有效的应用pid,都是能进入if逻辑分支中的
        if (app.pid > 0 && app.pid != MY_PID) {
            handleAppCrashLocked(app, "user-terminated" /*reason*/,
                    null /*shortMsg*/, null /*longMsg*/, null /*stackTrace*/, null /*data*/);
            app.kill("user request after error", true);
        }
    }

app.kill()调用的是ProcessRecord#kill(),最终转调到AMS#killProcessGroup()方法了。这里AMS方式kill掉进程的,在Android的logcat中其实都能搜索到,Activity Manager killing的字样的。

2.4 Service的哪些相关方法是有ANR埋炸弹——计时引爆逻辑的?

    需要注意:Service对于ANR的定义其实不止onCreate中,onBind、onStartCommand耗时超过20s其实都会触发ANR,下面进行详细分析。

2.4.1 Service#onCreate
private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    // ...
    // 会转调到scheduleServiceTimeoutLocked(r.app)方法进行埋炸弹操作
	bumpServiceExecutingLocked(r, execInFg, "create");
	// 炸弹埋下了,开始引爆计时,拆炸弹是在ActivityThread中执行完创建Servie实例、
	// 回调onCreate方法之后进行
	// (1)
	app.thread.scheduleCreateService(r, r.serviceInfo,
        mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
}
  • (1)ActivityThread#scheduleCreateService()方法源码
private 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, data.compatInfo);
    Service service = null;
    java.lang.ClassLoader cl = packageInfo.getClassLoader();
    // (1) 反射创建Service实例,直接Clazz.newInstance(),由AppComponentFactory实现
    service = packageInfo.getAppFactory()
    .instantiateService(cl, data.info.name, data.intent);
       
    // (2)创建ApplicationContext做为Service中的mBase变量,
    // 也就是实际的ContextImpl实例
    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
    context.setOuterContext(service);
	
	//(3)创建Application实例,也是通过反射创建,也是由AppComponentFactory实现
    Application app = packageInfo.makeApplication(false, mInstrumentation);
    service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
    service.onCreate();
    mServices.put(data.token, service);
    ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
2.4.2 Service#onStartCommand
  • Service#onStartCommand()方法埋炸弹代码
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
   // ...
   // 开始埋炸弹
   bumpServiceExecutingLocked(r, execInFg, "start");
   if (!oomAdjusted) {
      oomAdjusted = true;
      mAm.updateOomAdjLocked(r.app, true);
   }
   Exception caughtException = null;
   try {
   	   // (2) 转调到ActivityThread#scheduleServiceArgs()
       r.app.thread.scheduleServiceArgs(r, slice);
   } catch (TransactionTooLargeException e) {
       if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large for " + args.size()
                + " args, first: " + args.get(0).args);
         Slog.w(TAG, "Failed delivering service starts", e);
         caughtException = e;
   } catch (RemoteException e) {
          // Remote process gone...  we'll let the normal cleanup take care of this.
          if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r);
          Slog.w(TAG, "Failed delivering service starts", e);
          caughtException = e;
    } catch (Exception e) {
           Slog.w(TAG, "Unexpected exception", e);
           caughtException = e;
    }
    if (caughtException != null) {
        // Keep nesting count correct
        final boolean inDestroying = mDestroyingServices.contains(r);
        for (int i = 0; i < args.size(); i++) {
        	// 拆炸弹
            serviceDoneExecutingLocked(r, inDestroying, inDestroying);
        }
        if (caughtException instanceof TransactionTooLargeException) {
           throw (TransactionTooLargeException)caughtException;
        }
    }
}
  • app.thread.scheduleServiceArg会转到ActivityThread中,通过H.sendMessage(),最后调用Service的onStartComman方法传入intent中的参数
2.4.3 Service#onBind/onRebind
  • Service#onBind()方法埋炸弹代码
private void handleBindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    if (!data.rebind) {
       IBinder binder = s.onBind(data.intent);
       ActivityManager.getService().publishService(data.token, 
       data.intent, binder);
    } else {
       s.onRebind(data.intent);
       ActivityManager.getService().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
     }
}
2.4.4 Service#onUnbind
  • Service#unBind()方法埋炸弹代码
private final void bringDownServiceLocked(ServiceRecord r) {
	// Tell the service that it has been unbound.
    if (r.app != null && r.app.thread != null) {
        for (int i=r.bindings.size()-1; i>=0; i--) {
            IntentBindRecord ibr = r.bindings.valueAt(i);
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr
                    + ": hasBound=" + ibr.hasBound);
            if (ibr.hasBound) {
                try {
                    bumpServiceExecutingLocked(r, false, "bring down unbind");
                    mAm.updateOomAdjLocked(r.app, true);
                    ibr.hasBound = false;
                    ibr.requested = false;
                    r.app.thread.scheduleUnbindService(r,
                            ibr.intent.getIntent());
                } catch (Exception e) {
                    Slog.w(TAG, "Exception when unbinding service "
                            + r.shortName, e);
                    serviceProcessGoneLocked(r);
                }
            }
        }
    }
}

app.thread.scheduleUnbindService方法中,会给Service#onUnbind进行埋炸弹:

private void handleUnbindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    if (s != null) {
        try {
            data.intent.setExtrasClassLoader(s.getClassLoader());
            data.intent.prepareToEnterProcess();
            // onUnbind方法耗时是应用程序能控制的
            boolean doRebind = s.onUnbind(data.intent);
            try {
                if (doRebind) {
                    ActivityManager.getService().unbindFinished(
                            data.token, data.intent, doRebind);
                } else {
                    ActivityManager.getService().serviceDoneExecuting(
                            data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
                }
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(s, e)) {
                throw new RuntimeException(
                        "Unable to unbind to service " + s
                        + " with " + data.intent + ": " + e.toString(), e);
            }
        }
    }
}
2.4.5 Service#onDestroy
  • Service#onDestroy()方法埋炸弹
    这块的炸弹计时包含了两个可控的方法,Service#onDestroy()和QueuedWork.waitToFinish(),后者是我们平时通过apply方法修改SP时加入的Runnable任务,会在onDestroy执行后进行同步阻塞写入。

埋炸弹源码:

private void handleStopService(IBinder token) {
    Service s = mServices.remove(token);
    // (1)
    s.onDestroy();
    s.detachAndCleanUp();
    Context context = s.getBaseContext();
    if (context instanceof ContextImpl) {
        final String who = s.getClassName();
        ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
    }
    // (2)
    QueuedWork.waitToFinish();
    ActivityManager.getService().serviceDoneExecuting(token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
}

通过分析实现代码,上述就(1)s.onDestroy()和(2)QueuedWork.waitToFinish()是我们可控的。

通过上述分析,我们得出结论,Service可重写方法中,共有5个有炸弹引爆计时。在ActivityThread$H中对应的Message为:

public static final int CREATE_SERVICE          = 114;
public static final int SERVICE_ARGS            = 115; // 包含onStartCommand和onTaskRemoved方法
public static final int STOP_SERVICE            = 116;
public static final int BIND_SERVICE            = 121; // 包含onBind和onReBind方法
public static final int UNBIND_SERVICE          = 122;
  • 总结Service中除了dump、onTrimMemory、onConfigurationChanged是没有埋炸弹的,其余可重写代码均有炸弹计时,超时都会触发ANR,并且在写代码时需要注意回调方法的执行顺序问题,比如:onCreate->onStartCommand这条执行路径上,onCreate阻塞10s,onStartCommand阻塞10s,那也会触发ANR。另外,从源码中我们也不难看出,其实ANR的计时范围还包含了我们无法控制的Framework层的逻辑代码,所以我在试测时,发现根本不用20s就会ANR!!
2.4.6 总结
  • 实际写代码验证结果如下:
/**
 * @author TechMix
 * @date 2023年7月31日 23:00
 * @description 验证Service哪些回调方法会有ANR埋炸弹——爆炸计时逻辑
 */
class ANRService : Service() {
    companion object {
        private const val TAG = "yyg_ANRService"
    }

    /**
     * 无ANR计时炸弹
     */
    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        Log.d(TAG, "onConfigurationChanged: ")
    }

    /**
     * 无ANR计时炸弹
     */
    override fun onLowMemory() {
        super.onLowMemory()
        Log.d(TAG, "onLowMemory: ")
    }

    /**
     * 无ANR计时炸弹
     */
    override fun onTrimMemory(level: Int) {
        super.onTrimMemory(level)
        Log.d(TAG, "onTrimMemory: ")
    }

    /**
     * 有ANR计时炸弹,同onStartCommand()方法,是二选一的分支代码
     * if (!data.taskRemoved) {
     *   res = s.onStartCommand(data.args, data.flags, data.startId);
     * } else {
     *   s.onTaskRemoved(data.args);
     *   res = Service.START_TASK_REMOVED_COMPLETE;
     * }
     */
    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.d(TAG, "onTaskRemoved: ")
    }

    /**
     * 无ANR计时炸弹
     */
    override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<out String>?) {
        super.dump(fd, writer, args)
        Log.d(TAG, "dump: ")
    }

    /**
     * 有埋ANR炸弹
     */
    override fun onCreate() {
        super.onCreate()
        // Thread.sleep(19_000)
        Log.d(TAG, "onCreate: ")
    }

    /**
     * 有埋ANR炸弹,是在onStartCommand方法最前面回调的。
     */
    override fun onStart(intent: Intent?, startId: Int) {
        super.onStart(intent, startId)
        Thread.sleep(19_000)
        Log.d(TAG, "onStart: ")
    }

    /**
     * 有埋ANR炸弹
     */
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Thread.sleep(18_000)
        Log.d(TAG, "onStartCommand: ")
        return super.onStartCommand(intent, flags, startId)
    }

    /**
     * 有埋ANR炸弹
     */
    override fun onBind(intent: Intent): IBinder? {
        Log.d(TAG, "onBind: ")
        return null
    }

    /**
     * 有埋ANR炸弹,同onBind()方法,是二选一的分支代码
     */
    override fun onRebind(intent: Intent?) {
        Log.d(TAG, "onRebind: ")
        super.onRebind(intent)
        Thread.sleep(19_000)
    }

    /**
     * 有埋ANR炸弹
     */
    override fun onUnbind(intent: Intent?): Boolean {
        Log.d(TAG, "onUnbind: ")
        return super.onUnbind(intent)
    }

    /**
     * 有埋ANR炸弹
     * !!注意:ANR计时包含了QueuedWork.waitToFinish()的耗时(SP操作apply方法修改后,还没写入的任务,
     * 会在调用这个方法时同步阻塞执行文件写入。)
     */
    override fun onDestroy() {
        Log.d(TAG, "onDestroy: ")
        super.onDestroy()
    }
}
  • 阅读Android源码的感受:
        google工程师的代码,其实业务逻辑代码并不见得比我们写的优雅,技术上还是跟我们一样的就是用那些东西,Handler、各种成员变量。但是整体看起来是比较清晰的,不过跟我们写代码一样,方法多了,像ActiveServices那一堆什么startXxxLocked、brindDownXx x之类的,太多了就看的一脸懵逼,只能跟进关键的代码片段了。但是对于异常的处理是比较到位的,关键位置都有相应的log,并且能够控制打开/关闭,能给调试带来很大便捷,这点我们是值得借鉴学习的。
        另外一个就是业务代码味道没有那么重,技术转化成产品能力比较强,感觉就像是实现了某些技术,然后技术之上赋能了产品需要的能力的感觉。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 如果在测试过程中遇到app出现crash或者ANR,我这样处理: 1. 首先,我收集尽可能多的信息来描述crash或ANR发生的情况。这可能包括app的版本号、设备的型号、操作系统版本、运行的进程、以及在发生crash或ANR时用户正在做什么。 2. 然后,我检查crash日志或ANR报告,以了解crash或ANR是由哪个线程引起的,以及它们发生时的上下文。这些信息可以帮助我定位问题的根本原因。 3. 接下来,我尝试重现这个问题,以便于进一步分析。 4. 最后,我尝试解决这个问题。这可能包括修改代码来避免crash或ANR,或者调整app的配置以改善性能。 总之,在处理app出现crash或ANR的情况时,我尽可能收集信息、分析问题的原因,并找到有效的解决方案。 ### 回答2: 在测试过程中,若遇到应用程序出现crash(崩溃)或者ANR(应用无响应),我采取以下步骤进行处理。 首先,我记录下相关信息,包括出现crash或ANR的具体操作步骤、系统版本、设备型号等,这些信息有助于后续分析和解决问题。 其次,我尝试重现问题,通过重新执行导致crash或ANR的步骤,验证问题是否可复现。如果可以复现,我进一步分析出错的原因。 对于crash,我查看应用崩溃时的日志信息,包括堆栈追踪,以确定哪一部分代码出现异常。可能的原因包括内存泄漏、空指针引用、并发冲突等。我使用调试工具或日志分析工具来帮助定位并解决问题。 对于ANR,我检查是否存在耗时操作阻塞主线程的情况,例如网络请求、磁盘访问等。如果存在这样的耗时操作,我尝试优化或重构代码,将其移至后台线程执行,以避免主线程阻塞导致ANR。 另外,我检查相关资源的释放情况,例如数据库连接、文件句柄等,确保资源在使用完毕后正确释放,避免资源泄漏。 最后,在定位到问题的原因后,我与开发人员和其他测试人员进行沟通,确保问题得到彻底解决,并进行相关的测试用例修复或添加,以防止类似问题再次出现。 总之,当遇到应用程序崩溃或无响应时,我积极记录信息、重现问题、分析原因、解决问题,并与相关人员合作确保问题修复。 ### 回答3: 在测试过程中遇到app出现crash或者ANR(Application Not Responding),我采取以下步骤进行处理。 首先,我记录下出现crash或者ANR的具体情况,包括时间、设备型号、操作步骤等信息,以便后续的分析和复现。 然后,我尝试复现这个问题,通过重复操作或者使用不同的设备进行测试,以确定问题的复现性和范围。如果能成功复现问题,就能更好地找到问题的根源。 接下来,我检查app的日志文件,查找任何与crash或者ANR相关的错误信息或异常堆栈。这些信息常常提供有关问题的线索,指引我进一步定位问题的来源。 如果问题比较复杂或难以找到明显的原因,我使用一些辅助工具来帮助我分析。比如使用Android Studio的调试功能,可以设置断点,逐步调试代码,以找出问题发生的具体位置。 一旦找到问题的根源,我联系开发团队,向他们提供详细的问题报告和复现步骤。同时,我与他们沟通,讨论是否需要提供更多信息或者进行更深入的调查。 最后,在修复问题后,我重新测试app,验证修复的有效性,并确保其他功能或模块没有受到负面影响。 总结而言,对于app出现crash或者ANR,我记录、复现、分析和修复问题。通过这些步骤,我能尽可能准确地找到问题的根源,并与开发团队合作解决问题,以提升app的质量和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TechMix

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值