赞
踩
前段时间遇到了一个死机重启问题,比较复杂,涉及到多方面的知识,我也分析了很长的时间,期间学到了很多东西,现在把分析的过程整理一下,希望可以给大家一点帮助和启发,同时也帮助自己再巩固一下。
首先说一下问题最开始的分析思路以及复现的过程,log 中最核心的部分如下所示:
10-17 12:13:02.004 2096 4139 W art : Large object allocation failed: ashmem_create_region failed for 'large object space allocation': Too many open files 10-17 12:13:02.004 2096 4139 I art : Starting a blocking GC Alloc 10-17 12:13:02.004 2096 4139 I art : Starting a blocking GC Alloc 10-17 12:13:02.005 22944 28199 D MscSpeechLog: QTTSAudioGet enter 10-17 12:13:02.005 22944 28199 D MSC_LOG : QTTSAudioGet Begin 10-17 12:13:02.006 22944 28199 D MSC_LOG : QTTSAudioGet End 10-17 12:13:02.006 2096 2096 D AndroidRuntime: Shutting down VM 10-17 12:13:02.006 22944 28199 D MscSpeechLog: QTTSAudioGet leavel:0value len = 0 10-17 12:13:02.006 2096 5712 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.) 10-17 12:13:02.006 2096 5712 E JavaBinder: java.lang.RuntimeException: Could not copy bitmap to parcel blob. 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.graphics.Bitmap.nativeWriteToParcel(Native Method) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.app.Notification.writeToParcel(Notification.java:1687) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:124) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.service.notification.IStatusBarNotificationHolder$Stub.onTransact(IStatusBarNotificationHolder.java:53) 10-17 12:13:02.006 2096 5712 E JavaBinder: at android.os.Binder.execTransact(Binder.java:453) 10-17 12:13:02.007 3362 15429 W Binder : Caught a RuntimeException from the binder stub implementation. 10-17 12:13:02.007 3362 15429 W Binder : java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.Notification android.service.notification.StatusBarNotification.getNotification()' on a null object reference 10-17 12:13:02.007 3362 15429 W Binder : at android.service.notification.NotificationListenerService$INotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:692) 10-17 12:13:02.007 3362 15429 W Binder : at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:71) 10-17 12:13:02.007 3362 15429 W Binder : at android.os.Binder.execTransact(Binder.java:453) --------- beginning of crash 10-17 12:13:02.007 2096 2096 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main 10-17 12:13:02.007 2096 2096 E AndroidRuntime: java.lang.RuntimeException: Could not copy bitmap to parcel blob. 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.nativeWriteToParcel(Native Method) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.clone(RemoteViews.java:1903) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.cloneInto(Notification.java:1521) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.clone(Notification.java:1495) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.service.notification.StatusBarNotification.clone(StatusBarNotification.java:161) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$NotificationListeners.notifyPostedLocked(NotificationManagerService.java:3398) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$8.run(NotificationManagerService.java:2228) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:742) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:95) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Looper.loop(Looper.java:157) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.run(SystemServer.java:302) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.main(SystemServer.java:176) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:746) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
最开始的思路是觉得发生了 fd 泄露,进程中存在很多打开的 fd,从而导致 copy bitmap to parcel blob 失败。另外,发生问题前有大量 10-17 12:12:07.101 3362 3362 D PhoneStatusBar: updateNotification pkg=com.ophone.reader.ui;id=1 log 打出,大约三百多次。因此认为是频繁 updateNotification 导致了 fd 的泄露,下面就是尝试复现一下来验证自己的这个想法。
首先,研究了一下 updateNotification 这种 log 是如何打出的,根据系统 notification 的机制,app 创建一个 notification,然后调用 NotificationManager 的 public void notify(int id, Notification notification) 方法,假如之前 notify 过同一 id,那么再次 notify 此 id 时就会有这样的 log 打出(忽略一些细节条件判断)。于是写了一个 demo,每次改变 notification 的 TextViewText,并且循环 notify。然而问题并没有复现。
再次观察 log 查看是否有遗漏的细节,发现几份 log 中都是 pkg=com.ophone.reader.ui;id=1,由此可见这个 app 的 notification 这里一定有什么过人之处。。。于是下载这个 app(咪咕阅读)查看其 notification 是什么样子的。然而……,我并不知道这个 notification 是怎么出现的,只得作罢。
再次观察 log 发现同样每份 log 中有大量
10-17 12:12:07.083 22944 28033 D MscSpeechLog: QTTSAudioGet enter
10-17 12:12:07.083 22944 28033 D MSC_LOG : QTTSAudioGet Begin
10-17 12:12:07.084 22944 28033 D MSC_LOG : QTTSAudioGet End
10-17 12:12:07.084 22944 28033 D MscSpeechLog: QTTSAudioGet leavel:0value len = 0
10-17 12:12:07.094 22944 28033 D MscSpeechLog: QTTSAudioGet enter
10-17 12:12:07.094 22944 28033 D MSC_LOG : QTTSAudioGet Begin
10-17 12:12:07.094 22944 28033 D MSC_LOG : QTTSAudioGet End
10-17 12:12:07.094 22944 28033 D MscSpeechLog: QTTSAudioGet leavel:0value len = 0
这种 log 打出,甚至怀疑 fd 泄露是不是跟这些 log 有关?再次看这些 log 发现这些 log 好像是某种文字转语音引擎打出的,于是恍然大悟,打开咪咕阅读,随便找一本小说,开启语音朗读,回到桌面,下拉通知栏,果然发现了咪咕阅读的 notification。可是,notification 的内容并没有更新啊,为什么会频繁打出 updateNotification 这样的 log 呢。仔细盯了通知一分钟,发现这个 notification 每隔不到 10 秒,停止按钮的图标就会由 II 样子变为三角然后再变回来(不知道其目的是什么,我总感觉是循环的逻辑出了问题)。于是我也仿照它的样子,写了一个 remoteView 里面设置了一个 bitmap,并把 remoteView 添加到 notification 中,频繁切换 bitmap 并且 notify notification,问题果然复现。
下面我们看一下为什么会打出这样的 log,首先分析一下:
10-17 12:13:02.006 2096 5712 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
10-17 12:13:02.006 2096 5712 E JavaBinder: java.lang.RuntimeException: Could not copy bitmap to parcel blob.
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.graphics.Bitmap.nativeWriteToParcel(Native Method)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.app.Notification.writeToParcel(Notification.java:1687)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:124)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.service.notification.IStatusBarNotificationHolder$Stub.onTransact(IStatusBarNotificationHolder.java:53)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.os.Binder.execTransact(Binder.java:453)
10-17 12:13:02.007 3362 15429 W Binder : Caught a RuntimeException from the binder stub implementation.
10-17 12:13:02.007 3362 15429 W Binder : java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.Notification android.service.notification.StatusBarNotification.getNotification()' on a null object reference
10-17 12:13:02.007 3362 15429 W Binder : at android.service.notification.NotificationListenerService$INotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:692)
10-17 12:13:02.007 3362 15429 W Binder : at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:71)
10-17 12:13:02.007 3362 15429 W Binder : at android.os.Binder.execTransact(Binder.java:453)
这段 log 产生的原因。首先我们思考一下这样几个问题:
这段 log 是在 app 更新或者添加 notification 的过程中产生的,所以我们先看一下更新或添加 notification 的调用流程:
enqueueNotificationWithTag
方法、system_server 进程调用 systemui 进程的 onNotificationPosted
方法、systemui 进程调用 system_server 进程的 get
方法第一次跨进程调用很常见,我们就不做讨论了,主要看一下第二、三次跨进程调用是如何实现的。首先,我们需要了解一下 NotificationListener 的注册流程(以 systemui 为例),如下图所示:
下面我们来看一下 registerAsSystemService 的实现:
NotificationListenerService.java
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
mSystemContext = context;
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
INotificationManager noMan = getNotificationInterface();
noMan.registerListener(mWrapper, componentName, currentUser);
mCurrentUser = currentUser;
}
下面我们来看一下 registerService 的实现:
ManagedServices.java
public void registerService(IInterface service, ComponentName component, int userid) {
checkNotNull(service);
ManagedServiceInfo info = registerServiceImpl(service, component, userid);
if (info != null) {
onServiceAdded(info);
}
}
可以看到其分为两部分,我们主要看一下 registerServiceImpl 部分:
ManagedServices.java
private ManagedServiceInfo registerServiceImpl(final IInterface service, final ComponentName component, final int userid) { synchronized (mMutex) { try { // 这里的 service 实际上可以看作之前 mWrapper 的代理 ManagedServiceInfo info = newServiceInfo(service, component, userid, true /*isSystem*/, null, Build.VERSION_CODES.LOLLIPOP); service.asBinder().linkToDeath(info, 0); mServices.add(info); return info; } catch (RemoteException e) { // already dead } } return null; }
mServices 会在 notify notification 流程的 notifyPostedLocked 方法中用到:
NotificationManagerService.java
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
......
for (final ManagedServiceInfo info : mServices) {
......
mHandler.post(new Runnable() {
@Override
public void run() {
notifyPosted(info, sbnToPost, update);
}
});
}
}
从中可以看出此方法是从 mServices 中取出之前添加的 ManagedServiceInfo 对象,然后执行 notifyPosted(info, sbnToPost, update) 方法,看一下 notifyPosted 的实现:
NotificationManagerService.java
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener)info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
info.service 就是包装之前的 service,因此 listener 即为之前 mWrapper 的代理,通过 listener.onNotificationPosted(sbnHolder, rankingUpdate) 也就实现了这次跨进程调用,由 system_server 进程进入了 systemui 进程。
我们看一下 StatusBarNotificationHolder 这个类:
NotificationManagerService.java
private static final class StatusBarNotificationHolder
extends IStatusBarNotificationHolder.Stub {
private StatusBarNotification mValue;
public StatusBarNotificationHolder(StatusBarNotification value) {
mValue = value;
}
@Override
public StatusBarNotification get() {
StatusBarNotification value = mValue;
mValue = null;
return value;
}
}
因此 notifyPosted 方法中传过去的 sbnHolder 其实与之前的 mWrapper 一致,都是为了提供一个匿名 Binder 服务,systemui 进程可以借助于其方法实现到 system_server 进程的调用。
从上面的 log 我们可以看出,第一个 Exception 是在服务端执行 onTransact 中的 writeToParcel 的时候产生的,那我们思考这样几个问题:此处产生 RuntimeException 会被怎样处理?返回给 Client 端的结果会是怎样的?下一段 log 中的空指针是怎么来的?
我们看一下 IStatusBarNotificationHolder.aidl 编译出的 IStatusBarNotificationHolder.java 文件,IStatusBarNotificationHolder.aidl 中只定义了一个接口:StatusBarNotification get();
在 IStatusBarNotificationHolder.java 中看一下其对应的部分:
private static class Proxy implements android.service.notification.IStatusBarNotificationHolder { /** Fetch the held StatusBarNotification. This method should only be called once per Holder */ @Override public android.service.notification.StatusBarNotification get() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); android.service.notification.StatusBarNotification _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0); _reply.readException(); if ((0!=_reply.readInt())) { _result = android.service.notification.StatusBarNotification.CREATOR.createFromParcel(_reply); } else { _result = null; } } finally { _reply.recycle(); _data.recycle(); } return _result; } }
由 mRemote.transact 开始的调用流程为:
最后产生 Exception 的地方是在 Server 端的 onTransact 方法中,因此,我们想知道这个 Exception 会被如何处理、以及最终返回给 Client 端的结果是什么,需要顺着 Binder 的调用流程逆推回去。
Binder.java
private boolean execTransact(int code, long dataObj, long replyObj, int flags) { Parcel data = Parcel.obtain(dataObj); Parcel reply = Parcel.obtain(replyObj); boolean res; try { res = onTransact(code, data, reply, flags); } catch (RemoteException e) { ... } catch (RuntimeException e) { // onTransact 中抛出的 RuntimeException 会被这里 catch 住 if ((flags & FLAG_ONEWAY) != 0) { // 为 oneway 调用 Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e); } else { reply.setDataPosition(0); reply.writeException(e); } res = true; } catch (OutOfMemoryError e) { ... } checkParcel(this, code, reply, "Unreasonably large binder reply buffer"); reply.recycle(); data.recycle(); return res; }
由上面代码可知,onTransact 中抛出的 RuntimeException 会被 catch 住,并且 get()
方法不是 oneway 的,因此,接下来会依次执行:
reply.setDataPosition(0);
reply.writeException(e);
reply.setDataPosition(0)
可以理解为将 parcel 中 mData
段上的 mDataPos
偏移置为 0下面我们来看一下 writeException 方法:
Parcel.java
public final void writeException(Exception e) { int code = 0; if (e instanceof SecurityException) { code = EX_SECURITY; } else if (e instanceof BadParcelableException) { code = EX_BAD_PARCELABLE; } else if (e instanceof IllegalArgumentException) { code = EX_ILLEGAL_ARGUMENT; } else if (e instanceof NullPointerException) { code = EX_NULL_POINTER; } else if (e instanceof IllegalStateException) { code = EX_ILLEGAL_STATE; } else if (e instanceof NetworkOnMainThreadException) { code = EX_NETWORK_MAIN_THREAD; } else if (e instanceof UnsupportedOperationException) { code = EX_UNSUPPORTED_OPERATION; } writeInt(code); StrictMode.clearGatheredViolations(); if (code == 0) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } writeString(e.getMessage()); }
setDataPosition(0)
,所以 writeInt(code)
会将 parcel 对象的 mData 上的第一个值置为 0,并将 mDataPos
增加 4(填充数据的 length),即指向下一个需要填充的数据的位置android_util_Binder.cpp
virtual status_t onTransact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) { JNIEnv* env = javavm_to_jnienv(mVM); IPCThreadState* thread_state = IPCThreadState::self(); const int32_t strict_policy_before = thread_state->getStrictModePolicy(); // 调用 Binder.java 的 execTransact 方法 jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags); if (env->ExceptionCheck()) { jthrowable excep = env->ExceptionOccurred(); report_exception(env, excep, "*** Uncaught remote exception! " "(Exceptions are not yet supported across processes.)"); res = JNI_FALSE; /* clean up JNI local ref -- we don't return to Java code */ env->DeleteLocalRef(excep); } if (thread_state->getStrictModePolicy() != strict_policy_before) { set_dalvik_blockguard_policy(env, strict_policy_before); } if (env->ExceptionCheck()) { jthrowable excep = env->ExceptionOccurred(); report_exception(env, excep, "*** Uncaught exception in onBinderStrictModePolicyChange"); /* clean up JNI local ref -- we don't return to Java code */ env->DeleteLocalRef(excep); if (code == SYSPROPS_TRANSACTION) { BBinder::onTransact(code, data, reply, flags); } // 这里 res == JNI_FALSE,所以 return UNKNOWN_TRANSACTION return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION; }
因为在 2.1 中的 writeException
时会 throw (RuntimeException) e;
,所以 env->ExceptionCheck()
为 true;因此会执行 report_exception
,这就是下面这段 log 的由来:
10-17 12:13:02.006 2096 5712 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.)
10-17 12:13:02.006 2096 5712 E JavaBinder: java.lang.RuntimeException: Could not copy bitmap to parcel blob.
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.graphics.Bitmap.nativeWriteToParcel(Native Method)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.app.Notification.writeToParcel(Notification.java:1687)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:124)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.service.notification.IStatusBarNotificationHolder$Stub.onTransact(IStatusBarNotificationHolder.java:53)
10-17 12:13:02.006 2096 5712 E JavaBinder: at android.os.Binder.execTransact(Binder.java:453)
下面我们继续来看一下剩下的 log 是怎么来的
Binder.cpp
status_t BBinder::transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) { data.setDataPosition(0); status_t err = NO_ERROR; switch (code) { case PING_TRANSACTION: reply->writeInt32(pingBinder()); break; default: // err = UNKNOWN_TRANSACTION err = onTransact(code, data, reply, flags); break; } if (reply != NULL) { reply->setDataPosition(0); } return err; }
由上面这段代码可知,此函数会返回 UNKNOWN_TRANSACTION
,并且将 reply 对象的 mDataPos
再次置为 0
IPCThreadState.cpp
case BR_TRANSACTION: { binder_transaction_data tr; result = mIn.read(&tr, sizeof(tr)); if (result != NO_ERROR) break; Parcel buffer; buffer.ipcSetDataReference( reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets), tr.offsets_size/sizeof(binder_size_t), freeBuffer, this); Parcel reply; status_t error; ... // 重点部分 if (tr.target.ptr) { if (reinterpret_cast<RefBase::weakref_type*>( tr.target.ptr)->attemptIncStrong(this)) { // 此处调用 BBinder::transact,error = UNKNOWN_TRANSACTION error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer, &reply, tr.flags); reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this); } else { error = UNKNOWN_TRANSACTION; } } else { ... } // 重点部分 if ((tr.flags & TF_ONE_WAY) == 0) {// 不为 oneway 的情况 LOG_ONEWAY("Sending reply to %d!", mCallingPid); if (error < NO_ERROR) { alog << "error < NO_ERROR, reply = " << reply << endl; reply.setError(error); } sendReply(reply, 0); } else { LOG_ONEWAY("NOT sending reply to %d!", mCallingPid); } ... } break;
由于 BBinder::transact 的返回值为 UNKNOWN_TRANSACTION
,所以可知这里会依次调用:
reply.setError(error); // error = UNKNOWN_TRANSACTION
sendReply(reply, 0);
由于 IPCThreadState 的调用过程涉及到跨进程,比较复杂,所以这里详细讲解一下。
下面看一下 IPCThreadState 是如何跨进程调用到对端的:
talkWithDriver
时,会将 parcel 对象 mOut 的数据写入内核空间,并将读到的数据放入 parcel 对象 mIn 中BC_TRANSACTION
-> BR_TRANSACTION
)结合我们调查的这个问题,将 IPCThreadState 跨进程调用的具体过程用图示表示出来:
从图中我们可以看出:
waitForResponse
时,收到 Binder Driver 的 BR_TRANSACTION
指令,于是调用 executeCommand(cmd)
,并进入 switch 语句的 case BR_TRANSACTION:
情况error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer, &reply, tr.flags);
并沿着 2 中给出的调用流程一直执行到 Server 端的业务实现,我们这里的返回值 error
为 UNKNOWN_TRANSACTION
sendReply
时会先调用 writeTransactionData(BC_REPLY, ...)
将 cmd BC_REPLY
以及 parcel reply 写入输出 parcel mOut 中,然后再调用 waitForResponse(NULL, NULL);
收到 BR_TRANSACTION_COMPLETE
退出BC_REPLY
通过 Binder Driver 会被转化为 BR_REPLY
,Client 端在 talkWithDriver()
接收到 BR_REPLY
后会进入 case BR_REPLY:
情况,可以看到因为传过来的数据 tr
带有 flag TF_STATUS_CODE
,因此会执行 err = *reinterpret_cast<const status_t*>(tr.data.ptr.buffer);
获取 status code
,即 err = UNKNOWN_TRANSACTION
,而后执行 freeBuffer
后直接 goto finish;
status code(UNKNOWN_TRANSACTION)
,Client 端做的也只是提取出 status code
赋值给 reply
对象的 mError
成员,值得注意的是 Client 端的 reply
对象在此之前没有其他额外操作,也就是 reply
对象现在的 mDataSize
仍旧为 0到此,IPCThreadState
非 oneway 情况下的 transact
过程就讲解完了,剩下的流程比较简单,沿着 2.2 中给出的流程图推导过去即可,这里不再详细说明;下面来具体看一下 null
是怎么来的。
也就是说,这种情况下,返回的 reply 对象中除了 mError 中存储着 UNKNOWN_TRANSACTION 的错误信息之外,根本就没有其他的数据,仍旧保持着初始化时的样子。
我们继续看一下 2.2 中 get()
方法的代码,在 mRemote.transact(Stub.TRANSACTION_get, _data, _reply, 0);
返回后,会依次执行以下几步:
_reply.readException();
if ((0!=_reply.readInt())) {
_result = android.service.notification.StatusBarNotification.CREATOR.createFromParcel(_reply);
}
else {
_result = null;
}
return _result;
我们来看一下 readException()
的实现:
Parcel.java
public final void readException() { int code = readExceptionCode(); if (code != 0) { String msg = readString(); readException(code, msg); } } public final int readExceptionCode() { int code = readInt(); if (code == EX_HAS_REPLY_HEADER) { ... } return code; }
也就是说最终还是要看 readInt()
方法,其会执行到 Parcel.cpp 的 Parcel::readAligned()
方法:
Parcel.cpp
template<class T> T Parcel::readAligned() const { T result; if (readAligned(&result) != NO_ERROR) { result = 0; } return result; } template<class T> status_t Parcel::readAligned(T *pArg) const { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(T)) <= mDataSize) { const void* data = mData+mDataPos; mDataPos += sizeof(T); *pArg = *reinterpret_cast<const T*>(data); return NO_ERROR; } else { return NOT_ENOUGH_DATA; } }
2.2.5 中提到,reply 中的 mDataSize
为 0,所以 mDataPos+sizeof(T)
必定大于 mDataSize
,因此 Parcel::readAligned(T *pArg)
返回 NOT_ENOUGH_DATA
;所以 Parcel::readAligned()
最终会返回 0,由此可知:
Parcel::readAligned(T *pArg)
时,如果 (mDataPos+sizeof(T)) <= mDataSize
,那么每执行一次 Parcel::readAligned(T *pArg)
,mDataPos
的值都会改变,因此 Java 层多次调用 readInt()
得到的值可以不相同,其他 read*()
函数同理;因为 _reply.readInt()
的值为 0,因此最终会返回 null
,null
的由来我们知道了,那么第二段 log 是在什么位置打印的呢?下面我们来具体看一下这个问题。
由 2.1 可知,NPE 是在 INotificationListenerWrapper
的 onNotificationPosted
中抛出的;换个角度,此时是 systemui 作为 system_server 的 Server 端,因此这个 NPE 会被 2.2.1 中的 execTransact
函数 catch 住;而此调用为 oneway 调用,因此如下:
catch (RuntimeException e) {
if ((flags & FLAG_ONEWAY) != 0) { // 执行这里
Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
} else {
reply.setDataPosition(0);
reply.writeException(e);
}
res = true;
}
直接会将 NPE 信息输出,即第二段 log。
要想知道 fd 是如何开启的,首先要明确 bitmap 是如何通过 Parcel 跨进程传递的。
Bitmap.cpp
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jboolean isMutable, jint density, jobject parcel) { ... android::Parcel* p = android::parcelForJavaObject(env, parcel); SkBitmap bitmap; android::Bitmap* androidBitmap = reinterpret_cast<Bitmap*>(bitmapHandle); androidBitmap->getSkBitmap(&bitmap); ... // Transfer the underlying ashmem region if we have one and it's immutable. android::status_t status; // 如果 bitmap 存储在匿名共享内存中,那么将指示匿名共享内存的 fd 存储在 parcel 中进行传递 int fd = androidBitmap->getAshmemFd(); if (fd >= 0 && !isMutable && p->allowFds()) { status = p->writeDupImmutableBlobFileDescriptor(fd); ... return JNI_TRUE; } // Copy the bitmap to a new blob. bool mutableCopy = isMutable; size_t size = bitmap.getSize(); android::Parcel::WritableBlob blob; status = p->writeBlob(size, mutableCopy, &blob); if (status) { doThrowRE(env, "Could not copy bitmap to parcel blob."); return JNI_FALSE; } bitmap.lockPixels(); const void* pSrc = bitmap.getPixels(); if (pSrc == NULL) { memset(blob.data(), 0, size); } else { // 将 pSrc 指向的数据复制到 blob.data() 指示的内存当中 memcpy(blob.data(), pSrc, size); } bitmap.unlockPixels(); blob.release(); return JNI_TRUE; }
下面我们看一下 writeBlob 是如何实现的:
Parcel.cpp
status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob) { ... status_t status; ... ALOGV("writeBlob: write to ashmem"); // 1. 首先打开一块匿名共享内存 int fd = ashmem_create_region("Parcel Blob", len); if (fd < 0) return NO_MEMORY; int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE); if (result < 0) { status = result; } else { // 2. 将匿名共享内存映射到内存中,并得到指向这块内存的指针 ptr void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (ptr == MAP_FAILED) { status = -errno; } else { if (!mutableCopy) { result = ashmem_set_prot_region(fd, PROT_READ); } if (result < 0) { status = result; } else { status = writeInt32(mutableCopy ? BLOB_ASHMEM_MUTABLE : BLOB_ASHMEM_IMMUTABLE); if (!status) { // 3. 将匿名共享内存的 fd 写入 parcel 中 status = writeFileDescriptor(fd, true /*takeOwnership*/); if (!status) { // 4. 用 fd 和 ptr 等对 outBlob 进行初始化 outBlob->init(fd, ptr, len, mutableCopy); return NO_ERROR; } } } } ::munmap(ptr, len); } ::close(fd); return status; }
Parcel::writeBlob 主要做的事情已经在注释中给出,需要注意的是:
下面我们用图示来表示一下 bitmap writeToParcel 的整个过程:
这个问题中涉及到三个进程间的 bitmap 传递,详细过程如下图所示:
从上图中可以看出,open fd 的地方都有 close,那么 fd 泄露到底是什么原因造成的呢?
开始的时候怀疑会不会是频繁更新时,fd 来不及 close 从而导致 fd 越积越多呢?
这个时候我突然想起之前在调试 Binder 调用对象的时候,parcel 对象中好像有很多 fd(感觉有时候运气也是解 bug 一个重要因素呢,O(∩_∩)O哈哈~),这是为什么呢?一个 bitmap 传输时不是只需要一个 fd 吗?
于是,我再次加 log 看了一下,发现确实存在多个 fd,并且随着 updateNotification 次数的增加而增加。难道是更新间隔太短时,系统的处理机制会出现问题?
于是我改变 demo 为手动触发,点一次按钮触发一次 updateNotification,每次间隔 10 秒左右,发现此时传输的 parcel 中确实只有一个 fd 了,难道真是更新时间太短的缘故?
附上手动触发 demo 主要代码:
private int i = 0; ... mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.notification); remoteView.setTextViewText(R.id.title, "Hello,this message is for my test"); remoteView.setImageViewResource(R.id.icon, R.mipmap.ic_launcher); Notification.Builder builder = new Notification.Builder(getApplicationContext()); builder.setSmallIcon(R.mipmap.ic_launcher); Notification notification = builder.build(); notification.flags = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR | FLAG_FOREGROUND_SERVICE; Bitmap bitmap = null; remoteView.setTextViewText(R.id.text, "Notification, count = " + i); if (i % 2 == 1 ) { bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); remoteView.setImageViewBitmap(R.id.image, bitmap); notification.contentView = remoteView; mNotificationManager.notify(1, notification); } else { bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round); remoteView.setImageViewBitmap(R.id.image, bitmap); notification.contentView = remoteView; mNotificationManager.notify(1, notification); } i++; } });
为了再次确认这个问题,我将 demo 恢复成原来的样子,在每一次 notify notification 后 sleep(10000); 结果发现 fd 的数量再次随着 updateNotification 次数的增加而增加,这不科学啊,这样跟手动触发有什么区别吗?
附上 demo 主要代码:
mButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread("add Notification") { @Override public void run() { RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.notification); remoteView.setTextViewText(R.id.title, "Hello,this message is for my test"); remoteView.setImageViewResource(R.id.icon, R.mipmap.ic_launcher); Notification.Builder builder = new Notification.Builder(getApplicationContext()); builder.setSmallIcon(R.mipmap.ic_launcher); Notification notification = builder.build(); notification.flags = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR | FLAG_FOREGROUND_SERVICE; Bitmap bitmap = null; for (int i = 0; ; i++) { remoteView.setTextViewText(R.id.text, "Notification, count = " + i); if (i % 2 == 1 ) { bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); remoteView.setImageViewBitmap(R.id.image, bitmap); notification.contentView = remoteView; mNotificationManager.notify(1, notification); } else { bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round); remoteView.setImageViewBitmap(R.id.image, bitmap); notification.contentView = remoteView; mNotificationManager.notify(1, notification); } } } }.start(); } });
对比两份 demo,发现确实有区别:手动触发时,每次都是新创建的 remoteView,而原来 demo 中却是一直在使用同一个 remoteView,我恍然大悟,赶忙去看 setImageViewBitmap(…) 的实现:
RemoteViews.java
public void setImageViewBitmap(int viewId, Bitmap bitmap) { setBitmap(viewId, "setImageBitmap", bitmap); } public void setBitmap(int viewId, String methodName, Bitmap value) { addAction(new BitmapReflectionAction(viewId, methodName, value)); } BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { this.bitmap = bitmap; this.viewId = viewId; this.methodName = methodName; bitmapId = mBitmapCache.getBitmapId(bitmap); } public int getBitmapId(Bitmap b) { if (b == null) { return -1; } else { if (mBitmaps.contains(b)) { return mBitmaps.indexOf(b); } else { // 如果 mBitmaps 中不含 b,则将其 add 进 mBitmaps mBitmaps.add(b); return (mBitmaps.size() - 1); } } } private void addAction(Action a) { ... mActions.add(a); ... }
突然间,我好像发现了罪魁祸首,setImageViewBitmap(…) 根本不是像我们所想的那样的覆盖方式,而是在不断向 ArrayList mActions 中 add Action 对象,并且如果 bitmap 一直是新创建的,那么 mBitmaps 的 size 也会不断增加;这样看来多亏我当初复现问题时恰巧选择了和咪咕阅读开发同样的错误调用方式,否则这个问题还真是复现不出来,O(∩_∩)O哈哈~
RemoteViews.java
public void writeBitmapsToParcel(Parcel dest, int flags) {
int count = mBitmaps.size();
dest.writeInt(count);
for (int i = 0; i < count; i++) {
mBitmaps.get(i).writeToParcel(dest, flags);
}
}
从 contentView writeToParcel 的过程中可以看出,会将 mBitmaps 中的所有 bitmap 都 writeToParcel,这就解释了为什么 fd 的数量会不断增加,这样看来,系统对于此处 fd 的 close 机制应该确实没有问题。那么问题来了?bitmap 传输涉及三个进程,那么为什么每次都是 system_server 下面的 fd 数量先扛不住,达到 1024,而 app 和 systemui 下面的 fd 数量波动远没有这么剧烈?
下面我们看一下 notification notify 过程中 notifyPostedLocked 方法的实现:
NotificationManagerService.java
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) { ... for (final ManagedServiceInfo info : mServices) { ...... if (trim == TRIM_LIGHT && sbnCloneLight == null) { sbnCloneLight = sbn.cloneLight(); } else if (trim == TRIM_FULL && sbnClone == null) { // sbn.clone() 也涉及到 RemoteViews 对象的 writeToParcel sbnClone = sbn.clone(); } ...... mHandler.post(new Runnable() { @Override public void run() { notifyPosted(info, sbnToPost, update); } }); } }
从这段代码中可以看出其中有一个观察者模式,其会通知注册到 mServices 中的所有 info 有 notification 需要更新,而后这些注册的进程都会调用 IStatusBarNotificationHolder 中的 get() 方法从 system_server 获取 StatusBarNotification 对象。
从我的调试结果来看,在小米的手机上,有 systemui、xmsf 两个进程进行了注册,所以各进程开启 fd 情况简略图如下所示:
从图中可以看出当通过 Binder 传输 bitmaps 的时候,system_server 会同时传给两个进程——systemui 和 xmsf,所以 system_server 下面 open 的 fd 的数量是需要传递的 bitmap 的数量的两倍,所以总是 system_server 进程先达到 1024 的上限。并且这也是为什么每次 notification 更新到 300 多次的时候会出现异常:300多次 × 2 + system_server 进程下本来就打开的 200 多个 fd,正好约等于 1024。
那么下面这些 log 以及重启是怎样产生的呢?
10-17 12:13:02.007 2096 2096 E AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main 10-17 12:13:02.007 2096 2096 E AndroidRuntime: java.lang.RuntimeException: Could not copy bitmap to parcel blob. 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.nativeWriteToParcel(Native Method) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1553) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:984) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:2854) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.widget.RemoteViews.clone(RemoteViews.java:1903) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.cloneInto(Notification.java:1521) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.app.Notification.clone(Notification.java:1495) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.service.notification.StatusBarNotification.clone(StatusBarNotification.java:161) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$NotificationListeners.notifyPostedLocked(NotificationManagerService.java:3398) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.notification.NotificationManagerService$8.run(NotificationManagerService.java:2228) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:742) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:95) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at android.os.Looper.loop(Looper.java:157) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.run(SystemServer.java:302) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.server.SystemServer.main(SystemServer.java:176) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:746) 10-17 12:13:02.007 2096 2096 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
因为在上面所述的 notifyPostedLocked 方法中的 sbn.clone(); 也涉及到 writeBitmapsToParcel,所以如果在 fd 数量达到上限后,system_server 刚好调用到这里,就会抛出异常发生重启。
因此,这个问题既是咪咕阅读这个 app 写的有问题(为什么会频繁切换开始/暂停按钮的图标?以及调用 setImageViewBitmap 时肯定没有看其具体的实现),也是系统 RemoteViews 机制存在一些问题:为什么调用 setImageViewBitmap 会是这样的实现,而不是采用以 viewId 为 key,bitmap 对象为 value 这样的形式来实现呢?
在华为手机上安装这个 demo 进行测试,并没有发生 Exception,但是却打出了如下 log:
11-09 10:58:15.279 7547 7633 W RemoteViews: RemoteViews try to cache 50 bitmaps, only allows 50, replace bitmap at index 0
11-09 10:58:15.281 7547 7633 W RemoteViews: RemoteViews try to cache 51 bitmaps, only allows 50, replace bitmap at index 1
11-09 10:58:15.283 7547 7633 W RemoteViews: RemoteViews try to cache 52 bitmaps, only allows 50, replace bitmap at index 2
11-09 10:58:15.285 7547 7633 W RemoteViews: RemoteViews try to cache 53 bitmaps, only allows 50, replace bitmap at index 3
11-09 10:58:15.287 7547 7633 W RemoteViews: RemoteViews try to cache 54 bitmaps, only allows 50, replace bitmap at index 4
11-09 10:58:15.289 7547 7633 W RemoteViews: RemoteViews try to cache 55 bitmaps, only allows 50, replace bitmap at index 5
11-09 10:58:15.293 7547 7633 W RemoteViews: RemoteViews try to cache 56 bitmaps, only allows 50, replace bitmap at index 6
11-09 10:58:15.299 7547 7633 W RemoteViews: RemoteViews try to cache 57 bitmaps, only allows 50, replace bitmap at index 7
11-09 10:58:15.302 7547 7633 W RemoteViews: RemoteViews try to cache 58 bitmaps, only allows 50, replace bitmap at index 8
11-09 10:58:15.303 7547 7633 W RemoteViews: RemoteViews try to cache 59 bitmaps, only allows 50, replace bitmap at index 9
11-09 10:58:15.305 7547 7633 W RemoteViews: RemoteViews try to cache 60 bitmaps, only allows 50, replace bitmap at index 10
11-09 10:58:15.307 7547 7633 W RemoteViews: RemoteViews try to cache 61 bitmaps, only allows 50, replace bitmap at index 11
……
看来华为已经针对这种情况做出了处理,对于 mBitmaps 的 size 做出了限制(限制最多 50),当超过 50 时会按照存入的顺序进行 replace。
这个我没法判断…自己写的 demo 在这个手机上会因为 ClassNotFoundException 闪退,安装咪咕阅读进行复现会发现其直接暴力地 disable 了,压根弹不出 notification :
11-09 13:13:30.324 1500 3343 V NotificationService: disabling notifications for com.ophone.reader.ui
在 Nexus 手机上安装这个 demo 进行测试,产生 Exception,打出如下 log,与我们的基本一致:
11-09 14:52:11.474 1929 7030 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.) 11-09 14:52:11.474 1929 7030 E JavaBinder: java.lang.RuntimeException: Could not copy bitmap to parcel blob. 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.graphics.Bitmap.nativeWriteToParcel(Native Method) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1589) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:1091) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:3492) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.app.Notification.writeToParcelImpl(Notification.java:1951) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.app.Notification.writeToParcel(Notification.java:1899) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:175) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.service.notification.IStatusBarNotificationHolder$Stub.onTransact(IStatusBarNotificationHolder.java:53) 11-09 14:52:11.474 1929 7030 E JavaBinder: at android.os.Binder.execTransact(Binder.java:565) 11-09 14:52:11.474 1929 29176 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.) 11-09 14:52:11.474 1929 29176 E JavaBinder: java.lang.RuntimeException: Could not copy bitmap to parcel blob. 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.graphics.Bitmap.nativeWriteToParcel(Native Method) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1589) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:1091) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:3492) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.app.Notification.writeToParcelImpl(Notification.java:1951) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.app.Notification.writeToParcel(Notification.java:1899) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:175) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.service.notification.IStatusBarNotificationHolder$Stub.onTransact(IStatusBarNotificationHolder.java:53) 11-09 14:52:11.474 1929 29176 E JavaBinder: at android.os.Binder.execTransact(Binder.java:565) 11-09 14:52:11.474 3499 3521 W Binder : Binder call failed. 11-09 14:52:11.474 3499 3521 W Binder : java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.Notification android.service.notification.StatusBarNotification.getNotification()' on a null object reference 11-09 14:52:11.474 3499 3521 W Binder : at android.service.notification.NotificationListenerService$NotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:878) 11-09 14:52:11.474 3499 3521 W Binder : at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:71) 11-09 14:52:11.474 3499 3521 W Binder : at android.os.Binder.execTransact(Binder.java:565) 11-09 14:52:11.475 1929 3574 E JavaBinder: *** Uncaught remote exception! (Exceptions are not yet supported across processes.) 11-09 14:52:11.475 1929 3574 E JavaBinder: java.lang.RuntimeException: Could not copy bitmap to parcel blob. 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.graphics.Bitmap.nativeWriteToParcel(Native Method) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.graphics.Bitmap.writeToParcel(Bitmap.java:1589) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.widget.RemoteViews$BitmapCache.writeBitmapsToParcel(RemoteViews.java:1091) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.widget.RemoteViews.writeToParcel(RemoteViews.java:3492) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.app.Notification.writeToParcelImpl(Notification.java:1951) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.app.Notification.writeToParcel(Notification.java:1899) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:175) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.service.notification.IStatusBarNotificationHolder$Stub.onTransact(IStatusBarNotificationHolder.java:53) 11-09 14:52:11.475 1929 3574 E JavaBinder: at android.os.Binder.execTransact(Binder.java:565) 11-09 14:52:11.476 4161 4172 W Binder : Binder call failed. 11-09 14:52:11.476 4161 4172 W Binder : java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.Notification android.service.notification.StatusBarNotification.getNotification()' on a null object reference 11-09 14:52:11.476 4161 4172 W Binder : at android.service.notification.NotificationListenerService$NotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:878) 11-09 14:52:11.476 4161 4172 W Binder : at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:71) 11-09 14:52:11.476 4161 4172 W Binder : at android.os.Binder.execTransact(Binder.java:565)
从中也可以看出 Nexus 也是有两个进程:com.android.systemui 和 com.google.android.ext.services,注册监听了 notification 的更新。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。