ActivityManagerService广播并行发送与串行发送示例解析

目录
  • "并行"广播的发送
  • “串行”广播的发送
  • 广播发送给正在启动的进程
  • 广播 ANR
  • 结束

"并行"广播的发送

本文以 ActivityManagerService之广播(1): 注册与发送 为基础,分析“串行”和“并行”广播的发送流程,并介绍广播 ANR 的原理。

// 1. 获取广播队列
final BroadcastQueue queue = broadcastQueueForIntent(intent);
// 2. 创建广播记录
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
        callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
        requiredPermissions, excludedPermissions, appOp, brOptions, registeredReceivers,
        resultTo, resultCode, resultData, resultExtras, ordered, sticky, false, userId,
        allowBackgroundActivityStarts, backgroundActivityStartsToken,
        timeoutExempt);
// 3. 广播记录加入到并行队列中
queue.enqueueParallelBroadcastLocked(r);
// 4. 调度发送广播
queue.scheduleBroadcastsLocked();

第3步,把广播记录保存到并行队列中

// BroadcastQueue.java
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
    // mParallelBroadcasts 类型为 ArrayList<BroadcastRecord>
    mParallelBroadcasts.add(r);
    enqueueBroadcastHelper(r);
}

第4步,调度发送广播,最终会调用如下方法

// BroadcastQueue.java
// 此时,参数 fromMsg 为 true,skipOomAdj 为 false
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    BroadcastRecord r;
    mService.updateCpuStats();
    if (fromMsg) {
        mBroadcastsScheduled = false;
    }
    // 遍历"并行"广播队列
    while (mParallelBroadcasts.size() > 0) {
        r = mParallelBroadcasts.remove(0);
        r.dispatchTime = SystemClock.uptimeMillis();
        r.dispatchClockTime = System.currentTimeMillis();
        final int N = r.receivers.size();
        for (int i=0; i<N; i++) {
            Object target = r.receivers.get(i);
            // 广播发送给动态接收器
            deliverToRegisteredReceiverLocked(r,
                    (BroadcastFilter) target, false, i);
        }
        addBroadcastToHistoryLocked(r);
    }
    // ... 省略"串行"广播的发送 ...
}

虽然名为“并行”广播,但是仍然是从队列取出广播,然后逐个发送给动态接收器。很显然,这里的行为与“并行”的含义并不一致?那么广播的“并行”发送究竟是什么意思?

接着看“并行”广播如何发送给动态接收器的

private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
        BroadcastFilter filter, boolean ordered, int index) {
    // ... 省略一大堆的权限或者异常检测 ...
    // 一个广播可能有多个接收者,因此需要一个数组来保存发送的状态
    r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
    // ordered 目前为 false
    if (ordered) {
        // ...
        }
    } else if (filter.receiverList.app != null) {
        // 马上要发送广播给接收方,因此要暂时解冻接收方的进程
        mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(filter.receiverList.app);
    }
    try {
        if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
            // ... 处于备份状态中 ...
        } else {
            r.receiverTime = SystemClock.uptimeMillis();
            // 保存允许从后台启动activity的token
            maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
            // 添加到省电模式白名单中
            maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
            // 执行广播的发送
            performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                    new Intent(r.intent), r.resultCode, r.resultData,
                    r.resultExtras, r.ordered, r.initialSticky, r.userId);
            // parallel broadcasts are fire-and-forget, not bookended by a call to
            // finishReceiverLocked(), so we manage their activity-start token here
            if (filter.receiverList.app != null
                    && r.allowBackgroundActivityStarts && !r.ordered) {
                postActivityStartTokenRemoval(filter.receiverList.app, r);
            }
        }
        // ordered 目前为 false
        if (ordered) {
            r.state = BroadcastRecord.CALL_DONE_RECEIVE;
        }
    } catch (RemoteException e) {
        // ...
    }
}

抛开一些细节,直接看 performReceiveLocked()

// BroadcastQueue.java
void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
        Intent intent, int resultCode, String data, Bundle extras,
        boolean ordered, boolean sticky, int sendingUser)
        throws RemoteException {
    // 动态广播接收器的进程,应该是存在的
    if (app != null) {
        final IApplicationThread thread = app.getThread();
        if (thread != null) {
            try {
                // 发送广播给接收方进程
                thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                        data, extras, ordered, sticky, sendingUser,
            } catch (RemoteException ex) {
               // ...
            }
        } else {
            throw new RemoteException("app.thread must not be null");
        }
    } else {
        // ...
    }
}

很简单,就是通过进程 attach 的 IApplicationThread 接口,发送广播给进程。这个过程,暂时先不分析,后面会分析到。

那么,现在来回答一下,何为“并行”广播?其实这个答案,我也是对比了串行广播的发送过程,才得出来的。所谓的"并行"发送,实际上就是把广播逐个发送给动态接收器,但是不需要等待前一个接收器反馈处理结果,就可以发送下一个。而“串行”广播的发送,是需要等待前一个广播接收器反馈处理结果后,才能调度发送下一个广播。

“串行”广播的发送

// 1.获取广播队列
BroadcastQueue queue = broadcastQueueForIntent(intent);
// 2.创建广播记录
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
        callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
        requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
        receivers, resultTo, resultCode, resultData, resultExtras,
        ordered, sticky, false, userId, allowBackgroundActivityStarts,
        backgroundActivityStartsToken, timeoutExempt);
// 3.广播记录加入到串行队列中
queue.enqueueOrderedBroadcastLocked(r);
// 4.调度发送广播
queue.scheduleBroadcastsLocked();

