首先,咱们回顾一下上一篇的一些知识点,针对一个可识别的有效电子书文件来讲:android
查看清单文件,咱们能够看到FBReader的启动模式:缓存
android:launchMode="singleTask"
复制代码
那么图书信息界面,点击“阅读”再次打开FBReader时,其onNewIntent将被触发:app
@Override
protected void onNewIntent(final Intent intent) {
final String action = intent.getAction();
final Uri data = intent.getData();
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
super.onNewIntent(intent);
} else if (Intent.ACTION_VIEW.equals(action)
&& data != null && "fbreader-action".equals(data.getScheme())) {
//忽略部分代码...
} else if (Intent.ACTION_VIEW.equals(action) || FBReaderIntents.Action.VIEW.equals(action)) {
//为myOpenBookIntent赋值
myOpenBookIntent = intent;
//忽略部分代码...
} else if (FBReaderIntents.Action.PLUGIN.equals(action)) {
//忽略部分代码...
} else if (Intent.ACTION_SEARCH.equals(action)) {
//忽略部分代码...
} else if (FBReaderIntents.Action.CLOSE.equals(intent.getAction())) {
//忽略部分代码...
} else if (FBReaderIntents.Action.PLUGIN_CRASH.equals(intent.getAction())) {
//忽略部分代码...
} else {
super.onNewIntent(intent);
}
}
复制代码
发现校验了action,那么咱们的以前的Intent其action是什么呢?这里要回看一下打开阅读页面的时候调用的代码:ide
FBReader.openBookActivity(BookInfoActivity.this, myBook, null);
public static void openBookActivity(Context context, Book book, Bookmark bookmark) {
final Intent intent = defaultIntent(context);
FBReaderIntents.putBookExtra(intent, book);
FBReaderIntents.putBookmarkExtra(intent, bookmark);
context.startActivity(intent);
}
public static Intent defaultIntent(Context context) {
return new Intent(context, FBReader.class)
.setAction(FBReaderIntents.Action.VIEW)//设置action
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
复制代码
默认的Intent其action被设置为了FBReaderIntents.Action.VIEW,那么在onNewIntent方法,通过断点能够知道,针对当前咱们从图书信息跳转过来阅读的状况,这里只是对myOpenBookIntent进行了赋值,并无其余多余的操做。post
这样的话,咱们就要继续往下看,在FBReader的onResume中:测试
@Override
protected void onResume() {
super.onResume();
//忽略部分代码...
if (myCancelIntent != null) {
//忽略部分代码...
} else if (myOpenBookIntent != null) {
final Intent intent = myOpenBookIntent;
myOpenBookIntent = null;
getCollection().bindToService(this, new Runnable() {
public void run() {
openBook(intent, null, true);
}
});
} else if (myFBReaderApp.getCurrentServerBook(null) != null) {
//忽略部分代码...
} else if (myFBReaderApp.Model == null && myFBReaderApp.ExternalBook != null) {
//忽略部分代码...
} else {
//忽略部分代码...
}
}
复制代码
当myOpenBookIntent != null时,会执行getCollection().bindToService,这个好像咱们在那见过啊,看看getCollection:ui
private BookCollectionShadow getCollection() {
return (BookCollectionShadow)myFBReaderApp.Collection;
}
复制代码
老朋友BookCollectionShadow,以前的分析来看,下面就会执行runnable了,也就是openBook:this
private synchronized void openBook(Intent intent, final Runnable action, boolean force) {
if (!force && myBook != null) {
return;
}
//取出book
myBook = FBReaderIntents.getBookExtra(intent, myFBReaderApp.Collection);
final Bookmark bookmark = FBReaderIntents.getBookmarkExtra(intent);
if (myBook == null) {
final Uri data = intent.getData();
if (data != null) {
myBook = createBookForFile(ZLFile.createFileByPath(data.getPath()));
}
}
//忽略部分代码...
Config.Instance().runOnConnect(new Runnable() {
public void run() {
myFBReaderApp.openBook(myBook, bookmark, action, myNotifier);
AndroidFontUtil.clearFontCache();
}
});
}
复制代码
在openBook方法中,发现取出了咱们以前传递过来的book。并且,仔细阅读下面的判断,能够分析出,若是Intent中没有传递book,可是有传递的Uri,那么就回去调用方法createBookForFile:spa
private Book createBookForFile(ZLFile file) {
if (file == null) {
return null;
}
Book book = myFBReaderApp.Collection.getBookByFile(file.getPath());
if (book != null) {
return book;
}
if (file.isArchive()) {
for (ZLFile child : file.children()) {
book = myFBReaderApp.Collection.getBookByFile(child.getPath());
if (book != null) {
return book;
}
}
}
return null;
}
复制代码
熟悉的方法,去建立了一个Book。那么这样的话,咱们就还能够经过这种方式去打开一本电子书:插件
//path电子书绝对路径
public static void openBookActivity(Context context, String path) {
final Intent intent = FBReader.defaultIntent(context);
intent.setData(Uri.parse(path));
context.startActivity(intent);
}
复制代码
在这里,不知你们有没有发现一个问题,那就是咱们熟悉的BookCollectionShadow和BookCollection,咱们知道他们都是继承于AbstractBookCollection,可是BookCollectionShadow是使用的Book,而BookCollection是使用的DbBook:
public class BookCollectionShadow extends AbstractBookCollection<Book> implements ServiceConnection
public class BookCollection extends AbstractBookCollection<DbBook>
复制代码
再来看一下Book和DbBook这两个类的定义:
public final class DbBook extends AbstractBook
public final class Book extends AbstractBook
复制代码
很明显这两个类,是均继承于AbstractBook的不一样子类,可是咱们以前有分析过BookCollectionShadow中有关于IBookCollection的实现,实际最终是BookCollection来操做的,可是他们是基于两个不一样数据类型的,好比咱们查看getBookByFile:
BookCollectionShadow中:
public synchronized Book getBookByFile(String path) {
if (myInterface == null) {
return null;
}
try {
return SerializerUtil.deserializeBook(myInterface.getBookByFile(path), this);
} catch (RemoteException e) {
return null;
}
}
BookCollection中:
public DbBook getBookByFile(String path) {
return getBookByFile(ZLFile.createFileByPath(path));
}
复制代码
调用者BookCollectionShadow,调用getBookByFile指望获得Book类型的数据,而最终实现者调用getBookByFile却返回了DbBook类型的数据,这是怎么一回事?
在BookCollectionShadow中,咱们能够发现,最终return的是SerializerUtil.deserializeBook方法返回的数据。那这个方法又是作什么的呢?点进去看一下:
SerializerUtil.class
private static final AbstractSerializer defaultSerializer = new XMLSerializer();
public static <B extends AbstractBook> B deserializeBook(String xml, AbstractSerializer.BookCreator<B> creator) {
return xml != null ? defaultSerializer.deserializeBook(xml, creator) : null;
}
XMLSerializer.class
@Override
public <B extends AbstractBook> B deserializeBook(String xml, BookCreator<B> creator) {
try {
final BookDeserializer<B> deserializer = new BookDeserializer<B>(creator);
Xml.parse(xml, deserializer);
return deserializer.getBook();
} catch (SAXException e) {
System.err.println(xml);
e.printStackTrace();
return null;
}
}
复制代码
不难看出,在调用BookCollectionShadow的getBookByFile方法时,会调用LibraryService的getBookByFile,然后者会返回一段xml数据,BookCollectionShadow会根据这段xml数据,将其解析成对应的Book对象。咱们知道,虽然BookCollection是最终实施人,可是在他和BookCollectionShadow之间,还有一个LibraryService中的LibraryImplementation做为中间人,那么咱们就看看中间人的这个方法是作了些什么:
public String getBookByFile(String path) {
//这里myCollection是BookCollection实例,返回结果为DbBook
return SerializerUtil.serialize(myCollection.getBookByFile(path));
}
复制代码
一样进入了SerializerUtil中:
public static String serialize(AbstractBook book) {
return book != null ? defaultSerializer.serialize(book) : null;
}
XMLSerializer.class
@Override
public String serialize(AbstractBook book) {
final StringBuilder buffer = builder();
serialize(buffer, book);
return buffer.toString();
}
复制代码
细节咱们就再也不深刻去看了,这里流程已经比较清晰,就拿getBookByFile这个方法来讲:
这里也就实现了Book的跨进程传输,因为AbstractBook及其父类,均没有实现Serializable或者Parcelable,因此是不能夸进程传输的。经过跨进程传输,把Book的一些核心信息传递给客户端,同时使客户端能够忽略DbBook中其余的关于dataBase的操做行为。
通过上面简单的分析,FBReader已经拿到了book,那么接下来,FBReader又分别作了些什么呢?
这就要从openBook方法中的,最后一段代码来开始接下来的分析了:
private synchronized void openBook(Intent intent, final Runnable action, boolean force) {
//忽略部分代码...
Config.Instance().runOnConnect(new Runnable() {
public void run() {
myFBReaderApp.openBook(myBook, bookmark, action, myNotifier);
AndroidFontUtil.clearFontCache();
}
});
}
复制代码
runOnConnect这个方法咱们以前已经分析过了,接下来会执行runnable。这里,咱们发现了一个新的角色登场了,就是FBReaderApp。
先看看这个FBReaderApp在FBReader的中,是何时初始化的吧:
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//忽略部分代码...
myFBReaderApp = (FBReaderApp)FBReaderApp.Instance();
if (myFBReaderApp == null) {
myFBReaderApp = new FBReaderApp(Paths.systemInfo(this), new BookCollectionShadow());
}
myFBReaderApp.setWindow(this);
//忽略部分代码...
}
复制代码
首次进入FBReader时,FBReaderApp.Instance()为null,就会经过new建立,以后会被重用。看下它的构造方法:
public FBReaderApp(SystemInfo systemInfo, final IBookCollection<Book> collection)
复制代码
BookCollectionShadow咱们已经很熟了,这个SystemInfo是个啥呢?进去看看:
public interface SystemInfo {
String tempDirectory();
String networkCacheDirectory();
}
复制代码
在看看onCreate建立FBReaderApp时传入的Paths.systemInfo:
public static SystemInfo systemInfo(Context context) {
final Context appContext = context.getApplicationContext();
return new SystemInfo() {
public String tempDirectory() {
final String value = ourTempDirectoryOption.getValue();
if (!"".equals(value)) {
return value;
}
return internalTempDirectoryValue(appContext);
}
public String networkCacheDirectory() {
return tempDirectory() + "/cache";
}
};
}
复制代码
看来是获取文件存储和缓存路径的。
接下来,咱们就进入FBReaderApp,去看一下它的openBook方法:
public void openBook(Book book, final Bookmark bookmark, Runnable postAction, Notifier notifier) {
//忽略部分代码..
final SynchronousExecutor executor = createExecutor("loadingBook");
executor.execute(new Runnable() {
public void run() {
openBookInternal(bookToOpen, bookmark, false);
}
}, postAction);
}
复制代码
逐步分析:
protected SynchronousExecutor createExecutor(String key) {
if (myWindow != null) {
return myWindow.createExecutor(key);
} else {
return myDummyExecutor;
}
}
复制代码
FBReader在onCreate生成FBReaderApp以后,就调用了FBReaderApp.setWindow(this),那么当前的myWindow就是FBReader,其createExecutor方法:
@Override
public FBReaderApp.SynchronousExecutor createExecutor(String key) {
return UIUtil.createExecutor(this, key);
}
复制代码
接着进入了UIUtil:
public static ZLApplication.SynchronousExecutor createExecutor(final Activity activity, final String key) {
return new ZLApplication.SynchronousExecutor() {
private final ZLResource myResource =
ZLResource.resource("dialog").getResource("waitMessage");
private final String myMessage = myResource.getResource(key).getValue();
private volatile ProgressDialog myProgress;
public void execute(final Runnable action, final Runnable uiPostAction) {
activity.runOnUiThread(new Runnable() {
public void run() {
myProgress = ProgressDialog.show(activity, null, myMessage, true, false);
final Thread runner = new Thread() {
public void run() {
action.run();
activity.runOnUiThread(new Runnable() {
public void run() {
try {
myProgress.dismiss();
myProgress = null;
} catch (Exception e) {
e.printStackTrace();
}
if (uiPostAction != null) {
uiPostAction.run();
}
}
});
}
};
runner.setPriority(Thread.MAX_PRIORITY);
runner.start();
}
});
}
//忽略部分代码...
};
}
复制代码
简单分析一下,这段代码作了什么:
那么资源信息都是有哪些?又存储在什么地方呢?要想了解这两个问题的答案,咱们就须要去看一下ZLResource:
static void buildTree() {
synchronized (ourLock) {
if (ourRoot == null) {
ourRoot = new ZLTreeResource("", null);
ourLanguage = "en";
ourCountry = "UK";
loadData();
}
}
}
private static void loadData() {
ResourceTreeReader reader = new ResourceTreeReader();
loadData(reader, ourLanguage + ".xml");
loadData(reader, ourLanguage + "_" + ourCountry + ".xml");
}
private static void loadData(ResourceTreeReader reader, String fileName) {
reader.readDocument(ourRoot, ZLResourceFile.createResourceFile("resources/zlibrary/" + fileName));
reader.readDocument(ourRoot, ZLResourceFile.createResourceFile("resources/application/" + fileName));
reader.readDocument(ourRoot, ZLResourceFile.createResourceFile("resources/lang.xml"));
reader.readDocument(ourRoot, ZLResourceFile.createResourceFile("resources/application/neutral.xml"));
}
复制代码
ZLResource在加载资源前,会首先buildTree,在首次buildTree的时会调用loadData方法,最终加载了资源目录下当前系统语言的资源文件,分别是zlibrary下的相应语言资源文件,application下的相应语言资源文件,lang资源文件,application/neutral.xml资源文件。
中文系统资源文件:
上面分析处调用的UIUtil,分别加载了"dialog"-"waitMessage"-"loadingBook"
经过第一步的分析,在调用execute时,首先会执行第一个runnable,也就是其中的openBookInternal方法:
private synchronized void openBookInternal(final Book book, Bookmark bookmark, boolean force) {
//忽略部分代码...
final PluginCollection pluginCollection = PluginCollection.Instance(SystemInfo);
final FormatPlugin plugin;
try {
plugin = BookUtil.getPlugin(pluginCollection, book);
} catch (BookReadingException e) {
processException(e);
return;
}
//忽略部分代码...
try {
Model = BookModel.createModel(book, plugin);
Collection.saveBook(book);
ZLTextHyphenator.Instance().load(book.getLanguage());
BookTextView.setModel(Model.getTextModel());
setBookmarkHighlightings(BookTextView, null);
gotoStoredPosition();
if (bookmark == null) {
setView(BookTextView);
} else {
gotoBookmark(bookmark, false);
}
Collection.addToRecentlyOpened(book);
final StringBuilder title = new StringBuilder(book.getTitle());
if (!book.authors().isEmpty()) {
boolean first = true;
for (Author a : book.authors()) {
title.append(first ? " (" : ", ");
title.append(a.DisplayName);
first = false;
}
title.append(")");
}
setTitle(title.toString());
} catch (BookReadingException e) {
processException(e);
}
getViewWidget().reset();
getViewWidget().repaint();
//忽略部分代码...
}
复制代码
关于PluginCollection.Instance(SystemInfo):
public static PluginCollection Instance(SystemInfo systemInfo) {
if (ourInstance == null) {
createInstance(systemInfo);
}
return ourInstance;
}
private static synchronized void createInstance(SystemInfo systemInfo) {
if (ourInstance == null) {
ourInstance = new PluginCollection(systemInfo);
// This code cannot be moved to constructor
// because nativePlugins() is a native method
for (NativeFormatPlugin p : ourInstance.nativePlugins(systemInfo)) {
ourInstance.myBuiltinPlugins.add(p);
System.err.println("native plugin: " + p);
}
}
}
private native NativeFormatPlugin[] nativePlugins(SystemInfo systemInfo);
复制代码
PluginCollection初始化以后,会调用native的nativePlugins去获取一个图书解析插件集合,返回的结果就是可解析的各电子书类型对应的解析插件。这里我打开的电子书格式为epub,获取到的插件是OEBNativePlugin:
紧接着咱们来看这个方法,BookUtil.getPlugin(pluginCollection, book),在上一篇已经分析过,这里最终会经过对Book文件类型的区分,获取该电子书格式对应的解析插件。
随后,一个超级核心的方法出现了!那就是解析电子书内容的方法:
BookModel.createModel(book, plugin);
BookModel.class
public static BookModel createModel(Book book, FormatPlugin plugin) throws BookReadingException {
if (plugin instanceof BuiltinFormatPlugin) {
final BookModel model = new BookModel(book);
((BuiltinFormatPlugin)plugin).readModel(model);
return model;
}
throw new BookReadingException(
"unknownPluginType", null, new String[] { String.valueOf(plugin) }
);
}
对于我测试使用的书来讲,最终解析图书内容会调用NativeFormatPlugin的readModel
synchronized public void readModel(BookModel model) throws BookReadingException {
final int code;
final String tempDirectory = SystemInfo.tempDirectory();
synchronized (ourNativeLock) {
//这里返回解析结果code,为0时则正确解析
code = readModelNative(model, tempDirectory);
}
switch (code) {
case 0:
return;
case 3:
throw new CachedCharStorageException(
"Cannot write file from native code to " + tempDirectory
);
default:
throw new BookReadingException(
"nativeCodeFailure",
BookUtil.fileByBook(model.Book),
new String[] { String.valueOf(code), model.Book.getPath() }
);
}
}
private native int readModelNative(BookModel model, String cacheDir);
复制代码
解析前的BookMode内容:
解析后的BookMode内容:
最后,咱们再看一下最后两句:
getViewWidget().reset();
getViewWidget().repaint();
public final ZLViewWidget getViewWidget() {
return myWindow != null ? myWindow.getViewWidget() : null;
}
咱们知道myWindow为FBReader,那么就去看一下FBReader中的getViewWidget:
@Override
public ZLViewWidget getViewWidget() {
return myMainView;
}
在FBReader的onCreate中:
myMainView = (ZLAndroidWidget)findViewById(R.id.main_view);
复制代码
进入ZLAndroidWidget看一下对应的方法:
@Override
public void reset() {
myBitmapManager.reset();
}
@Override
public void repaint() {
postInvalidate();
}
BitmapManagerImpl.class
void reset() {
for (int i = 0; i < SIZE; ++i) {
myIndexes[i] = null;
}
}
复制代码
最终,页面绘制出了电子书的内容。
固然,因为本人接触此项目时间有限,并且书写技术文章的经验实在欠缺,过程当中不免会有存在错误或描述不清或语言累赘等等一些问题,还望你们可以谅解,同时也但愿你们继续给予指正。最后,感谢你们对个人支持,让我有了强大的动力坚持下去。
PS:《Android开发艺术探索》,前言中的第一行“从目前形势来看,Android开发至关火热...”。看到这句话,眼中尽是泪水啊!青春!怎么这么快就过去了!......