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

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.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,输出的日志如下:

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中输出的:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值