第3步,广播加入到串行队列中

// BroadcastQueue.java
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
    mDispatcher.enqueueOrderedBroadcastLocked(r);
    enqueueBroadcastHelper(r);
}
// BroadcastDispatcher.java
void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
    mOrderedBroadcasts.add(r);
}

并行发送的广播保存到 BroadcastQueue#mParallelBroadcasts 中,而串行发送的广播保存到 BroadcastDispatcher#mOrderedBroadcasts 中,为何要这样设计呢?有兴趣的读者可以研究下。

第4步,“串行”广播的调度发送,仍然使用的是 processNextBroadcastLocked() 方法,但是代码量是非常的大,下面将把函数分段解析。

processNextBroadcastLocked() 函数有400多行代码,这个函数里有很多东西都可以抽出来的,但是随着版本的更新,这块代码一直没有优化过。

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    BroadcastRecord r;
    mService.updateCpuStats();
    if (fromMsg) {
        mBroadcastsScheduled = false;
    }
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // 检测 receiver 进程是否死亡
        boolean isDead;
        if (mPendingBroadcast.curApp.getPid() > 0) {
            synchronized (mService.mPidsSelfLocked) {
                ProcessRecord proc = mService.mPidsSelfLocked.get(
                        mPendingBroadcast.curApp.getPid());
                isDead = proc == null || proc.mErrorState.isCrashing();
            }
        } else {
            final ProcessRecord proc = mService.mProcessList.getProcessNamesLOSP().get(
                    mPendingBroadcast.curApp.processName, mPendingBroadcast.curApp.uid);
            isDead = proc == null || !proc.isPendingStart();
        }
        if (!isDead) {
            // 进程仍然存活,结束此次广播的处理流程,继续等待
            // 等待什么呢?等待广播进程起来,并与 AMS 完成 attach application
            // 在 attach application 的过程中,会完成广播的发送
            return;
        } else {
            // 进程死亡,继续处理下一个广播
            mPendingBroadcast.state = BroadcastRecord.IDLE;
            mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
            mPendingBroadcast = null;
        }
    }

当发送一个广播给 receiver 时,如果 receiver 进程没有启动,那么会先 fork 一个 receiver 进程,然后用 mPendingBroadcast 保存待发送的广播。当 receiver 进程起来的时候,会与 AMS 执行 attach application 过程,在这个过程中,会自动把 mPendingBroadcast 保存的广播发送给 receiver 进程。

因此,这里检测到 mPendingBroadcast 不为 null 时,那么 receiver 进程肯定在启动中,只要 receiver 进程没有死亡,就什么也不用做,因为广播会自动发送给 receiver 进程。

接着看下一步的处理

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // ...
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // ...
    }
    boolean looped = false;
    // 2. 通过 do-while 循环,找到一个现在可以处理的广播
    do {
        final long now = SystemClock.uptimeMillis();
        // 获取一个待处理的广播
        r = mDispatcher.getNextBroadcastLocked(now);
        if (r == null) {
            // ... 没有广播需要处理 ...
            return;
        }
        boolean forceReceive = false;
        // 处理严重超时的广播,有两种情况
        // 一种情况是,在系统还没有起来前,发送的广播得不到执行,发生严重超时
        // 另外一种情况是,在系统起来后,有一些超时豁免的广播,发生了严重超时
        int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
        if (mService.mProcessesReady && !r.timeoutExempt && r.dispatchTime > 0) {
            if ((numReceivers > 0) &&
                    (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) {
                broadcastTimeoutLocked(false); // forcibly finish this broadcast
                forceReceive = true;
                r.state = BroadcastRecord.IDLE;
            }
        }
        if (r.state != BroadcastRecord.IDLE) {
            return;
        }
        // 当前广播因为某种原因,终止处理,然后处理下一个广播
        if (r.receivers == null || r.nextReceiver >= numReceivers
                || r.resultAbort || forceReceive) {
            // ...
            // 通知 BroadcastDispatcher ,不处理这个广播了
            mDispatcher.retireBroadcastLocked(r);
            r = null;
            looped = true;
            // 下一次循环,获取下一个广播来处理
            continue;
        }
        // 处理推迟发送广播的情况
        if (!r.deferred) {
            final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver));
            if (mDispatcher.isDeferringLocked(receiverUid)) {
                // ...
                // 保存推迟发送的广播
                mDispatcher.addDeferredBroadcast(receiverUid, defer);
                r = null;
                looped = true;
                // 下一次循环时,获取下一个广播来处理
                continue;
            }
        }
    } while (r == null);

先从整体看,通过一个 do-while 循环,最终是为了找到下一个处理的广播。为何要用一个循环来寻找呢? 因为广播可能没有接收器,或者已经严重超时,又或者广播需要推迟发送。所以要通过一个循环,找到一个能立即发送的广播。

由于本文主要是为了分析广播发送的整体流程,对于有些细节,只做注释而不做细致分析。需要深入研究的读者,可以在本文的基础上继续分析。

