最近看了郭神的LitePal框架感觉愣牛逼,牛逼之余,也很好奇他是如何实现的,好奇心害死猫啊!跟随大神脚步,看源码.
1.在使用LitePal框架的时候,在项目的assets目录下面新建一个litepal.xml文件,其中的内容包括数据库的名称,版本,以及映射,那它如何去把这些内容映射进去的?
先贴一下litepal.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<!-- 数据库名 -->
<dbname value="demo" >
</dbname>
<!-- 数据库版本 -->
<version value="1" >
</version>
<!-- 映射模型 -->
<list>
<mapping class="com.sdufe.thea.guo.model.News" >
</mapping>
</list>
</litepal>
下面是litepal.xml对应函数中的属性代码:
/*
* Copyright (C) Tony Green, Litepal Framework Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.litepal.parser;
import java.util.ArrayList;
import java.util.List;
import org.litepal.exceptions.InvalidAttributesException;
import org.litepal.util.Const;
import org.litepal.util.SharedUtil;
import android.text.TextUtils;
/**
* The object model for the litepal.xml file. Once database connection happens,
* LitePal will try to analysis the litepal.xml, and read all the attribute into
* the LitePalAttr model for further usage.
*
* @author Tony Green
* @since 1.0
*/
public final class LitePalAttr {
/**
* Static litePalAttr object.
*/
private static LitePalAttr litePalAttr;
/**
* The version of database.
*/
private int version;
/**
* The name of database.
*/
private String dbName;
/**
* The case of table names and column names and SQL.
*/
private String cases;
/**
* All the model classes that want to map in the database. Each class should
* be given the full name including package name.
*/
private List<String> classNames;
/**
* Do not allow new a LitePalAttr object. Makes it a singleton class.
*/
private LitePalAttr() {
}
/**
* Provide a way to get the object of LitePalAttr class.
*
* @return the singleton object of LitePalAttr
*/
public static LitePalAttr getInstance() {
if (litePalAttr == null) {
synchronized (LitePalAttr.class) {
if (litePalAttr == null) {
litePalAttr = new LitePalAttr();
}
}
}
return litePalAttr;
}
public int getVersion() {
return version;
}
void setVersion(int version) {
this.version = version;
}
public String getDbName() {
return dbName;
}
void setDbName(String dbName) {
this.dbName = dbName;
}
/**
* Get the class name list. Always add table_schema as a value.
*
* @return The class name list.
*/
public List<String> getClassNames() {
if (classNames == null) {
classNames = new ArrayList<String>();
classNames.add("org.litepal.model.Table_Schema");
} else if (classNames.isEmpty()) {
classNames.add("org.litepal.model.Table_Schema");
}
return classNames;
}
/**
* Add a class name into the current mapping model list.
*
* @param className
* Full package class name.
*/
void addClassName(String className) {
getClassNames().add(className);
}
public String getCases() {
return cases;
}
void setCases(String cases) {
this.cases = cases;
}
/**
* Before application build the connection with database, check the fields
* in LitePalAttr. If all of the fields are passed, the connection will be
* continued.If anyone of them doesn't pass, an exception will be thrown.
*
* @return If all of the fields are passed, return true. If dbname is
* undefined, or version is less than 1, or version is earlier than
* current version, throw InvalidAttributesException
*
* @throws InvalidAttributesException
*/
public boolean checkSelfValid() {
if (TextUtils.isEmpty(dbName)) {
throw new InvalidAttributesException(
InvalidAttributesException.DBNAME_IS_EMPTY_OR_NOT_DEFINED);
}
if (!dbName.endsWith(Const.LitePal.DB_NAME_SUFFIX)) {
dbName = dbName + Const.LitePal.DB_NAME_SUFFIX;
}
if (version < 1) {
throw new InvalidAttributesException(
InvalidAttributesException.VERSION_OF_DATABASE_LESS_THAN_ONE);
}
if (version < SharedUtil.getLastVersion()) {
throw new InvalidAttributesException(
InvalidAttributesException.VERSION_IS_EARLIER_THAN_CURRENT);
}
if (TextUtils.isEmpty(cases)) {
cases = Const.LitePal.CASES_LOWER;
} else {
if (!cases.equals(Const.LitePal.CASES_UPPER)
&& !cases.equals(Const.LitePal.CASES_LOWER)
&& !cases.equals(Const.LitePal.CASES_KEEP)) {
throw new InvalidAttributesException(cases
+ InvalidAttributesException.CASES_VALUE_IS_INVALID);
}
}
return true;
}
}
这就是郭哥代码中对应xml文件中的属性了,那如何映射进去的呢?它自己应该不能平白无故的就对应上了,继续看源码
解析xml文件中出现了这么一个函数:
/**
* Analyze litepal.xml, and store the analyzed result in LitePalParser. Use
* DomParse to parse the configuration file as default. SAXParser and
* XmlPullParser is also optional, but not visible to developers.
*/
public static void parseLitePalConfiguration() {
if (parser == null) {
parser = new LitePalParser();
}
parser.useSAXParser();
}
从函数名上就猜到使用了SAX解析xml,也不能胡乱猜,继续看郭哥的源码, 赶紧去useSAXParser()看看到底是如何实现的
/**
* Use SAXParser to parse the litepal.xml file. It will get the parsed
* result from LitePalContentHandler and stored in the instance of
* LitePalAttr.
*
* Note while analyzing litepal.xml file, ParseConfigurationFileException
* could be thrown. Be careful of writing litepal.xml file, or developer's
* application may be crash.
*/
void useSAXParser() {
LitePalContentHandler handler = null;
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
handler = new LitePalContentHandler();
xmlReader.setContentHandler(handler);
xmlReader.parse(new InputSource(getConfigInputStream()));
return;
} catch (NotFoundException e) {
throw new ParseConfigurationFileException(
ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE);
} catch (SAXException e) {
throw new ParseConfigurationFileException(
ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT);
} catch (ParserConfigurationException e) {
throw new ParseConfigurationFileException(
ParseConfigurationFileException.PARSE_CONFIG_FAILED);
} catch (IOException e) {
throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION);
}
}
是的,你没有猜错,上面的就是SAX解析xml的格式了,使用SAX解析xml差不多就是这么个格式,不同的就在那个handler了,当然郭神的代码相当规范,向大神学习,要想知道他是怎么解析litepal.xml还是继续看handler的实现吧!这里只贴主要代码,不能弄得很长,长了就不太好了哈!
/**
* Start analysis the litepal.xml file. Set all the parsed value into the
* LitePalAttr model.
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (LitePalParser.NODE_DB_NAME.equalsIgnoreCase(localName)) {
for (int i = 0; i < attributes.getLength(); i++) {
if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
litePalAttr.setDbName(attributes.getValue(i).trim());
}
}
} else if (LitePalParser.NODE_VERSION.equalsIgnoreCase(localName)) {
for (int i = 0; i < attributes.getLength(); i++) {
if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
litePalAttr.setVersion(Integer.parseInt(attributes.getValue(i).trim()));
}
}
} else if (LitePalParser.NODE_MAPPING.equalsIgnoreCase(localName)) {
for (int i = 0; i < attributes.getLength(); i++) {
if (LitePalParser.ATTR_CLASS.equalsIgnoreCase(attributes.getLocalName(i))) {
litePalAttr.addClassName(attributes.getValue(i).trim());
}
}
} else if (LitePalParser.NODE_CASES.equalsIgnoreCase(localName)) {
for (int i = 0; i < attributes.getLength(); i++) {
if (LitePalParser.ATTR_VALUE.equalsIgnoreCase(attributes.getLocalName(i))) {
litePalAttr.setCases(attributes.getValue(i).trim());
}
}
}
}
上面的就是handler的解析内容了,根据开始元素解释开始元素,个人猜测,既然是用SAX解析xml,就可以有多个Lite标签,所以我觉得可以使用LitePal框架可以建多个数据库,不过LitePal是继承自SQLite数据库,一般一个app应该不会有很多数据库吧,本来就很小!当然源代码中还有用Pull解析的xml,这里也贴一下代码
/**
* Use XmlPullParser to parse the litepal.xml file. It will store the result
* in the instance of LitePalAttr.
*
* Note while analyzing litepal.xml file, ParseConfigurationFileException
* could be thrown. Be careful of writing litepal.xml file, or developer's
* application may be crash.
*/
void usePullParse() {
try {
LitePalAttr litePalAttr = LitePalAttr.getInstance();
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(getConfigInputStream(), "UTF-8");
int eventType = xmlPullParser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
String nodeName = xmlPullParser.getName();
switch (eventType) {
case XmlPullParser.START_TAG: {
if (NODE_DB_NAME.equals(nodeName)) {
String dbName = xmlPullParser.getAttributeValue("", ATTR_VALUE);
litePalAttr.setDbName(dbName);
} else if (NODE_VERSION.equals(nodeName)) {
String version = xmlPullParser.getAttributeValue("", ATTR_VALUE);
litePalAttr.setVersion(Integer.parseInt(version));
} else if (NODE_MAPPING.equals(nodeName)) {
String className = xmlPullParser.getAttributeValue("", ATTR_CLASS);
litePalAttr.addClassName(className);
} else if (NODE_CASES.equals(nodeName)) {
String cases = xmlPullParser.getAttributeValue("", ATTR_VALUE);
litePalAttr.setCases(cases);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
} catch (XmlPullParserException e) {
throw new ParseConfigurationFileException(
ParseConfigurationFileException.FILE_FORMAT_IS_NOT_CORRECT);
} catch (IOException e) {
throw new ParseConfigurationFileException(ParseConfigurationFileException.IO_EXCEPTION);
}
}
当然知道了解析方法,知道了属性对应的函数,那他是如何找到的litepal.xml的呢?不急,我们继续看
/**
* Iterates all files in the root of assets folder. If find litepal.xml,
* open this file and return the input stream. Or throw
* ParseConfigurationFileException.
*
* @return The input stream of litepal.xml.
* @throws IOException
*/
private InputStream getConfigInputStream() throws IOException {
AssetManager assetManager = LitePalApplication.getContext().getAssets();
String[] fileNames = assetManager.list("");
if (fileNames != null && fileNames.length > 0) {
for (String fileName : fileNames) {
if (Const.LitePal.CONFIGURATION_FILE_NAME.equalsIgnoreCase(fileName)) {
return assetManager.open(fileName, AssetManager.ACCESS_BUFFER);
}
}
}
throw new ParseConfigurationFileException(
ParseConfigurationFileException.CAN_NOT_FIND_LITEPAL_FILE);
}
以上就是他找到本地litepal.xml的方法啦,通过getAssets()读到本地的asset里面的文件,那文件名跟Const.LitePal.CONFIGURATION_FILE_NAME对比,相同的话就读到本地文件了,当然我开始也有个疑问,我是否可以随便起个名字呢?这里当然不可以,因为asset可以有很多文件,他不知道要用哪一个,这里郭哥直接写死了,文件名只能叫litepal.xml
public static final String CONFIGURATION_FILE_NAME = "litepal.xml";
看到这,你应该大概连接到郭哥是怎么操作litepal.xml,理一理,首先你使用LitePal框架引进包之后,需要建一个litepal.xml文件,郭哥通过getAsset()读到本地asset文件夹的文件名,通过跟litepal.xml文件名对比,获得里面的内容,在使用SAX解析本地的文件,获得文件中的内容,赋值到LitePalAttr里.
2.在LitePal框架的使用中,需要配置AndroidManifest.xml,在application中加入android:name="org.litepal.LitePalApplication" 那我们不妨从LitePalApplication开始看
public class LitePalApplication extends Application {
/**
* Global application context.
*/
private static Context mContext;
/**
* Construct of LitePalApplication. Initialize application context.
*/
public LitePalApplication() {
mContext = this;
}
/**
* Get the global application context.
*
* @return Application context.
* @throws GlobalException
*/
public static Context getContext() {
if (mContext == null) {
throw new GlobalException(GlobalException.APPLICATION_CONTEXT_IS_NULL);
}
return mContext;
}
@Override
public void onLowMemory() {
super.onLowMemory();
mContext = getApplicationContext();
}
}
郭哥这里为了方便用户多次调用context,将context配置到AndroidManifest.xml,并在LitePalApplication中给context赋值,这里需要注意一下,郭哥为了防止context为空,所以在onLowMemory给context赋值.大神不愧是大神,这么机智的保护措施.
今天就到这,欲知后事如何,请听下回解说!