内容简介:首先,我们回顾一下上一篇的一些知识点,针对一个可识别的有效电子书文件来说:查看清单文件,我们可以看到FBReader的启动模式:那么图书信息界面,点击“阅读”再次打开FBReader时,其onNewIntent将被触发:
首先,我们回顾一下上一篇的一些知识点,针对一个可识别的有效电子书文件来说:
- 手机存储中的电子书文件会通过ZLFile.createFileByPath被创建成一个ZLPhysicalFile类型的文件对象
- BookCollectionShadow的大部分方法其实是由BookCollection来实现的
- BookCollection中有一个非常重要的方法getBookByFile(ZLFile),其中会校验文件的扩展名,如果是支持的电子书格式时,那么就会获取到相应的解析插件
- 随后在BookCollection中创建一个DbBook对象,DbBook在初始化时会读取book的基本信息,这里主要是通过传入的plugin,调用plugin的native方法读取到的
- 图书信息页打开FBReader进行阅读时,通过FBReaderIntents.putBookExtra(intent, book),传递的一个有效参数的Book对象
一、FBReader是如何获取book,又是如何获取并显示图书内容的
FBReader如何获取Book,以及如何更简便的打开一本电子书
查看清单文件,我们可以看到FBReader的启动模式:
android:launchMode="singleTask" 复制代码
那么图书信息界面,点击“阅读”再次打开FBReader时,其onNewIntent将被触发:
@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是什么呢?这里要回看一下打开阅读页面的时候调用的代码:
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进行了赋值,并没有其他多余的操作。
这样的话,我们就要继续往下看,在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:
private BookCollectionShadow getCollection() { return (BookCollectionShadow)myFBReaderApp.Collection; } 复制代码
老朋友BookCollectionShadow,之前的分析来看,下面就会执行runnable了,也就是openBook:
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:
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); } 复制代码
关于Book和DbBook
在这里,不知大家有没有发现一个问题,那就是我们熟悉的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方法时,会调用service的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这个方法来说:
- 客户端通过 BookCollectionShadow 实例调用此方法,意图得到Book类型的数据
- BookCollectionShadow调用到中间人 LibraryImplementation 的getBookByFile方法
- LibraryImplementation调用最终实施人 BookCollection 的getBookByFile方法,后者返回 DbBook 数据
- LibraryImplementation对返回的DbBook,通过 SerializerUtil 转换成对应 xml 数据
- 转换后的xml返回客户端BookCollectionShadow中,再次通过SerializerUtil转为 Book 对象
这里也就实现了Book的跨进程传输,由于AbstractBook及其父类,均没有实现Serializable或者Parcelable,所以是不能夸进程传输的。通过跨进程传输,把Book的一些核心信息传递给客户端,同时使客户端可以忽略DbBook中其他的关于dataBase的操作行为。
Book获取内容及显示前的准备工作
经过上面简单的分析,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); } 复制代码
逐步分析:
1.createExecutor:
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
- 根据传入的key获取resouce下对应的资源信息msg
- 实现execute(action,uiaction)
- 执行execute时创建ProgressDialog,并设置其提示信息为msg
- 随后创建子线程执行action,执行完毕后通过activity调度到主线程关闭ProgressDialog,然后执行uiaction
那么资源信息都是有哪些?又存储在什么地方呢?要想了解这两个问题的答案,我们就需要去看一下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"
2.openBookInternal(bookToOpen, bookmark, false)
通过第一步的分析,在调用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开发相当火热...”。看到这句话,眼中满是泪水啊!青春!怎么这么快就过去了!......
以上所述就是小编给大家介绍的《开源电子书项目FBReader初探(四)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 开源电子书项目FBReader初探(一)
- 开源电子书项目FBReader初探(二)
- 开源电子书项目FBReader初探(三)
- 开源电子书项目FBReader初探(五)
- 开源电子书项目FBReader初探(六)
- 初探 Google 开源的 Python 命令行库
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。