继续接着看下一步

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    BroadcastRecord r;
    mService.updateCpuStats();
    if (fromMsg) {
        mBroadcastsScheduled = false;
    }
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // ...
    }
    boolean looped = false;
    // 2. 通过 do-while 循环,找到一个现在可以处理的广播
    do {
        final long now = SystemClock.uptimeMillis();
        // 获取一个待处理的广播
        r = mDispatcher.getNextBroadcastLocked(now);
        // ...
    } while (r == null);
    // 走到这里,表示已经获取了一个现在可以处理的广播
    int recIdx = r.nextReceiver++;
    // 3. 在发送广播之前,先发送一个超时消息
    r.receiverTime = SystemClock.uptimeMillis();
    if (recIdx == 0) {
        // 在广播开始发送给第一个接收器时,记录发送的时间
        r.dispatchTime = r.receiverTime;
        r.dispatchClockTime = System.currentTimeMillis();
    }
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        setBroadcastTimeoutLocked(timeoutTime);
    }

在广播发送给一个 receiver 之前,会先发送一个超时消息。从广播准备发送给一个 receiver 算起,到 receiver 处理完广播,并反馈给 AMS,如果这个时间段超过了一个时间阈值,就会引发 ANR。触发 ANR 的代码设计非常巧妙,后面会具体分析这个过程。

接着看下一步

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // ...
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // ...
    }
    boolean looped = false;
    // 2. 通过 do-while 循环,找到一个现在可以处理的广播
    do {
        // ...
    } while (r == null);
    int recIdx = r.nextReceiver++;
    // ...
    // 3. 在发送广播之前,先发送一个超时消息
    // 当广播处理超时时,会触发 ANR
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        setBroadcastTimeoutLocked(timeoutTime);
    }
    final BroadcastOptions brOptions = r.options;
    // 4. 获取一个 receiver
    final Object nextReceiver = r.receivers.get(recIdx);
    // 5. 如果这个接收器是动态接收器,先把广播发送给它
    // 注意,这里处理的是有序广播发送给动态接收器的情况
    if (nextReceiver instanceof BroadcastFilter) {
        BroadcastFilter filter = (BroadcastFilter)nextReceiver;
        // 发送广播给动态接收器
        deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);
        if (r.receiver == null || !r.ordered) {
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Quick finishing ["
                    + mQueueName + "]: ordered="
                    + r.ordered + " receiver=" + r.receiver);
            r.state = BroadcastRecord.IDLE;
            scheduleBroadcastsLocked();
        } else {
            if (filter.receiverList != null) {
                maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
            }
        }
        // 注意,把广播发送给 动态receiver 后,直接返回
        return;
    }

现在一切就绪,那么开始获取一个 receiver,当这个 receiver 是一个动态接收器时,直接发送广播给它,这个发送过程前面已经分析过。

注意,这里处理的情况是,把有序广播发送给动态接收器。并且发送完成后,直接 return, 也就是结束了此次广播的发送流程。

一个广播可能有多个接收器,为何这里只发送给一个动态接收器,就直接返回了? 这就是从“串行”广播的本质,需要等待当前的广播接收器处理完广播,并返回结果后,才能把广播发送给下一个广播接收器。

接着看下一步

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // ...
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // ...
    }
    boolean looped = false;
    // 2. 通过 do-while 循环,找到一个现在可以处理的广播
    do {
        final long now = SystemClock.uptimeMillis();
        // 获取一个待处理的广播
        r = mDispatcher.getNextBroadcastLocked(now);
        // ...
    } while (r == null);
    // 走到这里,表示已经获取了一个现在可以处理的广播
    int recIdx = r.nextReceiver++;
    // 3. 在发送广播之前,先发送一个超时消息
    r.receiverTime = SystemClock.uptimeMillis();
    // ...
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        setBroadcastTimeoutLocked(timeoutTime);
    }
    final BroadcastOptions brOptions = r.options;
    // 4. 获取一个 receiver
    final Object nextReceiver = r.receivers.get(recIdx);
    // 5. 如果这个接收器是动态接收器,先把广播发送给它
    // 注意,这里处理的是有序广播发送给动态接收器的情况
    if (nextReceiver instanceof BroadcastFilter) {
        // ...
        return;
    }
    // 走到这里,表示当前的广播接收器,是静态接收器
    // 获取静态接收器的信息
    ResolveInfo info =
        (ResolveInfo)nextReceiver;
    ComponentName component = new ComponentName(
            info.activityInfo.applicationInfo.packageName,
            info.activityInfo.name);
    boolean skip = false;
    // 6. 检测是否不需要把广播发送给静态接收器
    // ... 省略一大堆的检测代码 ...
    String targetProcess = info.activityInfo.processName;
    ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
            info.activityInfo.applicationInfo.uid);
    if (!skip) {
        // 检测是否允许把广播发送给静态接收器
        final int allowed = mService.getAppStartModeLOSP(
                info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
                info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false, false);
        // 例如,大于等于 O+ 版本的 app ,不允许广播发送给静态接收器
        if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
            // ephemeral app 会返回这个模式
            if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
                skip = true;
            } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                    || (r.intent.getComponent() == null
                        && r.intent.getPackage() == null
                        && ((r.intent.getFlags()
                                & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
                        && !isSignaturePerm(r.requiredPermissions))) {
                // 打破以上任意一个条件,即可把广播发送给静态接收器
                mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                        component.getPackageName());
                skip = true;
            }
        }
    }
    // 跳过当前广播的发送
    if (skip) {
        // ...
        return;
    }

如果这个 reciever 是静态接收器,那么在把广播发送给它之前,首先得进行一大堆的检测。最常见的就是权限,但是这里展示了一段 Android O+ 限制广播发送给静态接收器的限制,有兴趣的读者可以详细分析。

