五、Android中 IPC 机制(2)之序列化---Serializable 接口

为什么要使用 Serializable 接口:

    我们在进行 Android 开发时,经常使用 Intent 传输数据,比如从 Activity A 跳转到 Activity B 时,通过 intent 传输数据给 Activity B:

Intent intent = new Intent(AActivity.this, BActivity.class);
intent.putExtra("name", "cfm");
intent.putExtra("dream", "open source");
startActivity(intent);

这样使用很方便,但是 Intent 只能传输基本数据类型、String 类型以及可序列化和反序列化的对象类型。所以如果我们要想通过 Intent 传输一个我们自己定义的普通类对象,就必须让这个普通类对象能够实现序列化和反序列化操作。有时我们可能还需要把对象持久化到存储设备上或者通过网络传输给其它客户端,这个时候该对象都必须能序列化和反序列化才可以操作成功。

    在 Android 中我们可以通过 Serializable 接口和 Parcelable 接口来实现对象的可序列化和反序列化。而我们这里就先详细说明一下 Serializable 接口的原理与使用。

如何使用 Serializable 接口:

    Serializable 是 Java 给我们提供的一个序列化接口,在 java.io.Serializable 包下,是一个空接口,为对象提供标准的序列化和反序列化操作。

// java.io.Serializable
public interface Serializable {
}

    使用起来也非常简单,只需要在要序列化的类中声明一个类似下面的标识即可自动实现默认的序列化过程。(当然,也可以不声明这个标识,具体的区别后面详细说。)

private static final long serialVersionUID = 1L;

    对对象序列化和反序列化的操作也非常简单,使用 java 提供的 ObjectOutputStream 的 wirteObject() 方法 和 ObjectInputStream 的 readObject() 方法即可:

eg1:通过 Intent 传输对象:

// User.java
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private int mId;
    private String mName;
    private String mDream;

    public User(int id, String name, String dream) {
        mId = id;
        mName = name;
        mDream = dream;
    }

    public int getId() {
        return mId;
    }

    public void setId(int id) {
        mId = id;
    }

    public String getName() {
        return mName;
    }

    public void setName(String name) {
        mName = name;
    }

    public String getDream() {
        return mDream;
    }

    public void setDream(String dream) {
        mDream = dream;
    }
}
// AActivity.java
public class AActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn = findViewById(R.id.first_btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(AActivity.this, BActivity.class);
                Serializable user = new User(1, "cfm", "open source");
                Log.d("cfmtest", "序列化的user: " + user.toString());
                intent.putExtra("userObject", user);
                startActivity(intent);
            }
        });
    }
}
// BActivity.java
public class BActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        User user = (User) getIntent().getSerializableExtra("userObject");
        Log.d("cfmtest", "反序列化的user: " + user.toString());
        Log.d("cfmtest", "ID: " + user.getId());
        Log.d("cfmtest", "Name: " + user.getName());
        Log.d("cfmtest", "Dream: " + user.getDream());
    }
}
// Log 打印信息
cfmtest: 序列化的user: com.cfm.serializabletest.User@1f02ac1
cfmtest: 反序列化的user: com.cfm.serializabletest.User@d5d2208
cfmtest: ID: 1
cfmtest: Name: cfm
cfmtest: Dream: open source

    结论: 可以看到我们可以通过 Intent 传输该对象,并且获取到的 User 对象和传输的 User 对象只是内容一样,但不是同一个对象。

eg2:将 User 对象持久化到设备中,然后再反序列获取该对象。

public class AActivity extends AppCompatActivity {
    private ObjectOutputStream mOutputStream;
    private ObjectInputStream mInputStream;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button writeBtn = findViewById(R.id.write_btn);
        writeBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    User user = new User(1, "cfm", "open source");
                    Log.d("cfmtest", "序列化的user: " + user.toString());
                    mOutputStream = new ObjectOutputStream(openFileOutput("test.txt", Context.MODE_PRIVATE));
                    mOutputStream.writeObject(user);
                    mOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        Button readBtn = findViewById(R.id.read_btn);
        readBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    mInputStream = new ObjectInputStream(openFileInput("test.txt"));
                    User newUser = (User) mInputStream.readObject();
                    Log.d("cfmtest", "反序列化的user: " + newUser.toString());
                    Log.d("cfmtest", "ID: " + newUser.getId());
                    Log.d("cfmtest", "Name: " + newUser.getName());
                    Log.d("cfmtest", "Dream: " + newUser.getDream());
                    mInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

// output
cfmtest: 序列化的user: com.cfm.serializabletest.User@630369f
cfmtest: 反序列化的user: com.cfm.serializabletest.User@b46c484
cfmtest: ID: 1
cfmtest: Name: cfm
cfmtest: Dream: open source

