本文出自门心叼龙的博客,属于原创类容,转载请注明出处。
BroadcastReceiver在Android四大组件中排行老三,它是一个广播接收器,用于系统中不同组件之间的通信,类似于事件编程中的事件监听器,只不过事件编程中监听的对象是控件,而广播接收器监听的对象是系统中的组件。广播分为普通广播,有序广播和粘性广播,本文主要研究普通广播的工作过程,包括广播接收器的注册、广播的发送、广播的接受背后的工作原理,其他两种类型都是类似的,大家可以触类旁通。
BroadcastReceiver的基本用法
首先需要定义一个广播接收器,如下所示:
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Log.v("MYTAG","onReceive start...");
}
}
定义一个广播接收器的过程很简单,直接继承BroadcastReceiver这个抽象类,并实现它的抽象方法onReceive即可。广播接收器定义完毕,接下来的工作是注册广播,广播的注册分为动态注册和静态注册,大家注意了BroadcastReveiver是四大组件中唯一可以动态注册的组件,静态注册比较简单直接可以在清单文件AndroidMinifest.xml中通过receiver标签完成,我们主要看动态注册:
registerReceiver(new MyReceiver(),new IntentFilter("com.test.receiver"));
直接调用Context给我们提供的registerReceiver即可完成广播接收器的注册,该方法需要传入两个参数,第一个参数就是我们前面所定义的广播接收器MyReceiver对象,第二个参数是一个IntentFilter,它主要用来过滤广播的。广播接收器注册完成,最后一步就是发送广播了,代码实现如下:
sendBroadcast(new Intent("com.test.receiver"));
发送之后广播接收器MyReceiver就可以收到消息了,onReceive的方法响应了,打印日志如下:
2019-11-18 09:22:35.522 2721-2721/com.mxdl.customview V/MYTAG: onReceive start...
这样我们就轻轻轻松的体验了一把BroadcastReceiver的基本用法,接下来我们来看广播接收器的注册,广播发送,以及广播的接收他们背后的工作原理。
BroadcastReceiver的注册过程
ContextImpl中的流程
注册过程是从ContextWraper的registerReceiver方法开始的,如下所示:
@Override
public Intent registerReceiver(
BroadcastReceiver receiver, IntentFilter filter) {
return mBase.registerReceiver(receiver, filter);
}
ContextWraper的registerReceiver方法很简单只有一行,ContextWraper什么都没有做,就把注册的工作交给了mBase,mBase在前面几篇文章我们多次提到过,在这里就不在重复讲解了,它就是ContextImpl,接下来,我们看ContextImpl的registerReceiver方法的实现:
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return registerReceiver(receiver, filter, null, null);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
return registerReceiverInternal(receiver, getUserId(),
filter, broadcastPermission, scheduler, getOuterContext(), 0);
}
registerReceiver方法回重载调用一次,然后再调用自己的registerReceiverInternal方法,该方法实现如下:
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context, int flags) {
IIntentReceiver rd = null;
if (receiver != null) {
if (mPackageInfo != null && context != null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
//注释1
rd = new LoadedApk.ReceiverDispatcher(
receiver, context, scheduler, null, true).getIIntentReceiver();
}
}
try {
//注释2
final Intent intent = ActivityManager.getService().registerReceiver(
mMainThread.getApplicationThread(), mBasePackageName, rd, filter,
broadcastPermission, userId, flags);
if (intent != null) {
intent.setExtrasClassLoader(getClassLoader());
intent.prepareToEnterProcess();
}
return intent;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我们主要看注释1处,将传递过来的receiver转化为了IIntentReceiver,我们看ReceiverDispatcher的getIIntentReceiver方法返回的到底是什么,如下所示:
final IIntentReceiver.Stub mIIntentReceiver;
@UnsupportedAppUsage
IIntentReceiver getIIntentReceiver() {
return mIIntentReceiver;
}
一目了然,返回的是一个IIntentReceiver.Stub类型的变量mIIntentReceiver,我看mIIntentReceiver是在什么时候初始化的,经过变量跟踪我们可以知道,就是在ReceiverDispatcher对象实例化的时候就赋值了,代码实现如下:
ReceiverDispatcher(BroadcastReceiver receiver, Context context,
Handler activityThread, Instrumentation instrumentation,
boolean registered) {
if (activityThread == null) {
throw new NullPointerException("Handler must not be null");
}
//注释1
mIIntentReceiver = new InnerReceiver(this, !registered);
mReceiver = receiver;
mContext = context;
mActivityThread = activityThread;
mInstrumentation = instrumentation;
mRegistered = registered;
mLocation = new IntentReceiverLeaked(null);
mLocation.fillInStackTrace();
}
在注释1处mIIntentReceiver变量被初始化了,它的具体实现就是InnerReceiver,它的继承关系如下:
final static class InnerReceiver extends IIntentReceiver.Stub
到了这一步我们也就明白了,客户端会将BroadcastReceiver对象转换为一个ReceiverDispatcher.InnerReceiver对象,之所以不能直接传递BroadcastReceiver对象是因为广播的发送有可能是跨进程的,BroadcastReceiver只能借助于Binder对象才能在服务端回调自己的方法,而ReceiverDispatcher的内部类InnerReceiver正好充当了Binder这个角色。ReceiverDispatcher同时持有BroadcastReceiver和InnerReceiver,
InnerReceiver反向持有ReceiverDispatcher。我们有没有发现,这和上一篇我们在学习Service绑定的时候是一个套路,ServiceConnetion对象也是被转化为ServiceDispatcher.InnerConnection对象传递的。
回过头我们再看ContextImple对象的registerReceiverInternal方法的注释2处调用了ActivityManager.getService()对象的registerReceiver方法此时把InnerReceiver对象传递给给服务端了,ActivityManager.getService()对象不用多想就是ActivityManagerService对象,现在我们看registerReceiver方法的实现,如下所示:
ActivityManagerService中的流程
public Intent registerReceiver(IApplicationThread caller, String callerPackage,
IIntentReceiver receiver, IntentFilter filter, String permission, int userId,
int flags) {
...
if (rl == null) {
rl = new ReceiverList(this, callerApp, callingPid, callingUid,
userId, receiver);
if (rl.app != null) {
final int totalReceiversForApp = rl.app.receivers.size();
if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
throw new IllegalStateException("Too many receivers, total of "
+ totalReceiversForApp + ", registered for pid: "
+ rl.pid + ", callerPackage: " + callerPackage);
}
rl.app.receivers.add(rl);
} else {
try {
receiver.asBinder().linkToDeath(rl, 0);
} catch (RemoteException e) {
return sticky;
}
rl.linkedToDeath = true;
}
//注释1
mRegisteredReceivers.put(receiver.asBinder(), rl);
} else if (rl.uid != callingUid) {
throw new IllegalArgumentException(
"Receiver requested to register for uid " + callingUid
+ " was previously registered for uid " + rl.uid
+ " callerPackage is " + callerPackage);
} else if (rl.pid != callingPid) {
throw new IllegalArgumentException(
"Receiver requested to register for pid " + callingPid
+ " was previously registered for pid " + rl.pid
+ " callerPackage is " + callerPackage);
} else if (rl.userId != userId) {
throw new IllegalArgumentException(
"Receiver requested to register for user " + userId
+ " was previously registered for user " + rl.userId
+ " callerPackage is " + callerPackage);
}
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
permission, callingUid, userId, instantApp, visibleToInstantApps);
if (rl.containsFilter(filter)) {
Slog.w(TAG, "Receiver with filter " + filter
+ " already registered for pid " + rl.pid
+ ", callerPackage is " + callerPackage);
} else {
rl.add(bf);
if (!bf.debugCheck()) {
Slog.w(TAG, "==> For Dynamic broadcast");
}
//注释2
mReceiverResolver.addFilter(bf);
}
...
服务端的registerReceiver是实现广播注册的真正方法,该方法很长我们只看重要的部分,在注释1处将客户端传递过来的广播接收器InnerReceiver保存起来,在最后的注释2处将客户端传递过来的IntentFilter也保存起来,这样整个广播注册过程就完成了。
广播的发送和接收
ContextImpl中的流程
接下来我们看广播的发送和接收过程,和广播注册一样,广播发送也是从ContextWraper的sendBroadcast方法开始的,方法如下:
@Override
public void sendBroadcast(Intent intent) {
mBase.sendBroadcast(intent);
}
该方法里面什么也没有做,直接就把发送的工作交给了ContextImple的sendBroadcast方法了,该方法实现如下:
@Override
public void sendBroadcast(Intent intent) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
try {
intent.prepareToLeaveProcess(this);
//注释1
ActivityManager.getService().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
ActivityManagerService中的流程
在注释1处调用了ActivityManagerService的broadcastIntent方法,具体的代码实现如下所示:
public final int broadcastIntent(IApplicationThread caller,
Intent intent, String resolvedType, IIntentReceiver resultTo,
int resultCode, String resultData, Bundle resultExtras,
String[] requiredPermissions, int appOp, Bundle bOptions,
boolean serialized, boolean sticky, int userId) {
enforceNotIsolatedCaller("broadcastIntent");
synchronized(this) {
intent = verifyBroadcastLocked(intent);
final ProcessRecord callerApp = getRecordForAppLocked(caller);
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
//注释1
return broadcastIntentLocked(callerApp,
callerApp != null ? callerApp.info.packageName : null,
intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
requiredPermissions, appOp, bOptions, serialized, sticky,
callingPid, callingUid, callingUid, callingPid, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
ActivityManagerService的broadcastIntent方法也不长,在方法的最后会调用它的broadcastIntentLocked方法,该方法会被重载调用一次,broadcastIntentLocked方法的具体实现如下:
@GuardedBy("this")
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid, int realCallingUid,
int realCallingPid, int userId, boolean allowBackgroundActivityStarts) {
...
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
// If we are not serializing this broadcast, then send the
// registered receivers separately so they don't wait for the
// components to be launched.
if (isCallerSystem) {
checkBroadcastFromSystem(intent, callerApp, callerPackage, callingUid,
isProtectedBroadcast, registeredReceivers);
}
//注释1
final BroadcastQueue queue = broadcastQueueForIntent(intent);
BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
callerPackage, callingPid, callingUid, callerInstantApp, resolvedType,
requiredPermissions, appOp, brOptions, registeredReceivers, resultTo,
resultCode, resultData, resultExtras, ordered, sticky, false, userId,
allowBackgroundActivityStarts, timeoutExempt);
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing parallel broadcast " + r);
final boolean replaced = replacePending
&& (queue.replaceParallelBroadcastLocked(r) != null);
// Note: We assume resultTo is null for non-ordered broadcasts.
if (!replaced) {
queue.enqueueParallelBroadcastLocked(r);
//注释2
queue.scheduleBroadcastsLocked();
}
registeredReceivers = null;
NR = 0;
}
...
这个方法的很长,但是他只做了一件事,通过传递过来的Intent查找相匹配的的BroadcastReceiver并把它放入到广播队列BroadcastQueue中,如注释1所示,接下来会调用注释2处的scheduleBroadcastsLocked方法执行广播发送任务,下面是BroadcastQueue的scheduleBroadcastsLocked方法的具体实现:
BroadcastQueue中的流程
public void scheduleBroadcastsLocked() {
if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["
+ mQueueName + "]: current="
+ mBroadcastsScheduled);
if (mBroadcastsScheduled) {
return;
}
mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
mBroadcastsScheduled = true;
}
该方法的逻辑实现也不是很长,就向消息管理器mHandler发送了一个BROADCAST_INTENT_MSG类型的消息,mHandler收到消息后回调它自己的processNextBroadcast方法,processNextBroadcast方法又会调用processNextBroadcastLocked方法,它的具体实现如下:
final void processNextBroadcastLocked(boolean fromMsg, boolean skipOomAdj) {
BroadcastRecord r;
...
// First, deliver any non-serialized broadcasts right away.
while (mParallelBroadcasts.size() > 0) {
r = mParallelBroadcasts.remove(0);
r.dispatchTime = SystemClock.uptimeMillis();
r.dispatchClockTime = System.currentTimeMillis();
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_PENDING),
System.identityHashCode(r));
Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
createBroadcastTraceTitle(r, BroadcastRecord.DELIVERY_DELIVERED),
System.identityHashCode(r));
}
final int N = r.receivers.size();
if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG_BROADCAST, "Processing parallel broadcast ["
+ mQueueName + "] " + r);
for (int i=0; i
这个方法的实现还是很长的,我么主要看注释1处,它调用了deliverToRegisteredReceiverLocked
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
BroadcastFilter filter, boolean ordered, int index) {
try {
if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
"Delivering to " + filter + " : " + r);
if (filter.receiverList.app != null && filter.receiverList.app.inFullBackup) {
// Skip delivery if full backup in progress
// If it's an ordered broadcast, we need to continue to the next receiver.
if (ordered) {
skipReceiverLocked(r);
}
} else {
r.receiverTime = SystemClock.uptimeMillis();
maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
//注释1
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 (r.allowBackgroundActivityStarts && !r.ordered) {
postActivityStartTokenRemoval(filter.receiverList.app, r);
}
}
if (ordered) {
r.state = BroadcastRecord.CALL_DONE_RECEIVE;
}
}
}
紧接着会调用注释1处的performReceiveLocked方法,该方法实现如下:
void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
Intent intent, int resultCode, String data, Bundle extras,
boolean ordered, boolean sticky, int sendingUser)
throws RemoteException {
// Send the intent to the receiver asynchronously using one-way binder calls.
if (app != null) {
if (app.thread != null) {
// If we have an app thread, do the call through that so it is
// correctly ordered with other one-way calls.
try {
//注释1
app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
data, extras, ordered, sticky, sendingUser, app.getReportedProcState());
// TODO: Uncomment this when (b/28322359) is fixed and we aren't getting
// DeadObjectException when the process isn't actually dead.
//} catch (DeadObjectException ex) {
// Failed to call into the process. It's dying so just let it die and move on.
// throw ex;
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
synchronized (mService) {
Slog.w(TAG, "Can't deliver broadcast to " + app.processName
+ " (pid " + app.pid + "). Crashing it.");
app.scheduleCrash("can't deliver broadcast");
}
throw ex;
}
} else {
// Application has died. Receiver doesn't exist.
throw new RemoteException("app.thread must not be null");
}
} else {
receiver.performReceive(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
}
}
最终会执行app.thread对象的scheduleRegisteredReceiver方法,app.thread对象是ActivityThread的内部类对象ApplicationThread,这个在前面几篇文章多次提到过,在这里也就不做过多的解释,我们继续跟进ApplicationThread对象的scheduleRegisteredReceiver方法,它的实现如下:
ApplicationThread中的流程
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
int resultCode, String dataStr, Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState) throws RemoteException {
updateProcessState(processState, false);
receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
sticky, sendingUser);
}
在客户端更新进程状态之后经过短暂的停留,随着receiver对象的performReceive的调用流程再次回到服务端进程,receiver是不是熟悉?就是在前面我们在讲ContextImpl对象的registerReceiverInternal方法的时候所提到的ReceiverDispatcher.InnerReceiver对象,下面我们来看看InnerReceiver对象的performReceive方法的实现,如下所示:
InnerReceiver中的流程
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
final LoadedApk.ReceiverDispatcher rd;
if (intent == null) {
Log.wtf(TAG, "Null intent received");
rd = null;
} else {
rd = mDispatcher.get();
}
if (ActivityThread.DEBUG_BROADCAST) {
int seq = intent.getIntExtra("seq", -1);
Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction()
+ " seq=" + seq + " to " + (rd != null ? rd.mReceiver : null));
}
if (rd != null) {
//注释1
rd.performReceive(intent, resultCode, data, extras,
ordered, sticky, sendingUser);
} else {
// The activity manager dispatched a broadcast to a registered
// receiver in this process, but before it could be delivered the
// receiver was unregistered. Acknowledge the broadcast on its
// behalf so that the system's broadcast sequence can continue.
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing broadcast to unregistered receiver");
IActivityManager mgr = ActivityManager.getService();
try {
if (extras != null) {
extras.setAllowFds(false);
}
mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
在performReceive方法的注释1处会调用ReceiverDispatcher对象的performReceive方法,该方法如下所示:
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
final Args args = new Args(intent, resultCode, data, extras, ordered,
sticky, sendingUser);
if (intent == null) {
Log.wtf(TAG, "Null intent received");
} else {
if (ActivityThread.DEBUG_BROADCAST) {
int seq = intent.getIntExtra("seq", -1);
Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction()
+ " seq=" + seq + " to " + mReceiver);
}
}
//注释1
if (intent == null || !mActivityThread.post(args.getRunnable())) {
if (mRegistered && ordered) {
IActivityManager mgr = ActivityManager.getService();
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing sync broadcast to " + mReceiver);
args.sendFinished(mgr);
}
}
}
在注释1处给ActivityThread的消息管理器发送一个args.getRunnable类型回调的消息,下面就是该回调的具体实现:
public final Runnable getRunnable() {
return () -> {
final BroadcastReceiver receiver = mReceiver;
final boolean ordered = mOrdered;
if (ActivityThread.DEBUG_BROADCAST) {
int seq = mCurIntent.getIntExtra("seq", -1);
Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction()
+ " seq=" + seq + " to " + mReceiver);
Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered
+ " mOrderedHint=" + ordered);
}
final IActivityManager mgr = ActivityManager.getService();
final Intent intent = mCurIntent;
if (intent == null) {
Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched
+ (mRunCalled ? ", run() has already been called" : ""));
}
mCurIntent = null;
mDispatched = true;
mRunCalled = true;
if (receiver == null || intent == null || mForgotten) {
if (mRegistered && ordered) {
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing null broadcast to " + mReceiver);
sendFinished(mgr);
}
return;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");
try {
ClassLoader cl = mReceiver.getClass().getClassLoader();
intent.setExtrasClassLoader(cl);
intent.prepareToEnterProcess();
setExtrasClassLoader(cl);
receiver.setPendingResult(this);
//注释1
receiver.onReceive(mContext, intent);
} catch (Exception e) {
if (mRegistered && ordered) {
if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG,
"Finishing failed broadcast to " + mReceiver);
sendFinished(mgr);
}
if (mInstrumentation == null ||
!mInstrumentation.onException(mReceiver, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Error receiving broadcast " + intent
+ " in " + mReceiver, e);
}
}
if (receiver.getPendingResult() != null) {
finish();
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
};
}
在注释1处广播接收器BroadcastReceiver的onReceive方法被回调了,至此整个广播接收器的注册、广播的发送、广播的接收所有的流程就全部走完了,最后我画了两幅流程图,方便大家对整个流程的理解。
总结
注册流程
ContextWraper.registerReceiver
ContextImpl.registerReceiver
ContextImpl.registerReceiverInternal
ActivityManagerService.registerReceiver
发送接收流程
ContextWraper.sendBroadcast
ContextImpl.sendBroadcast
ActivityManagerService.broadcastIntent
ActivityManagerService.broadcastIntentLocked
BroadcastQueue.scheduleBroadcastsLocked
BroadcastQueue.processNextBroadcastLocked
BroadcastQueue.deliverToRegisteredReceiverLocked
BroadcastQueue.performReceiveLocked
ApplicationThread.scheduleRegisteredReceiver
InnerReceiver.performReceive
ReceiverDispatcher.performReceive
BroadcastReceiver.onReceive
问题反馈
在使用学习中有任何问题,请留言,或加入Android、Java开发技术交流群
- QQ群:810970432
- email:[email protected]