Android | 序列化Serializable/Parcelable 使用总结

article/2025/8/5 4:15:15

本文已同步发表于我的微信公众号,搜索 代码说 即可关注,欢迎与我沟通交流。

文章目录

    • 一、什么是序列化?为什么要序列化?怎么进行序列化?
    • 二、Serializable
      • 2.1 序列化举例
      • 2.2 重写readObject、writeObject、readResolve、writeReplace
      • 2.3 serialVersionUID
      • 2.4 实现原理
      • 2.5 Externalizable
    • 三、Parcelable
      • 3.1 序列化举例
      • 3.2 实现原理
    • 四、Parcelable、Serializable比较
      • 4.1 效率对比
      • 4.2 容错率对比
    • 五、总结
    • 六、参考

一、什么是序列化?为什么要序列化?怎么进行序列化?

序列化定义:将一个类对象转换成可存储、可传输状态的过程。序列化有两个过程:

1、序列化:将对象编码成字节流(serializing)
2、反序列化:从字节流编码中重新构建对象(deserializing)。对象序列化后,可以在进程内/进程间、网络间进行传输,也可以做本地持久化存储。

为什么要序列化: 系统底层并不认识对象,数据传输是以字节序列形式传递,以进程间通信为例,需要将对象转化为字节序列(字节序列中包括该对象的类型,成员信息等),然后在目标进程里通过反序列化字节序列,将字节序列转换成对象。

序列化方式:

  • Serializable(Java提供 后面简称为S)
  • Parcelable(Android特有 下面简称为P)

二、Serializable

S是Java API,是一个通用的序列化机制,可以将对象序列化并保存在本地或内存中。S是一个空接口:

public interface Serializable {}

S只起到了一个标识的作用,用于告知程序实现了Serializable的对象是可以被序列化的,但真正进行序列化和反序列化的操作是通过ObjectOutputStreamObjectInputStream实现的。

2.1 序列化举例

S_Shop.java:

public class S_Shop implements Serializable {private static final long serialVersionUID = -1399695071515887643L;public String mShopName;public int mShopId;public String mShopPhone;public static int STATIC_VALUE = 100;//静态值public transient int TRANSIENT_VALUE;//被transient修饰 不能序列化@NonNull@Overridepublic String toString() {return "Serializable: mShopName is " + mShopName+ ",mShopId is " + mShopId+ ",mShopPhone is " + mShopPhone+ ",STATIC_VALUE is " + STATIC_VALUE+ ",TRANSIENT_VALUE is " + TRANSIENT_VALUE;}
}

