1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/**
* {@link ViewHierarchyEncoder} is a serializer that is tailored towards writing out
* view hierarchies (the view tree, along with the properties for each view) to a stream.
*
* It is typically used as follows:
* <pre>
* ViewHierarchyEncoder e = new ViewHierarchyEncoder();
*
* for (View view : views) {
* e.beginObject(view);
* e.addProperty("prop1", value);
* ...
* e.endObject();
* }
*
* // repeat above snippet for each view, finally end with:
* e.endStream();
* </pre>
*
* <p>On the stream, a snippet such as the above gets encoded as a series of Map's (one
* corresponding to each view) with the property name as the key and the property value
* as the value.
*
* <p>Since the property names are practically the same across all views, rather than using
* the property name directly as the key, we use a short integer id corresponding to each
* property name as the key. A final map is added at the end which contains the mapping
* from the integer to its property name.
*
* <p>A value is encoded as a single byte type identifier followed by the encoding of the
* value. Only primitive types are supported as values, in addition to the Map type.
*
* @hide
*/
public class ViewHierarchyEncoder {
// Prefixes for simple primitives. These match the JNI definitions.
private static final byte SIG_BOOLEAN = 'Z';
private static final byte SIG_BYTE = 'B';
private static final byte SIG_SHORT = 'S';
private static final byte SIG_INT = 'I';
private static final byte SIG_LONG = 'J';
private static final byte SIG_FLOAT = 'F';
private static final byte SIG_DOUBLE = 'D';
// Prefixes for some commonly used objects
private static final byte SIG_STRING = 'R';
private static final byte SIG_MAP = 'M'; // a map with an short key
private static final short SIG_END_MAP = 0;
private final DataOutputStream mStream;
private final Map<String,Short> mPropertyNames = new HashMap<String, Short>(200);
private short mPropertyId = 1;
private Charset mCharset = Charset.forName("utf-8");
public ViewHierarchyEncoder(@NonNull ByteArrayOutputStream stream) {
mStream = new DataOutputStream(stream);
}
public void beginObject(@NonNull Object o) {
startPropertyMap();
addProperty("meta:__name__", o.getClass().getName());
addProperty("meta:__hash__", o.hashCode());
}
public void endObject() {
endPropertyMap();
}
public void endStream() {
// write out the string table
startPropertyMap();
addProperty("__name__", "propertyIndex");
for (Map.Entry<String,Short> entry : mPropertyNames.entrySet()) {
writeShort(entry.getValue());
writeString(entry.getKey());
}
endPropertyMap();
}
@UnsupportedAppUsage
public void addProperty(@NonNull String name, boolean v) {
writeShort(createPropertyIndex(name));
writeBoolean(v);
}
public void addProperty(@NonNull String name, short s) {
writeShort(createPropertyIndex(name));
writeShort(s);
}
@UnsupportedAppUsage
public void addProperty(@NonNull String name, int v) {
writeShort(createPropertyIndex(name));
writeInt(v);
}
@UnsupportedAppUsage
public void addProperty(@NonNull String name, float v) {
writeShort(createPropertyIndex(name));
writeFloat(v);
}
@UnsupportedAppUsage
public void addProperty(@NonNull String name, @Nullable String s) {
writeShort(createPropertyIndex(name));
writeString(s);
}
/**
* Writes the given name as the property name, and leaves it to the callee
* to fill in value for this property.
*/
public void addPropertyKey(@NonNull String name) {
writeShort(createPropertyIndex(name));
}
private short createPropertyIndex(@NonNull String name) {
Short index = mPropertyNames.get(name);
if (index == null) {
index = mPropertyId++;
mPropertyNames.put(name, index);
}
return index;
}
private void startPropertyMap() {
try {
mStream.write(SIG_MAP);
} catch (IOException e) {
// does not happen since the stream simply wraps a ByteArrayOutputStream
}
}
private void endPropertyMap() {
writeShort(SIG_END_MAP);
}
private void writeBoolean(boolean v) {
try {
mStream.write(SIG_BOOLEAN);
mStream.write(v ? 1 : 0);
} catch (IOException e) {
// does not happen since the stream simply wraps a ByteArrayOutputStream
}
}
private void writeShort(short s) {
try {
mStream.write(SIG_SHORT);
mStream.writeShort(s);
} catch (IOException e) {
// does not happen since the stream simply wraps a ByteArrayOutputStream
}
}
private void writeInt(int i) {
try {
mStream.write(SIG_INT);
mStream.writeInt(i);
} catch (IOException e) {
// does not happen since the stream simply wraps a ByteArrayOutputStream
}
}
private void writeFloat(float v) {
try {
mStream.write(SIG_FLOAT);
mStream.writeFloat(v);
} catch (IOException e) {
// does not happen since the stream simply wraps a ByteArrayOutputStream
}
}
private void writeString(@Nullable String s) {
if (s == null) {
s = "";
}
try {
mStream.write(SIG_STRING);
byte[] bytes = s.getBytes(mCharset);
short len = (short)Math.min(bytes.length, Short.MAX_VALUE);
mStream.writeShort(len);
mStream.write(bytes, 0, len);
} catch (IOException e) {
// does not happen since the stream simply wraps a ByteArrayOutputStream
}
}
}
|