接着看下一步

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // ...
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // ...
    }
    boolean looped = false;
    // 2. 通过 do-while 循环,找到一个现在可以处理的广播
    do {
        final long now = SystemClock.uptimeMillis();
        // 获取一个待处理的广播
        r = mDispatcher.getNextBroadcastLocked(now);
        // ...
    } while (r == null);
    // 走到这里,表示已经获取了一个现在可以处理的广播
    int recIdx = r.nextReceiver++;
    // 3. 在发送广播之前,先发送一个超时消息
    r.receiverTime = SystemClock.uptimeMillis();
    // ...
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        setBroadcastTimeoutLocked(timeoutTime);
    }
    final BroadcastOptions brOptions = r.options;
    // 4. 获取一个 receiver
    final Object nextReceiver = r.receivers.get(recIdx);
    // 5. 如果这个接收器是动态接收器,先把广播发送给它
    // 注意,这里处理的是有序广播发送给动态接收器的情况
    if (nextReceiver instanceof BroadcastFilter) {
        // ...
        return;
    }
    // 走到这里,表示当前的广播接收器,是静态接收器
    ResolveInfo info =
        (ResolveInfo)nextReceiver;
    ComponentName component = new ComponentName(
            info.activityInfo.applicationInfo.packageName,
            info.activityInfo.name);
    boolean skip = false;
    // 6. 检测是否不需要把广播发送给静态接收器
    // ... 省略一大堆的检测代码 ...
    String targetProcess = info.activityInfo.processName;
    ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
            info.activityInfo.applicationInfo.uid);
    // ...
    // 跳过当前广播的发送
    if (skip) {
        // ...
        return;
    }
    // 现在可以把广播发送给静态接收器了
    // ...
    // 7. 静态接收器的进程正在运行,那么就把广播发送给它
    if (app != null && app.getThread() != null && !app.isKilled()) {
        try {
            app.addPackage(info.activityInfo.packageName,
                    info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
            maybeAddAllowBackgroundActivityStartsToken(app, r);
            // 发送广播给广播进程
            processCurBroadcastLocked(r, app);
            // 注意,广播发送给这个静态接收器后,直接结束此次广播的处理
            return;
        } catch (RemoteException e) {
            // ...
        }
    }
}

如果没有限制,那么现在就可以把广播发送给静态接收器。

如果静态接收器所在的进程已经运行了,那么把广播发送给这个进程,这个过程与前面发送广播给动态接收器的过程非常类似,这里就不分析了。

注意,这里把广播发送给一个静态接收器,也是直接 return,懂了吧?

接着往下看

final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
    // ...
    // 把并行广播发送给动态接收器
    while (mParallelBroadcasts.size() > 0) {
        // ...
    }
    // 1. 处理 receiver 进程正在启动的情况
    if (mPendingBroadcast != null) {
        // ...
    }
    boolean looped = false;
    // 2. 通过 do-while 循环,找到一个现在可以处理的广播
    do {
        final long now = SystemClock.uptimeMillis();
        // 获取一个待处理的广播
        r = mDispatcher.getNextBroadcastLocked(now);
        // ...
    } while (r == null);
    // 走到这里,表示已经获取了一个现在可以处理的广播
    int recIdx = r.nextReceiver++;
    // 3. 在发送广播之前,先发送一个超时消息
    r.receiverTime = SystemClock.uptimeMillis();
    // ...
    if (! mPendingBroadcastTimeoutMessage) {
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        setBroadcastTimeoutLocked(timeoutTime);
    }
    final BroadcastOptions brOptions = r.options;
    // 4. 获取一个 receiver
    final Object nextReceiver = r.receivers.get(recIdx);
    // 5. 如果这个接收器是动态接收器,先把广播发送给它
    // 注意,这里处理的是有序广播发送给动态接收器的情况
    if (nextReceiver instanceof BroadcastFilter) {
        // ...
        return;
    }
    // 走到这里,表示当前的广播接收器,是静态接收器
    ResolveInfo info =
        (ResolveInfo)nextReceiver;
    ComponentName component = new ComponentName(
            info.activityInfo.applicationInfo.packageName,
            info.activityInfo.name);
    boolean skip = false;
    // 6. 检测是否不需要把广播发送给静态接收器
    // ...
    // 跳过当前广播的发送
    if (skip) {
        r.delivery[recIdx] = BroadcastRecord.DELIVERY_SKIPPED;
        r.receiver = null;
        r.curFilter = null;
        r.state = BroadcastRecord.IDLE;
        r.manifestSkipCount++;
        // 发送下一个广播
        scheduleBroadcastsLocked();
        return;
    }
    // 现在可以把广播发送给静态接收器了
    // ...
    // 7. 静态接收器的进程正在运行,那么就把广播发送给它
    if (app != null && app.getThread() != null && !app.isKilled()) {
        // ...
    }
    // 8. 静态接收器的进程没有运行,fork it!
    r.curApp = mService.startProcessLocked(targetProcess,
            info.activityInfo.applicationInfo, true,
            r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
            new HostingRecord("broadcast", r.curComponent), isActivityCapable
            ? ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE : ZYGOTE_POLICY_FLAG_EMPTY,
            (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false);
    // 处理 fork 进程失败的情况
    if (r.curApp == null) {
        // ...
        return;
    }
    maybeAddAllowBackgroundActivityStartsToken(r.curApp, r);
    // fork 进程成功,保存广播数据,等待进程起来后,再处理这个广播
    mPendingBroadcast = r;
    mPendingBroadcastRecvIndex = recIdx;
}