执行序列化和反序列化过程:

    public static void main(String[] args) throws IOException {//------------------Serializable------------------S_Shop shop = new S_Shop();shop.mShopName = "便利蜂";shop.mShopId = 2020;shop.mShopPhone = "18888888888";shop.TRANSIENT_VALUE = 1000;saveObject(shop); //序列化readObject();//反序列化}//序列化private static void saveObject(S_Shop shop) {ObjectOutputStream outputStream = null;try {outputStream = new ObjectOutputStream(new FileOutputStream("shop.obj"));outputStream.writeObject(shop);System.out.println("write-hashCode: " + shop.hashCode());outputStream.close();} catch (IOException e) {e.printStackTrace();} finally {if (outputStream != null) {try {outputStream.close();} catch (IOException e) {e.printStackTrace();}}}}private static void readObject() {//反序列化ObjectInputStream inputStream = null;try {inputStream = new ObjectInputStream(new FileInputStream("shop.obj"));S_Shop shop = (S_Shop) inputStream.readObject();System.out.println(shop.toString());System.out.println("read-hashCode: " + shop.hashCode());} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}}

执行结果:

Serializable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888,STATIC_VALUE is 100,TRANSIENT_VALUE is 0

结果看到反序列化成功,从序列化结构中又重新生成了对象,这里注意一点,类中的变量TRANSIENT_VALUE是由transient修饰的,不能被序列化,所以反序列化时得到的是默认值。另外STATIC_VALUEstatic修饰,也不参与序列化过程。

2.2 重写readObject、writeObject、readResolve、writeReplace

一般来讲,只有当你自行设计的自定义序列化形式与默认的序列化形式基本相同时,才能接受默认的序列化形式。否则就要设计一个自定义的序列化形式,通过它合理地描述对象的状态。——《Effective Java》

Serializable实现自定义序列化必须重写readObject、writeObject方法,readResolve、writeReplace方法是可选的,看下面的例子:

public class S_Shop implements Serializable{private static final long serialVersionUID = -1399695071515887643L;public transient String mShopName;//注意mShopName是瞬态的public int mShopId;public String mShopPhone;/*** 序列化时执行 执行顺序早于writeObject 可以在此方法中做一些替换*/private Object writeReplace() {System.out.println("-----writeReplace() start-----");S_Shop shop = new S_Shop();shop.mShopName = "物美超市";//将mShopName替换shop.mShopId = mShopId;shop.mShopPhone = mShopPhone;return shop;}/*** 序列化时执行 通过defaultWriteObject将非transient字段序列化 也可以自定义序列化字段*/private void writeObject(ObjectOutputStream outputStream) throws IOException {System.out.println("-----writeObject() start-----");outputStream.defaultWriteObject();outputStream.writeObject(mShopName);}/*** 反序列化时执行 通过defaultReadObject将非transient字段反序列化 也可以将自定义字段反序列化*/private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException {System.out.println("-----readObject() start-----");inputStream.defaultReadObject();mShopName = (String) inputStream.readObject();}/*** 反序列化时执行,执行顺序在readObject之后 可以在此方法中重新生成一个新对象*/private Object readResolve() {System.out.println("-----readResolve() start-----");S_Shop shop = new S_Shop();shop.mShopName = mShopName;shop.mShopId = mShopId;shop.mShopPhone = "12345678";//将mShopPhone替换return shop;}@NonNull@Overridepublic String toString() {return "Serializable: mShopName is " + mShopName+ ",mShopId is " + mShopId+ ",mShopPhone is " + mShopPhone;}
}

执行结果:

修改前:Serializable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888
-----writeReplace() start-----
-----writeObject() start-----
-----readObject() start-----
-----readResolve() start-----
修改后:Serializable: mShopName is 物美超市,mShopId is 2020,mShopPhone is 12345678

序列化过程的执行顺序:writeReplace->writeObject;反序列化过程的执行顺序:readObject->readResolve 通过上面四个方法,可以实现Serializable的自定义序列化。

:虽然上述的四个方法都是private级别的,但在反序列化过程中是通过反射执行的。

2.3 serialVersionUID

序列化会导致类的演变收到限制。这种限制与序列化唯一标识符serialVersionUID(后面简称sUID)有关,每个可序列化的类都有一个唯一标识号与它相关,sUID用来辅助序列化和反序列化的,序列化过程中会把类中的sUID写入序列化文件中。在反序列化时,检测序列化文件中sUID和当前类中的sUID是否一致,如果一致,才可以继续进行反序列化操作,否则说明序列化后类发生了一些改变,比如成员变量的类型发生改变等,此时是不能反序列化的。

是否需要指定serialVersionUID? 答案是肯定的,如果不指定sUID,在序列化时系统也会经过一个复杂运算过程,自动帮我们生成一个并写入序列化文件中。sUID的值受当前类名称、当前类实现的接口名称、以及所有公有、受保护的成员名称等所影响,此时即使当前类发生了微小的变化(如添加/删除一个不重要的方法)也会导致sUID改变,进而反序列化失败;如果指定了sUID,上述操作依然可以进行反序列化,但一些类结构发生改变,如类名改变、成员变量的类型发生了改变,此时即使sUID验证通过了,反序列化依然会失败。

2.4 实现原理

使用hexdump命令来查看上述生成的shop.obj二进制文件:

0000000 ac ed 00 05 73 72 00 1a 63 6f 6d 2e 65 78 61 6d
0000010 70 6c 65 2e 64 65 6d 6f 61 70 70 2e 53 5f 53 68
0000020 6f 70 ec 93 48 bf 94 6e 37 e5 02 00 03 49 00 07
0000030 6d 53 68 6f 70 49 64 4c 00 09 6d 53 68 6f 70 4e
0000040 61 6d 65 74 00 12 4c 6a 61 76 61 2f 6c 61 6e 67
0000050 2f 53 74 72 69 6e 67 3b 4c 00 0a 6d 53 68 6f 70
0000060 50 68 6f 6e 65 71 00 7e 00 01 78 70 00 00 07 e4
0000070 74 00 09 e4 be bf e5 88 a9 e8 9c 82 74 00 0b 31
0000080 38 38 38 38 38 38 38 38 38 38                  
000008a
  • AC ED: STREAM_MAGIC. 声明使用了序列化协议.
  • 00 05: STREAM_VERSION. 序列化协议版本.
  • 0x73: TC_OBJECT. 声明这是一个新的对象.
  • 0x72: TC_CLASSDESC. 声明这里开始一个新Class
  • 00 1a: Class名字的长度.

序列化步骤:

  • 将对象实例相关的类元数据输出
  • 递归地输出类的超类描述直到不再有超类
  • 类元数据完了之后,开始从最顶层的超类开始输出对象实例的实际数据值
  • 从上至下递归输出实例的数据

Serializable 的序列化与反序列化分别通过 ObjectOutputStreamObjectInputStream 进行,都是在Java层实现的。两个相关概念:
ObjectStreamClass: 序列化类的描述符。它包含类的名称和serialVersionUID。它由Java VM加载,可以使用lookup方法找到或创建。
ObjectStreamField: 类(可序列化)的可序列化字段的描述。ObjectStreamFields数组用于声明类的可序列化字段。

1、序列化过程(writeObject方法)

  • 通过ObjectStreamClass记录目标对象的类型、类名等信息,内部有个ObjectStreamFields数组,用来记录目标对象的内部变量(内部变量可以是基本类型,也可以是自定义类型,但是必须都支持序列化—必须是S不能是P)。
  • 首先通过ObjectStreamClass.lookup()找到或创建ObjectStreamClass,然后调用defaultWriteFields方法,在方法中通过getPrimFieldValues()获取基本数据类型并赋值到primVals(byte[]类型)中,再通过getObjFieldValues()获取到自定义对象(通过Unsafe类实现而不是反射)并赋值到objVals(Object[]类型)中,接着遍历objVals数组,然后递归调用writeObject方法重复上述操作。
  • 调用过程:writeObject() -> writeObject0()-> writeOrdinaryObject() -> writeSerialData() -> invokeWriteObject() -> defaultWriteFields()

2、反序列化过程(readObject方法)

  • 通过readClassDescriptor()读取InputStream里的数据并初始化ObjectStreamClass类,再根据这个实例通过反射创建目标对象实例。
  • 调用过程:readObject() -> readObject0() -> readOrdinaryObject() -> readSerialData() -> defaultReadFields()

Serializable常见异常

异常名称异常起因
java.io.InvalidClassException1、序列化时自动生成serialVersionUID,此时改变类名、类实现的接口名、内部成员变化或添加/删除方法,都会导致serialVersionUID改变,反序列化时就会抛出此异常
java.io.NotSerializableException当前类或类中成员变量未实现序列化

2.5 Externalizable

Externalizable 继承自Serializable,并在其基础上添加了两个方法:writeExternal()readExternal()。这两个方法在序列化和反序列化时会被执行,从而可以实现一些特殊的需求(如指定哪些元素不参与序列化,作用等同于transient)。如果说默认的Serializable序列化方式是自动序列化,那么Externalizable就是手动序列化了,通过writeExternal()指定参与序列化的内部变量个数,然后通过readExternal()反序列化重新生成对象。

public class S_Shop_External implements Externalizable {private static final long serialVersionUID = -61368254488136487L;public String mShopName;public int mShopId;public String mShopPhone;public S_Shop_External() {System.out.println("S_Shop_External()构造方法");}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(mShopName);out.writeInt(mShopId);out.writeObject(mShopPhone);}@Overridepublic void readExternal(ObjectInput in) throws ClassNotFoundException, IOException {mShopName = (String) in.readObject();mShopId = in.readInt();mShopPhone = (String) in.readObject();}@NonNull@Overridepublic String toString() {return "Serializable: mShopName is " + mShopName+ ",mShopId is " + mShopId+ ",mShopPhone is " + mShopPhone;}
}