    结论:同样,我们可以看到序列化的对象和最后反序列的对象不是同一个对象,但是内容一样。

serialVersionUID 详解:

    serialVersionUID 是用来辅助序列化和反序列化过程的,原则上序列化后数据中的 serialVersionUID 只有和当前类的 serialVersionUID 一样才能够正常地被反序列化。如果我们不手动指定 serialVersionUID 的值,那么系统就会自动将当前类的 hash 值作为 serialVersionUID 赋值给它,而且这个 hash 值会随着当前类的变化(比如增加或者删除了某些成员变量)而改变,所以这样就很容易导致序列化时对象的 serialVersionUID 和反序列化时当前类的 serialVersionUID 不一样,从而导致反序列化失败。当然,即使我们手动指定了 serialVersionUID 的值,但是当前类的类结构发生了非常规性的改变(比如修改了类名,修改了成员变量的类型等等),即使 serialVersionUID 验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

    serialVersionUID 的工作机制:序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也有可能是其它的介质),当反序列化的时候系统会检查当前类的 serialVersionUID 和文件中的 serialVersionUID 是否一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变化,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。

    通常情况下,我们通过两种方式为序列化类指定 serialVersionUID,一种是直接指定为 1L,另一种是直接将当前类生成的 hash 值显式的赋值给它。这两种方式本质上没有什么不同,都能实现同样的目的。

    注意:

    1. 静态成员变量属于类不属于对象,所以不会参与序列化过程。

    2. transient 关键字修饰的成员变量不参与序列化过程。

Serializable 原理剖析:

    怎么剖析?无非就是分析完成序列化和反序列过程调用的方法的源码嘛:

    先看序列化过程:ObjectOutputStream.writeObject():

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {          // 1
        writeObjectOverride(obj);
        return;
    }

    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        ...
    }
}
public ObjectOutputStream(OutputStream out) throws IOException {
    ...
    enableOverride = false;   // 所以 1 不调用
    ...
}

    接着调用: writeObject0(obj, false);

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
            ...
            Class<?> cl = obj.getClass();   // 通过反射得到 obj 的 Class 对象
            ObjectStreamClass desc;

            Class repCl;
            desc = ObjectStreamClass.lookup(cl, true); // 返回给定类的类描述符

            if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
            // END Android-changed:  Make Class and ObjectStreamClass replaceable.
            } else if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);  // 我们这里是实现 Serializable 接口的对象,所以跳转到这个方法
            } else {
                ...
            }
        } finally {
            ...
        }
    }

    接着跳转到:writeOrdinaryObject(obj, desc, unshared)

private void writeOrdinaryObject(Object obj, ObjectStreamClass desc, boolean unshared) throws IOException
{
    ...
    try {
        ...
        if (desc.isExternalizable() && !desc.isProxy()) { 
            writeExternalData((Externalizable) obj);
        } else {
            writeSerialData(obj, desc);
        }
   ...
}

    上面 writeOrdinaryObject() 方法内部判断当前类是实现了 Externalizable 接口还是 Serializable 接口,我们当前类实现的是 Serializable 接口,所以接着跳转到 writeSerialData(obj, desc) 方法中:

private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException
{
    ...
    if (slotDesc.hasWriteObjectMethod()) {
            ...
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } ...
        } else {
            defaultWriteFields(obj, slotDesc);
        }
}
writeObjectMethod = getPrivateMethod(cl, "writeObject", new Class<?>[] { ObjectOutputStream.class }, Void.TYPE);  // 通过反射获取 writeObject(ObjectOutputStream) 方法
boolean hasWriteObjectMethod() { return (writeObjectMethod != null); }  // 如果当前类中有 writeObject(ObjectOutputStream) 方法,则返回 ture,否则返回 false
void invokeWriteObject(Object obj, ObjectOutputStream out) throws IOException, UnsupportedOperationException
{
    if (writeObjectMethod != null) {
        try {
            writeObjectMethod.invoke(obj, new Object[]{ out });  // 通过反射调用当前类中的 writeObject(ObjectOutputStream)
        }
        ...
    }
}

    可以看到这里通过调用 hasWriteObjectMethod() 方法判断当前类是否有 writeObject(ObjectOutputStream) 方法。如果有则调用 invokeWriteObject() 方法,该方法最终还是通过反射调用当前类中 writeObject(ObjectOutputStream),如果没有则调用 defaultWriteFields(obj, slotDesc) 方法。

    同理,ObjectInputStream.readObject() 分析一样,如果当前类中有 readObject(ObjectInputStream) 方法,则调用该方法,否则调用 defaultReadFields(obj, slotDesc) 方法。

    综上,我们可以看到,系统默认的序列化过程是可以改变的,只要在当前类中定义 writeObject(ObjectOutputStream)  readObject(ObjectInputStream) 即可!

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值