刚才已经处理了静态接收器的进程存在的情况,那么现在处理进程不存在的情况,因此首先得 fork 进程。当成功 fork 进程后,保存待发送的广播的数据,例如,用 mPendingBroadcast 保存广播,然后当进程启动时,与 AMS 进行 attach application 时,会自动把广播发送给该进程。这个过程后面会分析。

注意,此时函数已经结束,而广播正在发送给一个正在启动的进程。很显然,需要等待这个广播的处理结果,才能继续下一个广播的发送,这也符合“串行”广播的定义。

广播发送给正在启动的进程

刚才,我们分析到一个过程,当静态接收器所在的进程没有启动的时候,首先 fork 进程,那么广播之后是如何发送给进程的呢?

首先,我们知道当进程启动后,会执行 attach application 过程,最终会调用 AMS 如下方法

// ActivityManagerService.java
    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
        // ...
        try {
            // ...
            if (app.getIsolatedEntryPoint() != null) {
                // ...
            } else if (instr2 != null) {
                // ...
            } else {
                // 初始化进程环境
                thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
                        new Configuration(app.getWindowProcessController().getConfiguration()),
                        app.getCompat(), getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, autofillOptions, contentCaptureOptions,
                        app.getDisabledCompatChanges(), serializedSystemFontMap);
            }
            // ...
        } catch (Exception e) {
            // ...
        }
        // ....
        // 处理正在等待宿主进程起来的广播
        if (!badApp && isPendingBroadcastProcessLocked(pid)) {
            try {
                // 发送队列中正在等待进程起来的广播
                didSomething |= sendPendingBroadcastsLocked(app);
                checkTime(startTime, "attachApplicationLocked: after sendPendingBroadcastsLocked");
            } catch (Exception e) {
                // ...
            }
        }
        // ...
        return true;
    }
    boolean sendPendingBroadcastsLocked(ProcessRecord app) {
        boolean didSomething = false;
        for (BroadcastQueue queue : mBroadcastQueues) {
            didSomething |= queue.sendPendingBroadcastsLocked(app);
        }
        return didSomething;
    }

看到了,AMS 首先对进程进行了初始化,然后就会把等待进程启动的广播,发送给它。

// BroadcastQueue.java
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
    boolean didSomething = false;
    // mPendingBroadcast 保存的就是等待进程启动启动后,需要发送的广播。
    final BroadcastRecord br = mPendingBroadcast;
    if (br != null && br.curApp.getPid() > 0 && br.curApp.getPid() == app.getPid()) {
        if (br.curApp != app) {
            Slog.e(TAG, "App mismatch when sending pending broadcast to "
                    + app.processName + ", intended target is " + br.curApp.processName);
            return false;
        }
        try {
            mPendingBroadcast = null;
            // 发送广播给进程
            processCurBroadcastLocked(br, app);
            didSomething = true;
        } catch (Exception e) {
            // ...
        }
    }
    return didSomething;
}

mPendingBroadcast 保存的就是等待进程启动启动后,需要发送的广播。现在进程已经启动,立即发送广播

// BroadcastQueue.java
private final void processCurBroadcastLocked(BroadcastRecord r,
        ProcessRecord app) throws RemoteException {
    final IApplicationThread thread = app.getThread();
    if (thread == null) {
        throw new RemoteException();
    }
    if (app.isInFullBackup()) {
        skipReceiverLocked(r);
        return;
    }
    // 更新正在处理广播的 receiver 数据
    r.receiver = thread.asBinder();
    r.curApp = app;
    // 保存当前正在运行的 receiver
    final ProcessReceiverRecord prr = app.mReceivers;
    prr.addCurReceiver(r);
    app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
    mService.updateLruProcessLocked(app, false, null);
    mService.enqueueOomAdjTargetLocked(app);
    mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER);
    r.intent.setComponent(r.curComponent);
    boolean started = false;
    try {
        mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
                                  PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
        // 通知进程启动 receiver 来处理广播
        thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
                mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo),
                r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
                app.mState.getReportedProcState());
        started = true;
    } finally {
        if (!started) {
            // ...
        }
    }
}

现在 AMS 通知 receiver 所在的进程来处理广播

// ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
    private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
    public final void scheduleReceiver(Intent intent, ActivityInfo info,
            CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
            boolean sync, int sendingUser, int processState) {
        updateProcessState(processState, false);
        // 广播数据包装成 ReceiverData
        ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                sync, false, mAppThread.asBinder(), sendingUser);
        r.info = info;
        r.compatInfo = compatInfo;
        sendMessage(H.RECEIVER, r);
    }

最终调用 handleReceiver() 处理广播数据

private void handleReceiver(ReceiverData data) {
    unscheduleGcIdler();
    String component = data.intent.getComponent().getClassName();
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    IActivityManager mgr = ActivityManager.getService();
    Application app;
    BroadcastReceiver receiver;
    ContextImpl context;
    try {
        // 1. 创建 Application 对象,并调用 Application#onCreate()
        app = packageInfo.makeApplication(false, mInstrumentation);
        // ...
        // 2. 创建 BroadcastReceiver 对象
        receiver = packageInfo.getAppFactory()
                .instantiateReceiver(cl, data.info.name, data.intent);
    } catch (Exception e) {
        // ...
    }
    try {
        sCurrentBroadcastIntent.set(data.intent);
        receiver.setPendingResult(data);
        // 3. 执行 BroadcastReceiver#onReceive()
        receiver.onReceive(context.getReceiverRestrictedContext(),
                data.intent);
    } catch (Exception e) {
        // ...
    } finally {
        sCurrentBroadcastIntent.set(null);
    }
    // 4. 返回广播的处理结果给 AMS
    if (receiver.getPendingResult() != null) {
        data.finish();
    }
}