执行代码跟Serializable一样,只是将对象变成了S_Shop_External,执行结果:

Externalizable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888

readExternal()中得到的数据都是在writeExternal()中写入的数据。

Externalizable常见异常

异常名称异常起因
java.io.InvalidClassException: no valid constructor反序列化时,必须要有修饰符为public的默认构造参数
java.io.OptionalDataException:readExternal反序列化时readExternal添加元素、删除末尾之外的元素、修改元素类型

三、Parcelable

P是Android SDK API,其序列化操作完全由底层实现,可以在进程内、进程间(AIDL)高效传输数据。不同版本的API实现方式可能不同,不宜做本地持久化存储。

3.1 序列化举例

P_Shop.java:

public class P_Shop implements Parcelable {public P_Shop(){}public String mShopName;public int mShopId;public String mShopPhone;public static int STATIC_VALUE = 100;//静态值public transient int TRANSIENT_VALUE;//被transient修饰 不能序列化/*** 从序列化结构中创建原始对象*/protected P_Shop(Parcel in) {mShopName = in.readString();mShopId = in.readInt();mShopPhone = in.readString();}/*** 反序列化*/public static final Creator<P_Shop> CREATOR = new Creator<P_Shop>() {/*** 从序列化对象中创建原始对象*/@Overridepublic P_Shop createFromParcel(Parcel in) {return new P_Shop(in);}/*** 创建指定长度的原始对象数组*/@Overridepublic P_Shop[] newArray(int size) {return new P_Shop[size];}};/*** 序列化:将当前对象写入序列化结构中*/@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(mShopName);dest.writeInt(mShopId);dest.writeString(mShopPhone);}/*** 当前对象的内容描述,存在文件描述符时返回1  其余全返回0*/@Overridepublic int describeContents() {return 0;}@NonNull@Overridepublic String toString() {return "Parcelable: mShopName is " + mShopName+ ",mShopId is " + mShopId+ ",mShopPhone is " + mShopPhone+ ",STATIC_VALUE is " + STATIC_VALUE+ ",TRANSIENT_VALUE is " + TRANSIENT_VALUE;}
}

