Music应用负责音乐的播放,如果某音乐还有图片,也需要把图片显示出来;
当Music应用播放的音乐发生切换后,需要通过广播消息,将正在播放的音乐的名称、图片等通过广播消息通知给B应用。
如果播放的是歌曲1,应用B可以正常收到歌曲1的名称、图片等;但如果切换到歌曲2,应用B无法收到广播消息。
Music应用中发送广播消息的代码如下:
System.out.println("play " + "send intent, musicTitle=" + getTrackName() + " musicAlbum" + getAlbumName()); Intent intent = new Intent(); intent.setAction("com.zhao3546.ACTION_MUSIC_DETAIL"); intent.putExtra("musicTitle", getTrackName()); intent.putExtra("musicAlbum", getAlbumName()); Bitmap bmp = MusicUtils.getArtwork(this.getApplicationContext(), getAudioId(), getAlbumId()); intent.putExtra("musicImage", bmp); System.out.println("bmp.getByteCount() = " + bmp.getByteCount()); sendBroadcast(intent);
B应用中动态注册接收广播消息的代码如下:
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("com.zhao3546.ACTION_MUSIC_DETAIL"); registerReceiver(mReceiver, intentFilter);
private class MusicDetailReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String title = intent.getStringExtra("musicTitle"); final String album = intent.getStringExtra("musicAlbum"); System.out.println("MusicDetailReceiver.onReceiver" + " title=" + title + " album=" + album); final Bitmap bmp = (Bitmap) intent.getParcelableExtra("musicImage");
播放歌曲1,输出的日志如下:
09-11 16:12:16.732: I/System.out(14177): play send intent, musicTitle=最近超级火热的变形金刚简单机械短信息音效 musicAlbumwww.mozhao.net09-11 16:12:16.776: I/System.out(14177): bmp.getByteCount() = 36723609-11 16:12:16.802: I/System.out(13265): MusicDetailReceiver.onReceiver title=最近超级火热的变形金刚简单机械短信息音效 album=www.mozhao.net
播放歌曲2,输出的日志如下:
09-11 16:12:25.266: I/System.out(14177): play send intent, musicTitle=Bach-Goldberg Variations 1 2 3 musicAlbumClassical 109-11 16:12:25.329: I/System.out(14177): bmp.getByteCount() = 524288
在使用歌曲1测试时,和使用歌曲2测试时,中间我还修改过一些代码,一开始一起以为修改代码引入的问题;
但后来使用歌曲1测试发现最新的代码也是好使的,再仔细分析日志,发现在 bmp.getByteCount() = 524288 这行日志有一个错误日志,见红色字体:
09-11 16:12:25.266: I/System.out(14177): play send intent, musicTitle=Bach-Goldberg Variations 1 2 3 musicAlbumClassical 1
09-11 16:12:25.329: I/System.out(14177): bmp.getByteCount() = 524288
09-11 16:12:25.342: E/JavaBinder(9985): !!! FAILED BINDER TRANSACTION !!!
手头有Android 4.2.2完整的源码,根据“FAILED BINDER TRANSACTION” 搜索了一下代码,发现是在 android_util_Binder.cpp 这个类的signalExceptionForError中输出的:
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err, bool canThrowRemoteException){ switch (err) { ... case FAILED_TRANSACTION: ALOGE("!!! FAILED BINDER TRANSACTION !!!"); // TransactionTooLargeException is a checked exception, only throw from certain methods. // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION // but it is not the only one. The Binder driver can return BR_FAILED_REPLY // for other reasons also, such as if the transaction is malformed or // refers to an FD that has been closed. We should change the driver // to enable us to distinguish these cases in the future. jniThrowException(env, canThrowRemoteException ? "android/os/TransactionTooLargeException" : "java/lang/RuntimeException", NULL); break; ...
再根据 “FAILED_TRANSACTION” 这个关键字,进一步搜索,在IPCThreadState.cpp的waitForResponse()方法中,有如下代码:
从这个问题出现的所在的函数,我们可以知道,说明这个广播消息已经传输给Binder驱动层了,正在等待Binder驱动层回应答。
为什么?这是一个相当长的话题,如果你不知道,看一下《Android框架揭秘》,或者老罗的Blog:《Android进程间通信(IPC)机制Binder简要介绍和学习计划》。
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult){ int32_t cmd; int32_t err; while (1) { if ((err=talkWithDriver()) < NO_ERROR) break; err = mIn.errorCheck(); if (err < NO_ERROR) break; if (mIn.dataAvail() == 0) continue; cmd = mIn.readInt32(); IF_LOG_COMMANDS() { alog << "Processing waitForResponse Command: " << getReturnString(cmd) << endl; } switch (cmd) { ... case BR_FAILED_REPLY: err = FAILED_TRANSACTION; goto finish; ... } }finish: if (err != NO_ERROR) { if (acquireResult) *acquireResult = err; if (reply) reply->setError(err); mLastError = err; } return err;}
IPCThreadState::waitForResponse() 将 BR_FAILED_REPLY这个错误码,转成了FAILED_TRANSACTION抛给了上层,
再看看BR_FAILED_REPLY这个错误码是在哪里生成的,进一步搜索了一下,发现这个错误码是在binder.c(Binder的驱动源码中)大量出现:
Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;Binder.c (e:\git\android4.2.2源码\driver\binder): return_error = BR_FAILED_REPLY;
binder.c 这个类通过正常方式下载的Android全量源码中是不包含的,如何下载可以参考我的另一个blog:《Android Binder驱动源码下载地址》。
再进一步分析一下之前的日志:
正常传输的图片大小 : 09-11 16:12:16.776: I/System.out(14177): bmp.getByteCount() = 367236
无法正常传输的图片大小 : 09-11 16:12:25.329: I/System.out(14177): bmp.getByteCount() = 524288
异常的那个图片,比正常的图片要大42%左右,再反向去推测,既然是图片过大,那这个问题可能出现在哪里?
出现BR_FAILED_REPLY关键字的地方,我都过了一下,如下地方我个人觉得最像,由于图片大小过大,导致分配buffer出现了问题:
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, struct binder_transaction_data *tr, int reply){ ... t->buffer = binder_alloc_buf(target_proc, tr->data_size, tr->offsets_size, !reply && (t->flags & TF_ONE_WAY)); if (t->buffer == NULL) { return_error = BR_FAILED_REPLY; goto err_binder_alloc_buf_failed; }
当然,仅是推测,没有进一步验证。
大概分析出了问题根因,那怎么解决,其实就比较简单了:
1、将图片保存到sd卡,将路径传给应用B,让应用B自己去从sd卡加载;
2、将图片进行压缩后再传输;
搞Android,有一个好处,就是遇到问题,你可以通过全部的源码去进一步分析找到问题根因;通过解决某个问题,反过来又可以加深对Android的整体的了解。