Android ANR --Service

426 阅读4分钟

分类

Android 的ANR 主要可以分为四种

  1. 键盘无响应 (Activity超时)
  2. service启动超时
  3. BroadcastReceiver 处理超时
  4. ContentProvider 处理超时

超时定义

多次时间算超时? ContentProvider、BroadcastReceiver、键盘无响应 在 ActivityManagerService中已经定义好了
service的超时定义在ActiveServices中

public class ActivityManagerService{
	...
     // How long we wait for an attached process to publish its content providers
    // before we decide it must be hung.
    static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000;
    
    // How long we wait until we timeout on key dispatching.
    static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
    
    // How long we allow a receiver to run before giving up on it.
    static final int BROADCAST_FG_TIMEOUT = 10*1000;
    static final int BROADCAST_BG_TIMEOUT = 60*1000;
}

public class ActiveService{
	...
    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20*1000;
    
     // How long we wait for a service to finish executing.
    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;

}

可以看到 CONTENT_PROVIDER 发布时间时间阈值 是10s
input分发阈值是5s
前台广播和后台广播的超时时间不一样 分别为10s 和60s
前台service和后台service也不一样 分别为 10s和200s

如何触发的ANR

Service

当我们调用startService的时候 会从ActivityThread产生一个到AMS的binder调用 。调用用AMS的startService

public class ActivityManagerService{
    @Override
    public ComponentName startService(IApplicationThread caller, Intent service,
            String resolvedType, boolean requireForeground, String callingPackage, int userId)
            throws TransactionTooLargeException {
        enforceNotIsolatedCaller("startService");
      	...
        synchronized(this) {
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            final long origId = Binder.clearCallingIdentity();
            ComponentName res;
            try {
            	// 这个mServices 是ActiveService的实例
                res = mServices.startServiceLocked(caller, service,
                        resolvedType, callingPid, callingUid,
                        requireForeground, callingPackage, userId);
            } finally {
                Binder.restoreCallingIdentity(origId);
            }
            return res;
        }
    }
}

经过ActiveService的一系列调用,最终会进入ActiveService -> realStartServiceLocked()方法

public class ActiveService{
    private final void realStartServiceLocked(ServiceRecord r,
          ProcessRecord app, boolean execInFg) throws RemoteException {
           ...
			 bumpServiceExecutingLocked(r, execInFg, "create");
             ...
             
             // 通知app线程启动service
             app.thread.scheduleCreateService(r, r.serviceInfo,
                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                    app.repProcState);

          }
          
    private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
        boolean timeoutNeeded = true;
        if ((mAm.mBootPhase < SystemService.PHASE_THIRD_PARTY_APPS_CAN_START)
                && (r.app != null) && (r.app.pid == android.os.Process.myPid())) {

            Slog.w(TAG, "Too early to start/bind service in system_server: Phase=" + mAm.mBootPhase
                    + " " + r.getComponentName());
            timeoutNeeded = false;
        }

        long now = SystemClock.uptimeMillis();
        if (r.executeNesting == 0) {
            r.executeFg = fg;
            ServiceState stracker = r.getTracker();
            if (stracker != null) {
                stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
            }
            if (r.app != null) {
                r.app.executingServices.add(r);
                r.app.execServicesFg |= fg;
                if (timeoutNeeded && r.app.executingServices.size() == 1) {
                	// 定时任务 判断service是否超时
                    scheduleServiceTimeoutLocked(r.app);
                }
            }
        } else if (r.app != null && fg && !r.app.execServicesFg) {
            r.app.execServicesFg = true;
            if (timeoutNeeded) {
                scheduleServiceTimeoutLocked(r.app);
            }
        }
        r.executeFg |= fg;
        r.executeNesting++;
        r.executingStart = now;
    }
    
    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);
    }
}

在这个方法的入口出会调用 bumpServiceExecutingLocked 接着调用 scheduleServiceTimeoutLocked 从方法名字便能看出 这边shedule了一个判断service是否超时的任务

scheduleServiceTimeoutLocked 通过ActivityManagerService的Handler发送了一个SERVICE_TIMEOUT_MSG 的消息 。注意这个延时时间,根据service的前后台状态有所不同。我们得去看看 AMS是怎么处理这个消息的。

临时总结下:启动service的时候 ActiveService会发送一个定时任务 用于判断 service是否超时至于怎么判断的 后面再看。

ActivityManagerService -> MainHandler

	final class MainHandler extends Handler {
		   public void handleMessage(Message msg) {
           		switch (msg.what) {
                	 case SERVICE_TIMEOUT_MSG:
                     	 mServices.serviceTimeout((ProcessRecord)msg.obj);
                         break;
                }
           }
    }

