上一章在读DatabaseHelper的初始化时,发现读不动了,因为ModelInfo应该放在前面读。因此,现在开始读ModelInfo。
final class ModelInfo { ////////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS ////////////////////////////////////////////////////////////////////////////////////// private Map<Class<? extends Model>, TableInfo> mTableInfos = new HashMap<Class<? extends Model>, TableInfo>(); private Map<Class<?>, TypeSerializer> mTypeSerializers = new HashMap<Class<?>, TypeSerializer>() { { put(Calendar.class, new CalendarSerializer()); put(java.sql.Date.class, new SqlDateSerializer()); put(java.util.Date.class, new UtilDateSerializer()); put(java.io.File.class, new FileSerializer()); } }; ……}
第一个Map mTableInfos储存了类与表参数的的键值对。第二个Map mTypeSerializers储存了SQL的日期,时间,文件的序列化类与接口的键值对。
…… public ModelInfo(Configuration configuration) { if (!loadModelFromMetaData(configuration)) { try { scanForModel(configuration.getContext()); } catch (IOException e) { Log.e("Couldn‘t open source path.", e); } } Log.i("ModelInfo loaded."); } ……
这是ModelInfo的构造方法,里面有两个主要方法一个是loadModelFromMetaData(Configuration),另一个是scanForModel(Context);下面我们一一阅读它们:
private boolean loadModelFromMetaData(Configuration configuration) { if (!configuration.isValid()) { return false; } final List<Class<? extends Model>> models = configuration.getModelClasses(); if (models != null) { for (Class<? extends Model> model : models) { mTableInfos.put(model, new TableInfo(model)); } } final List<Class<? extends TypeSerializer>> typeSerializers = configuration.getTypeSerializers(); if (typeSerializers != null) { for (Class<? extends TypeSerializer> typeSerializer : typeSerializers) { try { TypeSerializer instance = typeSerializer.newInstance(); mTypeSerializers.put(instance.getDeserializedType(), instance); } catch (InstantiationException e) { Log.e("Couldn‘t instantiate TypeSerializer.", e); } catch (IllegalAccessException e) { Log.e("IllegalAccessException", e); } } } return true; }
在上一段代码中我们看到:
……if (!loadModelFromMetaData(configuration)) { try { scanForModel(configuration.getContext()); }……
也就是说,这段代码只会选loadModelFromMetaData或scanForModel中的一个执行,判断的依据则是configuration.isValid()
public boolean isValid() { return mModelClasses != null && mModelClasses.size() > 0; }
显而易见,当configuration中的mModelClasses没有值时,代码会执行loadModelFromMetaData中的主要代码,否则执行scanForModel中的代码。
先看loadModelFromMetaData中的主要代码:
private boolean loadModelFromMetaData(Configuration configuration) { if (!configuration.isValid()) { return false; } final List<Class<? extends Model>> models = configuration.getModelClasses(); if (models != null) { for (Class<? extends Model> model : models) { mTableInfos.put(model, new TableInfo(model)); } } final List<Class<? extends TypeSerializer>> typeSerializers = configuration.getTypeSerializers(); if (typeSerializers != null) { for (Class<? extends TypeSerializer> typeSerializer : typeSerializers) { try { TypeSerializer instance = typeSerializer.newInstance(); mTypeSerializers.put(instance.getDeserializedType(), instance); } catch (InstantiationException e) { Log.e("Couldn‘t instantiate TypeSerializer.", e); } catch (IllegalAccessException e) { Log.e("IllegalAccessException", e); } } } return true; }
这一段代码,主要是从configuration中取出modelClasses和typeSerializers一个可存对象的列表,一个序列化列表。并为每一个model新建TableInfo,为每一种序列化新建对象。通常情况下,除非我们专门设置了mModelClasses,默认情况下,它是空的。因此,默认情况下,我们的代码不会走到这个分支。所以,通常会进入另一个分支scanForModel:
private void scanForModel(Context context) throws IOException { String packageName = context.getPackageName(); String sourcePath = context.getApplicationInfo().sourceDir; List<String> paths = new ArrayList<String>(); if (sourcePath != null && !(new File(sourcePath).isDirectory())) { DexFile dexfile = new DexFile(sourcePath); Enumeration<String> entries = dexfile.entries(); while (entries.hasMoreElements()) { paths.add(entries.nextElement()); } } // Robolectric fallback else { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); Enumeration<URL> resources = classLoader.getResources(""); while (resources.hasMoreElements()) { String path = resources.nextElement().getFile(); if (path.contains("bin") || path.contains("classes")) { paths.add(path); } } } for (String path : paths) { File file = new File(path); scanForModelClasses(file, packageName, context.getClassLoader()); } }
一行一行看:
String packageName = context.getPackageName(); String sourcePath = context.getApplicationInfo().sourceDir; List<String> paths = new ArrayList<String>();
首先获取包面和app的安装路径,并初始化路径列表。
if (sourcePath != null && !(new File(sourcePath).isDirectory())) { DexFile dexfile = new DexFile(sourcePath); Enumeration<String> entries = dexfile.entries();
while (entries.hasMoreElements()) { paths.add(entries.nextElement()); }
接着可以看到DexFile:google官方文档中这样解释Opens a DEX file from a given filename. This will usually be a ZIP/JAR file with a "classes.dex" inside. 我们直接打开了apk包,用apk包的中路径信息创建了DexFile对象。并用枚举型迭代器Enumeration,将apk中的所有路径加入到paths中。
通常使用时是不会进else部分的,else部分暂时先不看。
接下来是
for (String path : paths) { File file = new File(path); scanForModelClasses(file, packageName, context.getClassLoader()); }
获得 apk中所有路径后,将依次寻找这些路径中的model类。
private void scanForModelClasses(File path, String packageName, ClassLoader classLoader) { if (path.isDirectory()) { for (File file : path.listFiles()) { scanForModelClasses(file, packageName, classLoader); } } else { String className = path.getName(); // Robolectric fallback if (!path.getPath().equals(className)) { className = path.getPath(); if (className.endsWith(".class")) { className = className.substring(0, className.length() - 6); } else { return; } className = className.replace(System.getProperty("file.separator"), "."); int packageNameIndex = className.lastIndexOf(packageName); if (packageNameIndex < 0) { return; } className = className.substring(packageNameIndex); } try { Class<?> discoveredClass = Class.forName(className, false, classLoader); if (ReflectionUtils.isModel(discoveredClass)) { @SuppressWarnings("unchecked") Class<? extends Model> modelClass = (Class<? extends Model>) discoveredClass; mTableInfos.put(modelClass, new TableInfo(modelClass)); } else if (ReflectionUtils.isTypeSerializer(discoveredClass)) { TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance(); mTypeSerializers.put(instance.getDeserializedType(), instance); } } catch (ClassNotFoundException e) { Log.e("Couldn‘t create class.", e); } catch (InstantiationException e) { Log.e("Couldn‘t instantiate TypeSerializer.", e); } catch (IllegalAccessException e) { Log.e("IllegalAccessException", e); } } }
方法通过一个递归,遍历目录及子目录下的所有文件:
if (!path.getPath().equals(className)) { className = path.getPath(); if (className.endsWith(".class")) { className = className.substring(0, className.length() - 6); } else { return; } className = className.replace(System.getProperty("file.separator"), "."); int packageNameIndex = className.lastIndexOf(packageName); if (packageNameIndex < 0) { return; } className = className.substring(packageNameIndex); }
如果path.getName()与path.getPath不相等,则从修正文件的名字已获取类名,其中System.getProperty("file.separator")的意思是获得当前系统下分隔符的符号。
接下来:
try { Class<?> discoveredClass = Class.forName(className, false, classLoader); if (ReflectionUtils.isModel(discoveredClass)) { @SuppressWarnings("unchecked") Class<? extends Model> modelClass = (Class<? extends Model>) discoveredClass; mTableInfos.put(modelClass, new TableInfo(modelClass)); } else if (ReflectionUtils.isTypeSerializer(discoveredClass)) { TypeSerializer instance = (TypeSerializer) discoveredClass.newInstance(); mTypeSerializers.put(instance.getDeserializedType(), instance); } } catch (ClassNotFoundException e) { Log.e("Couldn‘t create class.", e); } catch (InstantiationException e) { Log.e("Couldn‘t instantiate TypeSerializer.", e); } catch (IllegalAccessException e) { Log.e("IllegalAccessException", e); }
找到继承于model的类名后,用类的相关信息构造TableInfo类,并将tableInfo的对象放入mTableInfos中储存。至此,ModelInfo类才算是基本地构造完成。
至此,我们发现,在ActiveAndroid框架下,数据及数据的关系存在了三个类之中,分别是Conguration、TableInfo和ModelInfo。下一章,我将试着总结一下这三个类分别储存了哪些信息。
Done.