在不同进程中向Android服务传递自定义对象

7

我有一个服务,它被设置为在单独的进程中启动:

<service android:name=".services.UploadService"
          android:process=":UploadServiceProcess" />

我可以使用bindService()成功地绑定到它。问题出现在我尝试通过调用Messenger.send()发送消息时:

service.send(Message.obtain(null, UploadService.MESSAGE_UPLOAD_REQUEST, uploadRequest));

其中uploadRequest是一个实现了Parcelable接口的自定义对象。

public class UploadRequest implements Parcelable {
    public File file;
    public boolean deleteOnUpload;

public UploadRequest(File file, boolean deleteOnUpload) {
    this.file = file;
    this.deleteOnUpload = deleteOnUpload;
}

private UploadRequest(Parcel in) {
    this.file = new File(in.readString());
}

public int describeContents() {
    return 0;
}

public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.file.getPath());
}

public static final Parcelable.Creator<UploadRequest> CREATOR = new Parcelable.Creator<UploadRequest>() {
    public UploadRequest createFromParcel(Parcel in) {
        return new UploadRequest(in);
    }
    public UploadRequest[] newArray(int size) {
        return new UploadRequest[size];
    }
};

我在服务的 handleMessage 中设置了一个断点,但是我的应用程序从未到达断点。然而,如果我发送 null 而不是使用自定义的 UploadRequest 对象,我会像预期的那样到达 handleMessage 断点,但显然此时我什么也做不了。我已经验证了在调用 writeToParcel 时返回非 null 的字符串 file.getPath()。这使我相信我的 UploadRequest 类有问题,但是从谷歌搜索中我找不出我的类哪里有问题。有什么想法吗?

3个回答

13
Message成员变量obj的文档中提到:

发送给接收者的任意对象。当使用Messenger在进程间发送消息时,只有包含一个Android框架类(而不是应用程序实现的类)的Parcelable时,此参数才能为非空值。对于其他数据传输,请使用setData(Bundle)。请注意,在FROYO版本之前,此处不支持Parcelable对象。

我的猜测是您将自己创建的Parcelable跨越进程边界进行了传递,因此出现了问题。相反,您需要将对象封装到Bundle中。这也意味着您的对象需要实现Serializable但不需要是Parcelable。


1
谢谢!也许下次我会好好看文档的 :D - Nelson Monterroso
1
哈哈,这真的很深奥,所以我感到很高兴学到了一些东西! - Nick Campion

5

我刚刚尝试了一下。

Message.obj可以传输框架类,例如ContentValues。

而Message.SetData可以跨进程传输Bundle,您可以将任何可Parcelable对象放入Bundle中。

只需在收到Message时在Bundle上调用setClassLoader即可。

发送方

        Message localMsg = Message.obtain();
        localMsg.what = TPServiceConnection.MSG_REPLY_SERVICE_HELLO;

        Bundle data = new Bundle();

        ContentValues cv = new ContentValues();
        cv.put("KEY", mRand.nextInt());
        data.putParcelable("KEY", cv);

        TPServiceDataModal modal = new TPServiceDataModal(mRand.nextInt());
        data.putParcelable("KEY2", modal);

        localMsg.setData(data);

接收端

        Bundle data = msg.getData();
        data.setClassLoader(this.getClass().getClassLoader());

        Parcelable parcelable = data.getParcelable("KEY");
        if (parcelable instanceof ContentValues) {
            ContentValues cv = (ContentValues) parcelable;
            Log.d(TAG, "reply content: " + cv.getAsInteger("KEY"));
        }

        Parcelable parcelable2 = data.getParcelable("KEY2");
        if (parcelable2 instanceof TPServiceDataModal) {
            TPServiceDataModal modal = (TPServiceDataModal) parcelable2;
            Log.d(TAG, "reply modal: " + modal.mData);
        }

这里的TPServiceDataModal是一个可Parcelable类。


关键点是设置应用程序级别的类加载器,而不是框架的类加载器。否则,自定义的Parcelable对象将无法反序列化。 - Chris Li

0

通过Bundle传递可包含对象的示例,请记得使用classLoader并使用正确的包定义对象

客户端应用程序

val msg: Message = Message.obtain(null, MSG_SAY_HELLO)
msg.data = Bundle().apply {
    putParcelable("KEY_MY_OBJECT", MyObject("an", 12))
}
mService?.send(msg)

.

package com.example.androidmessengerclient    
@Parcelize
class MyObject(val name: String, val age: Int) : Parcelable

服务器应用程序

override fun handleMessage(msg: Message) {
            when (msg.what) {
                123 -> {

                     msg.data.classLoader = MyObject::class.java.classLoader
                     val o = msg.data.getParcelable<MyObject>("KEY_MY_OBJECT")
                }
            }
}

Server应用程序中的模型需要与Client应用程序具有相同的包

package com.example.androidmessengerclient    
@Parcelize
class MyObject(val name: String, val age: Int) : Parcelable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接