AMS 中的Handler处理又调回了ActiveService

public class ActiveService{
	void serviceTimeout(ProcessRecord proc) {
        String anrMessage = null;

        synchronized(mAm) {
            if (proc.executingServices.size() == 0 || proc.thread == null) {
                return;
            }
            final long now = SystemClock.uptimeMillis();
             // 因为这个任务是SERVICE_TIMEOUT或者SERVICE_BACKGROUND_TIMEOUT时间后才会执行 所以 这边计算出一个server启动的最大时间
            final long maxTime =  now -
                    (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
            ServiceRecord timeout = null;
            long nextTime = 0;
            // 遍历所有正在执行的service
            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);
                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);
        }
    }
}

可以看到 超时的时候会调用 mAm.mAppErrors.appNotResponding 响应ANR。但是未超时的时候,却是重新设置了一个定时任务去做检查,这样岂不是白白消耗性能。
其实不是的,当service启动成功后,会把这个定时任务取消。总的逻辑就是 启动的时候发送一个定时任务检查是否ANR,如果service在定时任务执行之前启动成功,定时任务就会被删除,否者就会执行定时任务检查是否ANR。

mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage); 会发送一个消息给AMS的Handler

 case SHOW_NOT_RESPONDING_UI_MSG: {
                mAppErrors.handleShowAnrUi(msg);
                ensureBootCompleted();
            }
            
public class AppErrors{
	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;
            }

            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);
                // Just kill the app if there is no dialog to be shown.
                mService.killAppAtUsersRequest(proc, null);
            }
        }
        // If we've created a crash dialog, show it without the lock held
        if (dialogToShow != null) {
            dialogToShow.show();
        }
    }
   }

handleShowAnrUi 弹出一个dialog 即我们看到的anr弹框

以上就是ANR产生逻辑,再来看看service启动成功后 定时消息是怎么移除的 ActiveService -> realStartServiceLocked 会调用 app.thread.scheduleCreateService 通知app进程启动 service 。在ActivityThread中会 执行 handleCreateService方法

    private void handleCreateService(CreateServiceData data) {
        unscheduleGcIdler();
        LoadedApk packageInfo = getPackageInfoNoCheck(
                data.info.applicationInfo, data.compatInfo);
        Service service = null;
        try {
        	// 通过反射 实例化Service
            java.lang.ClassLoader cl = packageInfo.getClassLoader();
            service = packageInfo.getAppFactory()
                    .instantiateService(cl, data.info.name, data.intent);
        } catch (Exception e) {
			...
        }

        try {
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);

            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);
            try {
            	//启动成功 通知 AMS 客户端的Serivce已经启动成功了
                //调用回到AMS 的serviceDoneExecuting方法
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        } catch (Exception e) {
            ...
        }
    }
public class ActivityManagerService{
    public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
        synchronized(this) {
            if (!(token instanceof ServiceRecord)) {
                Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);
                throw new IllegalArgumentException("Invalid service token");
            }
            // 这个mService是ActiveService
            mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
        }
    }
    
    
}
public class ActiveService{
    void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
        boolean inDestroying = mDestroyingServices.contains(r);
        if (r != null) {
			...
            final long origId = Binder.clearCallingIdentity();
            
            serviceDoneExecutingLocked(r, inDestroying, inDestroying);
            Binder.restoreCallingIdentity(origId);
        } else {
            Slog.w(TAG, "Done executing unknown service from pid "
                    + Binder.getCallingPid());
        }
    }

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
            boolean finishing) {
        r.executeNesting--;
        if (r.executeNesting <= 0) {
            if (r.app != null) {
                r.app.execServicesFg = false;
                r.app.executingServices.remove(r);
                if (r.app.executingServices.size() == 0) {
                	// 重点 如果service全部启动成功,则移除SERVICE_TIMEOUT_MSG的定时任务
                    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
                } else if (r.executeFg) {
                    // Need to re-evaluate whether the app still needs to be in the foreground.
                    for (int i=r.app.executingServices.size()-1; i>=0; i--) {
                        if (r.app.executingServices.valueAt(i).executeFg) {
                            r.app.execServicesFg = true;
                            break;
                        }
                    }
                }
                if (inDestroying) {
                    mDestroyingServices.remove(r);
                    r.bindings.clear();
                }
                mAm.updateOomAdjLocked(r.app, true);
            }
        }
    }

}

总结起来就是 Service启动的时候 产生一个Binder调用,调用AMS的 startSerivce ,接着调用ActiveSerivce的realStartServiceLocked,然后将会发送一个定时任务到AMS,在定时任务中将会判断是否ANR ;如果Service在定时任务之前启动成功,则定时任务会被移除,也就不会产生ANR了