注意createFromParcel()writeToParcel()方法中对应变量读写的顺序必须是一致的,否则序列化会失败。

Parcel处理工具:

public class PUtil {private static final String SP_NAME = "sp_parcel";private static final String PARCEL_KEY = "parcel_key";//marshall Parcel将自身保存的数据以byte数组形式返回public static byte[] marshall(Parcelable parcelable) {Parcel parcel = Parcel.obtain();parcel.setDataPosition(0);parcel.writeValue(parcelable);byte[] bytes = parcel.marshall();parcel.recycle();return bytes;}//将bytes经过base64转换成字符串并存储到sp中public static void save(Context context, byte[] bytes) {SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);SharedPreferences.Editor editor = preferences.edit();String saveStr = Base64.encodeToString(bytes, 0);editor.putString(PARCEL_KEY, saveStr);editor.apply();}//从sp中取出字符串并转换成bytes 然后bytes->Parcel->Objectpublic static Object getParcel(Context context) {SharedPreferences preferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);byte[] bytes = Base64.decode(preferences.getString(PARCEL_KEY, "").getBytes(), Base64.DEFAULT);//从bytes中获取ParcelParcel parcel = unmarshall(bytes);return parcel.readValue(context.getClassLoader());}//从byte数组中获取数据,存入自身的Parcel中private static Parcel unmarshall(byte[] bytes) {Parcel parcel = Parcel.obtain();parcel.unmarshall(bytes, 0, bytes.length);parcel.setDataPosition(0);return parcel;}
}

执行序列化/反序列化:

        //------------------Parcelable------------------Context context = this;P_Shop shopP = new P_Shop();shopP.mShopName = "便利蜂";shopP.mShopId = 2020;shopP.mShopPhone = "18888888888";shopP.TRANSIENT_VALUE = 1000;//序列化过程byte[] bytes = PUtil.marshall(shopP);//Parcel->bytes[]PUtil.save(context, bytes);//保存bytes[]//反序列化过程Object object = PUtil.getParcel(context);//bytes[]->Parcel->Objectif (object == null) return;if (object instanceof P_Shop) {P_Shop shop = (P_Shop) object;Log.e("TTT", shop.toString());}

执行结果:

Parcelable: mShopName is 便利蜂,mShopId is 2020,mShopPhone is 18888888888,STATIC_VALUE is 100,TRANSIENT_VALUE is 0

3.2 实现原理

P序列化过程中会用到ParcelParcel可以被认为是一个包含数据或者对象引用的容器,能够支持序列化及在跨进程之后的反序列化。P的序列化操作在Native层实现,通过write内存写入及read读内存数据重新生成对象。P将对象进行分解,且分解后每一部分都是支持可传递的数据类型。

序列化过程(Parcelable的写过程)

