通过Intent传输图片导致广播消息异常的问题根因分析(通过分析Android源码反向推理)

来源:互联网 发布:后盾网php视频教程 编辑:程序博客网 时间:2024/06/05 10:12

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的整体的了解。

原创粉丝点击