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

来源:互联网 发布:测试环境执行sql指令 编辑:程序博客网 时间:2024/06/04 19:52
Music应用负责音乐的播放,如果某音乐还有图片,也需要把图片显示出来;
当Music应用播放的音乐发生切换后,需要通过广播消息,将正在播放的音乐的名称、图片等通过广播消息通知给B应用。
如果播放的是歌曲1,应用B可以正常收到歌曲1的名称、图片等;但如果切换到歌曲2,应用B无法收到广播消息。
 
Music应用中发送广播消息的代码如下:
[java]  
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应用中动态注册接收广播消息的代码如下:
[java]  
        IntentFilter intentFilter = new IntentFilter();  
        intentFilter.addAction("com.zhao3546.ACTION_MUSIC_DETAIL");  
        registerReceiver(mReceiver, intentFilter);  
[java]  
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,输出的日志如下:
[plain]  
09-11 16:12:16.732: I/System.out(14177): play send intent, musicTitle=最近超级火热的变形金刚简单机械短信息音效 musicAlbumwww.mozhao.net  
09-11 16:12:16.776: I/System.out(14177): bmp.getByteCount() = 367236  
09-11 16:12:16.802: I/System.out(13265): MusicDetailReceiver.onReceiver title=最近超级火热的变形金刚简单机械短信息音效 album=www.mozhao.net  
 
播放歌曲2,输出的日志如下:
[java]  
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  
 
在使用歌曲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中输出的:
[cpp] 
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简要介绍和学习计划》。
[cpp]  
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的驱动源码中)大量出现:
[cpp]  
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出现了问题:
[cpp] 
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的整体的了解。
0 0