首页 > 代码库 > 浅析郭婶儿子--LitePal框架(一)

浅析郭婶儿子--LitePal框架(一)

最近看了郭神的LitePal框架感觉愣牛逼,牛逼之余,也很好奇他是如何实现的,好奇心害死猫啊!跟随大神脚步,看源码.

1.在使用LitePal框架的时候,在项目的assets目录下面新建一个litepal.xml文件,其中的内容包括数据库的名称,版本,以及映射,那它如何去把这些内容映射进去的?

先贴一下litepal.xml代码:

<?xml version="1.0" encoding="utf-8"?>
<litepal>

    <!-- 数据库名 -->
    <dbname value=http://www.mamicode.com/"demo" >>
下面是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为空,所以在onLowMemorycontext赋值.大神不愧是大神,这么机智的保护措施.

今天就到这,欲知后事如何,请听下回解说!








浅析郭婶儿子--LitePal框架(一)