调用过程Parcel.writeValue()->writeParcelable(),下面主要来看下此方法:

 public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {if (p == null) {writeString(null);return;}//1、先写入序列化类名writeParcelableCreator(p);//2、调用类中复写的writeToParcel方法按顺序写入p.writeToParcel(this, parcelableFlags);}//写入序列化类名public final void writeParcelableCreator(@NonNull Parcelable p) {String name = p.getClass().getName();writeString(name);}

序列化过程中,首先写入序列化类名,然后调用类中复写的writeToParcel()方法依次写入

反序列化过程(Parcelable的读过程)

调用过程:Pacel.readValue()->readParcelable()

    public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {//1、通过反射或缓存获取序列化类中的CREATORParcelable.Creator<?> creator = readParcelableCreator(loader);if (creator == null) {return null;}if (creator instanceof Parcelable.ClassLoaderCreator<?>) {Parcelable.ClassLoaderCreator<?> classLoaderCreator =(Parcelable.ClassLoaderCreator<?>) creator;return (T) classLoaderCreator.createFromParcel(this, loader);}//2、调用CREATOR中的createFromParcel进行反序列化return (T) creator.createFromParcel(this);}private static final HashMap<ClassLoader,HashMap<String,Parcelable.Creator<?> mCreators = new HashMap<>();public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {//1、首先读取之前写入的类名String name = readString();if (name == null) {return null;}Parcelable.Creator<?> creator;synchronized (mCreators) {//如果之前某个classLoader缓存过Parcelable的Creator,然后通过mCreators缓存过,//那么直接从缓存取;否则通过反射去加载并加入缓存中HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);if (map == null) {map = new HashMap<>();mCreators.put(loader, map);}creator = map.get(name);if (creator == null) {try {ClassLoader parcelableClassLoader =(loader == null ? getClass().getClassLoader() : loader);//通过反射去获取Parcelable中的CREATORClass<?> parcelableClass = Class.forName(name, false /* initialize */,parcelableClassLoader);Field f = parcelableClass.getField("CREATOR");Class<?> creatorType = f.getType();creator = (Parcelable.Creator<?>) f.get(null);}map.put(name, creator);}}return creator;}

四、Parcelable、Serializable比较

4.1 效率对比

S序列化和反序列化会经过大量的I/O操作,产生大量的临时变量引起GC;P是基于内存实现的封装和解封(marshalled& unmarshalled),效率比S快很多。

下面的测试来自非官方测试,通过ParcelableSerializable分别执行序列化/反序列化过程,循环1000次取平均值,实验结果如下:

效率对比

数据来自 parcelable-vs-serializable,实验结果对比Parcelable的效率比Serializable快10倍以上。

4.2 容错率对比

序列化到本地时,新版本字段改变对旧版本反序列化的影响

改变字段默认的Serializable序列化方式ExternalizableParcelable
增加字段✔️追加到末尾:✔️ 其他:❌
删除字段✔️删除末尾:✔️ 其他:❌
修改字段类型

总结:

  • Externalizable中,writeExternal参与序列化,readExternal参与的是反序列化。readExternal()中读入的元素一定是writeExternal()中写入过的,且读写的顺序、字段类型要一致。另外,readExternal中的元素可以少于writeExternal中的,但是注意少的元素一定是在末尾的元素(即不能删除前面的元素),否则反序列化就会失败。
  • 对于Parcelable来说,如果新版本中修改字段类型,那么该字段反序列化时会失败;如果是添加字段,那么反序列化时在添加字段位置到末尾位置都会失败;同样删除字段,反序列化时在删除字段的位置到末尾位置都会失败。

五、总结

对比SerializableParcelable
所属APIJava APIAndroid SDK API
特点序列化和反序列化会经过大量的I/O操作,产生大量的临时变量引起GC,且反序列化时需要反射基于内存拷贝实现的封装和解封(marshalled& unmarshalled),序列化基于Native层实现,不同版本的API实现可能不同
开销相对高相对低
效率相对低相对高
适用场景序列化到本地、网络传输主要内存序列化

另外序列化过程中的几个注意点:

  • 下面两种成员变量不会参与到默认序列化过程中:
    1、static静态变量属于类而不属于对象
    2、transient标记的成员变量
  • 参与序列化的成员变量本身也是需要可序列化的
  • 反序列化时,非可序列化的(如被transient修饰)变量将会调用自身的无参构造函数重新创建,因此也要求此成员变量的构造函数必须是可访问的,否则会报错。

六、参考

【1】Android 面试(七):Serializable 这么牛逼,Parcelable 要你何用?
【2】每日一问 Parcelable 为什么效率高于 Serializable ?
【3】Android中两种序列化方式的比较Serializable和Parcelable
【4】Android之序列化详解
【5】Android 开发之漫漫长途 X——Android序列化


http://chatgpt.dhexx.cn/article/gyB13sr9.shtml

相关文章

Android应用程序设计——入门配置

第一章:Android简介与项目部署 版权声明: 本文为博主学习整理原创文章,如有不正之处请多多指教。 未经博主允许不得转载。https://mp.csdn.net/postedit/81837814 Android由来Android又称“机器人”,版权归google公司所有。是Google于2007年11月5日宣布的基于Linux平台的…

Android程序设计基础 • 【第1章 Android程序入门】

全部章节 >>>> 本章目录 1.1 Android 平台介绍 1.1.1 Android 简介 1.1.2 Android 平台的发展 1.1.3 Android 平台架构 1.2 搭建 Android 开发环境 1.2.1 下载和配置 Android Studio 1.2.2 初始化配置 Android Studio 1.2.3 配置和使用 Genymotion 模拟器…

Android自动化的一般方法

1 背景 https://mp.weixin.qq.com/s/B7o3JSkYI_9c4RivatsZKQ Android自动化包含自动化测试和第三方App的自动化运行&#xff0c;这里的自动化测试主要指的是纯粹的黑盒测试&#xff0c;即在完全不了解代码逻辑的情况下编写的测试用例&#xff0c;可以代替人工完成重复性的工…

基于Android的点餐系统设计与实现

目 录 摘 要 2 ABSTRACT 3 目 录 1 1 绪 论 1 1.1课题研究背景和意义 1 1.2课题可行性分析 2 1.3论文框架安排 3 2 开发语言说明 4 2.1 开发工具及环境简介 4 2.2 Android系统 5 2.2.1 Android Features特性 5 2.2.2 Android系统的四大组件 6 3 点餐系统分析 8 3.1概述 8 3.1.1…

基于Android的记账本的设计与实现

1.基于Android平台的记账系统概述 记账就是按照生活中的发生的经济活动&#xff0c;及时进行记录&#xff0c;为了便于分析个人消费和各种财务状况等等&#xff0c;通过您的随身会计——记账本APP更易看出资金的流动方向。 本论文采用的是一种可移动的记账方法&#xff0c;由移…

Android设计模式之——模板方法模式

一、介绍 在面向对象开发过程中&#xff0c;通常会遇到这样的一个问题&#xff0c;我们知道一个算法所需的关键步骤&#xff0c;并确定了这些步骤的执行顺序&#xff0c;但是&#xff0c;某些步骤的具体实现是未知的&#xff0c;或者说某些步骤的实现是会随着环境的变化而改变…

Android程序设计基础

#Android应用程序架构 Android程序的入口被称为Main Activity&#xff0c;带有界面的Android应用程序一般需要从Main Activity启动。 目录结构 Adnroid工程主要包括两个区域&#xff1a;源码区和资源区。 Mainifest.xml 下面是一个标准的AndroidManifest.xml文件样例。 <…

qt如何编写android程序,如何利用Qt开发Android应用程序

Qt 工具箱是一个使用广泛的跨平台GUI工具箱,可用于 Windows、Linux、Mac OSX 和许多手持平台。QT 具有良好结构化(但灵活)的面向对象的结构、清晰的文档以及直观的 API。 Qt最大的优势就是跨平台,一次性开发的Qt应用程序,可以跑Windows、跑linux、跑MAC……如今移动平台Andr…

Android插件化方案实践

一、插件化概述 1、插件化和组件化的区别 组件化是将一个app拆分为多个模块进行协作开发&#xff0c;每个模块都是一个单独的组件&#xff0c;这些组件可以相互依赖&#xff0c;也可以单独调试运行。但是最终发布的时候&#xff0c;这些组件会合并在一起&#xff0c;组成一个整…

Android手机开发课程设计之记事本

一、需求分析 1.1业务需求分析 近年来&#xff0c;随着生活节奏的加快&#xff0c;工作和生活的双重压力全面侵袭着人们&#xff0c;如何避免忘记工作和生活中的诸多事情而造成不良的后果就显得非常重要。为此我们开发一款基于Android系统的简单记事本&#xff0c;其能够便携…

MATLAB图形绘制--添加图例

添加图例 专业的图像总是附有图例来告诉读者曲线表示什么&#xff0c;下面我们将绘制两个表示势能的函数&#xff0c;他们由双曲三角函数sinh(x)和cosh(x)来定义&#xff0c;X的定义域为0&#xff5e;2&#xff0c;首先我们定义X&#xff1a; >> x [0:0.01:2];然后表示…

matlab 图例自定义,matlab中如何自定义图例_常见问题解析

pd接口是什么口_常见问题解析 pd接口也是“Type-C”的接口&#xff0c;支持扩充协议&#xff0c;可以给手机笔记本充电&#xff0c;也可以传输数据&#xff1b;而“Type-C”中的PD的意思指的是“USB Power Delivery”功率传输协议。 matlab中如何自定义图例&#xff1f;Matlab如…

Matlab 画多个图例( Plot multiple legends )

用matlab 画图时&#xff0c;发现线太多&#xff0c;生成的图例&#xff0c;遮盖了曲线。于是想画成多个图例&#xff0c;然后可以自由拖动。 废话不多说&#xff0c;2&#xff09;代码&#xff08;设置多个图例的部分在最后20行&#xff09;&#xff1b; 1&#xff09;效果。 …

matlab之图例legend的数字变量显示

legend是matlab的图例使用函数&#xff0c;其主要的只用方式为legend(内容1,内容2) 本文主要介绍一下legend如何引用数组的数字表现形式。 例如&#xff0c;若想要画一个滤波器的不同反馈系数的频率响应曲线&#xff0c;在设置条例内容时&#xff0c;需要手动输入反馈系数K的数…

MATLAB图例变成一列变多列的方法

小编在绘制GPS数据图像时&#xff0c;发现图例中元素个数有31个&#xff0c;当采用默认生成图例时&#xff0c;只有一列图例&#xff0c;显示结果如下 图例非常难看&#xff0c;而且占位子&#xff0c;如何让图例从一列变成多列呢&#xff1f; 这里小编给大家提供一个思路&…

matlab把图例放在左边,如何将图例放在p之外

如何将图例放在p之外 我有一系列20个图(不是子图)可以在一个图中制作。 我希望传说能够超越盒子。 与此同时,我不想改变轴,因为图形的大小减少了。 请帮助我以下查询: 我想将情节框保留在情节区域之外。 (我希望传说位于情节区域的右侧)。 无论如何,我减少了图例框内文本的…

matlab之在坐标区上添加图例函数legend

目录 一、功能 二、语法 1.legend(label1,...,labelN) 2.legend(labels) 3.legend(subset,___) 4.legend(target,___) 5.legend(___,Location,lcn) 6.legend(___,Orientation,ornt) 7.legend(___,Name,Value) 8.legend(bkgd) 9.lgd legend(___) 10.legend(v…

Matlab作图后的各种调整方法——线条、坐标、标题、图例

Matlab作图后的各种调整方法——线条、坐标、标题、图例 文章目录 Matlab作图后的各种调整方法——线条、坐标、标题、图例一 &#xff0c; 写在前面1.整个图窗 Figure&#xff08;gcf&#xff09;2.我们使用命令做出的线条 Line&#xff0c;例如plot命令3.坐标轴 Axes (gca) 二…

Matlab图例Legend多行排布、字体格式

适用于图例文字多&#xff0c;简单排布效果差的情形 1. 绘制图形&#xff0c;添加图例 %数据 x[1 2 3 4]; y1[16 2 3 13]; y2[5 11 10 8]; y3[9 7 6 12]; y4[4 14 15 1]; %画图plot(x,y1,r-o); %红色&#xff0c;实线&#xff0c;圆圈 hold on; plot(x,y2,k-d…

在MATLAB中的图例标注及实例说明

1.基本绘图函数 plot(Y)&#xff1a;其中输入参数Y就是Y轴的数据&#xff0c;一般习惯性输入向量 plot(X1,Y1,LineSpec,...,Xn,Yn,LineSpec):LineSpec为选项&#xff08;开关量&#xff09;字符串&#xff0c;用于设置曲线颜色、线型、数据点等&#xff1b;LineSpec的标准设定…