这里的过程很清晰明了吧,直接看最后一步,把广播的处理结果反馈给 AMS

// BroadcastReceiver.java
public final void finish() {
    if (mType == TYPE_COMPONENT) {
        final IActivityManager mgr = ActivityManager.getService();
        if (QueuedWork.hasPendingWork()) {
            // ...
        } else {
            sendFinished(mgr);
        }
    } else if (mOrderedHint &amp;&amp; mType != TYPE_UNREGISTERED) {
        // ...
    }
}
public void sendFinished(IActivityManager am) {
    synchronized (this) {
        if (mFinished) {
            throw new IllegalStateException("Broadcast already finished");
        }
        mFinished = true;
        try {
            if (mResultExtras != null) {
                mResultExtras.setAllowFds(false);
            }
            if (mOrderedHint) {
                // 有序广播的反馈
                am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
                        mAbortBroadcast, mFlags);
            } else {
                // 非有序广播的费奎
                am.finishReceiver(mToken, 0, null, null, false, mFlags);
            }
        } catch (RemoteException ex) {
        }
    }
}

现在看下 AMS 如何处理这个反馈的结果

// ActivityManagerService.java
public void finishReceiver(IBinder who, int resultCode, String resultData,
        Bundle resultExtras, boolean resultAbort, int flags) {
    // ...
    final long origId = Binder.clearCallingIdentity();
    try {
        boolean doNext = false;
        BroadcastRecord r;
        BroadcastQueue queue;
        synchronized(this) {
            if (isOnOffloadQueue(flags)) {
                queue = mOffloadBroadcastQueue;
            } else {
                queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0
                        ? mFgBroadcastQueue : mBgBroadcastQueue;
            }
            // 1. 匹配进程正在处理的广播
            r = queue.getMatchingOrderedReceiver(who);
            // 2. 完成当前 receiver 广播的处理流程
            if (r != null) {
                doNext = r.queue.finishReceiverLocked(r, resultCode,
                    resultData, resultExtras, resultAbort, true);
            }
            // 3. 发送广播给下一个 receiver,或者发送下一个广播
            if (doNext) {
                r.queue.processNextBroadcastLocked(/*fromMsg=*/ false, /*skipOomAdj=*/ true);
            }
            // updateOomAdjLocked() will be done here
            trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}

看到了,只有当前 receiver 处理完广播,才会发送广播给下一个 receiver,这就是“串行”广播的本质。

广播 ANR

最后,来探讨一个广播 ANR 的原理,本来我以为很简单的,就是发送一个超时消息嘛。但是当我细看的时候,我发现这个 ANR 设计的很巧妙,我觉得我们可以学习下,因此这里单独拿出来分析。

这里,我得提醒大家一点,只有“串行”广播才会发生 ANR,因为它要等待 receiver 的处理结果。

根据前面分析,“串行”广播发送给 receiver 前,会发送一个超时消息,如下

// BroadcastQueue.java
if (! mPendingBroadcastTimeoutMessage) {
    long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
    setBroadcastTimeoutLocked(timeoutTime);
}

当这个消息被执行的时候,会调用如下代码

// BroadcastQueue.java
final void broadcastTimeoutLocked(boolean fromMsg) {
    if (fromMsg) {
        mPendingBroadcastTimeoutMessage = false;
    }
    if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) {
        return;
    }
    long now = SystemClock.uptimeMillis();
    // 获取当前正在处理的广播
    BroadcastRecord r = mDispatcher.getActiveBroadcastLocked();
    if (fromMsg) {
        // 系统还没有就绪
        if (!mService.mProcessesReady) {
            return;
        }
        // 广播超时被豁免
        if (r.timeoutExempt) {
            if (DEBUG_BROADCAST) {
                Slog.i(TAG_BROADCAST, "Broadcast timeout but it's exempt: "
                        + r.intent.getAction());
            }
            return;
        }
        // 1. 广播没有超时
        long timeoutTime = r.receiverTime + mConstants.TIMEOUT;
        if (timeoutTime > now) {
            // 发送下一个超时消息
            setBroadcastTimeoutLocked(timeoutTime);
            return;
        }
    }
    if (r.state == BroadcastRecord.WAITING_SERVICES) {
        // ...
        return;
    }
    // 2. 走到这里,表示广播超时,触发 ANR
    final boolean debugging = (r.curApp != null && r.curApp.isDebugging());
    r.receiverTime = now;
    if (!debugging) {
        r.anrCount++;
    }
    ProcessRecord app = null;
    String anrMessage = null;
    Object curReceiver;
    if (r.nextReceiver > 0) {
        curReceiver = r.receivers.get(r.nextReceiver-1);
        r.delivery[r.nextReceiver-1] = BroadcastRecord.DELIVERY_TIMEOUT;
    } else {
        curReceiver = r.curReceiver;
    }
    logBroadcastReceiverDiscardLocked(r);
    // 获取 receiver 进程
    if (curReceiver != null && curReceiver instanceof BroadcastFilter) {
        BroadcastFilter bf = (BroadcastFilter)curReceiver;
        if (bf.receiverList.pid != 0
                && bf.receiverList.pid != ActivityManagerService.MY_PID) {
            synchronized (mService.mPidsSelfLocked) {
                app = mService.mPidsSelfLocked.get(
                        bf.receiverList.pid);
            }
        }
    } else {
        app = r.curApp;
    }
    if (app != null) {
        anrMessage = "Broadcast of " + r.intent.toString();
    }
    if (mPendingBroadcast == r) {
        mPendingBroadcast = null;
    }
    // 强制结束当前广播的发送流程
    finishReceiverLocked(r, r.resultCode, r.resultData,
            r.resultExtras, r.resultAbort, false);
    // 调度下一次的广播发送
    scheduleBroadcastsLocked();
    // app 不处于 debug 模式,引发 ANR
    if (!debugging && anrMessage != null) {
        mService.mAnrHelper.appNotResponding(app, anrMessage);
    }
}

第2步,引发 ANR ,很简单,就是因为整个发送与反馈过程超时了。

而第1步,就是处理不超时的情况。这里大家是不是很疑惑,移除超时消息不是在接收到广播反馈后进行的吗? 我可以负责地告诉你,并不是!那这里第1步怎么理解呢?

首先这个超时消息一定触发,但是触发这个超时消息,并不代表一定会引发 ANR。

假如当前 receiver 的广播处理流程,在超时时间之前就完成了,那么 AMS 会调度广播发送给下一个 receiver。

于是,针对下一个 receiver ,会更新 r.receiverTime,那么第一步此时计算出来的 timeoutTime 是下一个 receiver 的广播超时时间,很显然是大于 now 的,于是就不会走第2步的 ANR 流程。

最后利用这个 timeoutTime,为下一个 receiver 再发送一个超时消息,简直是完美!

至于为何不在广播反馈的时候,移除这个超时消息,我心中有一点小小的想法,但是也不能确定是不是这个原因,才这样设计的。不过,对我来说,这一招,我算是学会了。

最后,提醒读者,广播接收器是在主线程中运行的,不要执行耗时任务,或者潜在耗时的任务,我在工作中看到了无数血与泪的案例。

结束

本文从整体上分析了“串行”和“并行”广播的发送流程,并以此为基础,解析了“串行”广播的 ANR 原理。

但是,还有一些广播的细节,我并没有分析,例如 Android O+ 如何限制广播发送给静态接收器,又例如,什么情况下,会把广播延迟发送给 app。只要你站在我的肩膀上,就可以自行分析这些细节。

另外,我在分析的时候,有个优化广播发送的想法,如果广播有多个app的静态接收器,我可以建立一个机制,优先把广播发送给某些 app,并且对于这些 app,我不需要等待它反馈广播处理结果,就可以发送广播给下一个接收器。如果以后工作有需要,我会尝试做一做,更多关于ActivityManagerService广播的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android 广播接收器BroadcastReceiver详解

    目录 一.什么是BroadcastReceiver 1.1.作用 1.2.实现原理 二.创建广播接收器 三.注册广播接收器 3.1.静态注册 注册 发送通知 3.2.动态注册 四.系统广播 总结 一.什么是BroadcastReceiver BroadcastReceiver 是安卓系统中四大组件之一,在Android开发中,BroadcastReceiver的应用场景非常多,Android 广播分为两个角色:广播发送者.广播接收者. 1.1.作用 广播接收器用于响应来自其他应用程序或者系统的广

  • android studio广播机制使用详解

    Intent 是一种消息传播机制,用于组件之间数据交换和发送广播消息.通过本次实验了解 Android 系统的组件通信原理,掌握利用 Intent 启动其他组件的方法,以及利用 Intent 获取信息和发送广播消息的方法. 1.实现具有“登录”按钮的主界面,输入用户名.密码,点击登录按钮后,经过判断进入一个广播Activity(需要传递主界面的用户名) 2.在广播Activity中,输入信息,点击发送广播按钮发送广播,并且在广播接收器中接收广播并显示. activity.xml <?xml ve

  • Android广播实现App开机自启动

    本文实例为大家分享了Android广播实现App开机自启动的具体代码,供大家参考,具体内容如下 一.概括 在安卓中,想要实现app开机自动启动,需要实现拦截广播android.permission.RECEIVE_BOOT_COMPLETED,并且需要使用静态注册广播的方法(即在AndroidManifest.xml文件中定义广播): 二.步骤 1. 先在AndroidManifest.xml文件中定义广播和声明权限: <uses-permission android:name="andr

  • ActivityManagerService之Service启动过程解析

    目录 缘由 启动 Service 宿主进程的启动 宿主进程创建 Service Service 接收参数 结束 缘由 我曾经任职于一家小公司,负责上层一切事务,而公司为了给客户(尤其是小客户)提供开发的便利,会强行去掉一些限制,其中就包括启动 Service 的限制. 本文来分析 Service 的整体启动流程,顺带也会提一提 Service 启动的一些限制.但是,读者请务必自行了解 Service 的启动方式,包括前台 Service 的启动,这些基本知识本文不会过多提及. 启动 Servic

  • Android ActivityManagerService启动流程详解

    目录 概述 AMS的启动流程 启动流程图 概述 AMS是系统的引导服务,应用进程的启动.切换和调度.四大组件的启动和管理都需要AMS的支持.从这里可以看出AMS的功能会十分的繁多,当然它并不是一个类承担这个重责,它有一些关联类. AMS的启动流程 1:SystemServer#main -> 2:SystemServer#run -> 3:SystemServiceManager#startBootstrapServices 1:首先SystemServer进程运行main函数, main函数

  • ActivityManagerService广播注册与发送示例解析

    目录 引言 注册广播接收器 发送广播 结束 引言 最近,帮同事解决了两个问题,一个问题是 app 接收开机广播的速度太慢,另一个问题是app有时无法接收到广播.同事不知道如何解决这个问题,是因为他们不了解广播发送超时的原理. 很早的时候,我就研究过广播的代码,由于工作比较忙,再加上我这个人比较懒,因此没有写成文章.由于最近这段时间,工作和生活都不是很如意,于是我想静下心来写写东西,因此就有了这篇文章. 在看本文之前,请读者自行了解 普通的广播.粘性(sticky)广播.有序广播 的使用. 注册广

  • ActivityManagerService广播并行发送与串行发送示例解析

    目录 "并行"广播的发送 “串行”广播的发送 广播发送给正在启动的进程 广播 ANR 结束 "并行"广播的发送 本文以 ActivityManagerService之广播(1): 注册与发送 为基础,分析“串行”和“并行”广播的发送流程,并介绍广播 ANR 的原理. // 1. 获取广播队列 final BroadcastQueue queue = broadcastQueueForIntent(intent); // 2. 创建广播记录 BroadcastReco

  • JDK8并行流及串行流区别原理详解

    由于处理器核心的增长及较低的硬件成本允许低成本的集群系统,致使如今并行编程无处不在,并行编程似乎是下一个大事件. Java 8 针对这一事实提供了新的 stream API 及简化了创建并行集合和数组的代码.让我们看一下它是怎么工作的. 假设 myList 是 List<Integer> 类型的,其中包含 500,000 个Integer值.在Java 8 之前的时代中,对这些整数求和的方法是使用 for 循环完成的. for( int i : myList){ result += i; }

  • JavaScript中Promise处理异步的并行与串行

    目录 一.异步的“并行” 并行中的综合处理 二.异步的“串行”: 2.1 then链机制处理 2.2 真实项目中,想实现异步的串行,我们一般使用async+await 2.3 promise.then(onfulfilled,onrejected) 在内存中的执行 三.aysnc修饰符 四.await:等待 await中的异步 五.思考题 思考题1 思路及图解 思考题2 思路及图解 思考题3 思路及图解 总结 一.异步的“并行” 同时处理,相互之间没啥依赖 // 执行FN1返回一个promise

  • 详解IOS串行队列与并行队列进行同步或者异步的实例

    详解IOS串行队列与并行队列进行同步或者异步的实例 IOS中GCD的队列分为串行队列和并行队列,任务分为同步任务和异步任务,他们的排列组合有四种情况,下面分析这四种情况的工作方式. 同步任务,使用GCD dispatch_sync 进行派发任务 - (void)testSync { dispatch_queue_t serialQueue = dispatch_queue_create("com.zyt.queue", DISPATCH_QUEUE_SERIAL); dispatch_

  • C++ 线程(串行 并行 同步 异步)详解

    C++  线程(串行 并行 同步 异步)详解 看了很多关于这类的文章,一直没有总结.不总结的话就会一直糊里糊涂,以下描述都是自己理解的非官方语言,不一定严谨,可当作参考. 首先,进程可理解成一个可执行文件的执行过程.在ios app上的话我们可以理解为我们的app的.ipa文件执行过程也即app运行过程.杀掉app进程就杀掉了这个app在系统里运行所占的内存. 线程:线程是进程的最小单位.一个进程里至少有一个主线程.就是那个main thread.非常简单的app可能只需要一个主线程即UI线程.

  • python实现测试工具(一)——命令行发送get请求

    本系列教程我们将使用python实现一些简单的测试工具,为了尽可能的简单,我们的工具以命令行工具为主. 本系列教程使用的python版本是3.6.3. 背景 这一节我们实现简单的命令行发送get请求的工具,使用方式如下: python get.py www.v2ex.com/api/nodes/show.json\?name\=python 接口地址: http://www.v2ex.com/api/nodes/show.json?name=python 状态码: 200 Headers: Da

  • 详解Node.js串行化流程控制

    串行任务:需要一个接着一个坐的任务叫做串行任务. 可以使用回调的方式让几个异步任务按顺序执行,但如果任务过多,必须组织一下,否则过多的回调嵌套会把代码搞得很乱. 为了用串行化流程控制让几个异步任务按顺序执行,需要先把这些任务按预期的执行顺序放到一个数组中,这个数组将起到队列的作用:完成一个任务后按顺序从数组中取出下一个. 数组中的每个任务都是一个函数.任务完成后应该调用一个处理器函数,告诉它错误状态和结果. 为了演示如何实现串行化流程控制,我们准备做个小程序,让它从一个随机选择的RSS预定源中获

  • Python3 pickle对象串行化代码实例解析

    1.pickle对象串行化 pickle模块实现了一个算法可以将任意的Python对象转换为一系列字节.这个过程也被称为串行化对象.可以传输或存储表示对象的字节流,然后再重新构造来创建有相同性质的新对象. 1.1 编码和解码字符串中的数据 第一个例子使用dumps()将一个数据结构编码为一个字符串,然后把这个字符串打印到控制台.它使用了一个完全由内置类型构成的数据结构.任何类的实例都可以pickled,如后面的例子所示. import pickle import pprint data = [{

  • spring定时任务(scheduler)的串行、并行执行实现解析

    对于spring的定时任务,最近有接触过一些,对于串行和并行也学习了一下,现在这里做下记录. 我是把每个定时任务分别写在不同的类中的,即一个类就是一个定时任务,然后在spring配置文件中进行配置,首先说串行任务的配置.如下: 1.串行 <task:scheduled-tasks> <task:scheduled ref="className1" method="methodName1" cron="0 0/5 * * * ?"

随机推荐