線上生成android應用程式初探(以線上生成EPUB電子書為例)
最近發現國內有的公司提供線上編輯並自動編譯生成android應用程式的產品。覺得有點意思,正好有幾個朋友老說需要epub格式的電子書,看了看android的SDK,自己業餘時間做了個線上生成apk電子書閱讀器和epub電子書的程式。近一週沒有看電視了,有必要把整個實現過程分享一下,讓有需要的朋友有更多的時間看電視。
(注:目前只支援上傳docx和epub格式的檔案, 支援epub是因為朋友需要為已經有的電子書加上封面圖片,以及其他資訊,如果你需要,可以試試。
目前在搜狗瀏覽器上效果不佳,懶得改了,這瀏覽器太多還真是個挺操蛋的問題。)
圖示效果:
之前寫了篇部落格,,要自動生成apk,無非在編譯打包之前要準備好原始碼,並且原始碼中的部分引數的值是使用者在網頁上提交的。所以整個過程的流程可以簡要概括為以下幾步:
1. 準備要生成的應用程式原始碼
2. 挑選出原始碼中需要定製的原始檔(.java)
3. 根據使用者在網頁上輸入的引數,替換原始檔中的關鍵字
4. 編譯工程,生成未簽名的apk
6. 為apk簽名
7. zipalign優化已簽名的apk
8. 生成下載連結,並根據連結地址生成二維碼供手機使用者下載。
下面詳細描述一下各個步驟的實現方式:
1. 準備要生成的應用程式原始碼(我挑選了FBReader 開源電子書閱讀器,整個閱讀器的效率還是不錯的,佔用資源小,不像有的電子書,實現了翻頁的效果,但是翻頁效果很卡(我用的是galaxy s3,,機器配置應該還算是不錯))
下載FBReader的原始碼後,假設我們需要對FBReader進行如下的小定製:
1. 將FBReader的名稱替換為自定義的名稱,包括進入程式的顯示名稱和生成的apk名稱
2. 第一次進入APK開啟的是自定義的電子書
APK的工程名稱是在android工程中,根目錄下AndroidManifest.xml檔案中定義的,同樣,FBReader的程式的名稱(在手機中安裝後顯示的名字),也是在AndroidManifest.xml檔案中定義。
所以,第一步要解決如何替換原始檔中的名稱了,實現方式很簡單,直接貼程式碼:
step1:新建AndroidManifest.xml.template檔案,將檔案中要修改的欄位用特殊名稱標註,比如我將android:label的值替換為ANDROID_APP_NAME
<activity android:name="org.geometerplus.android.fbreader.FBReader" android:launchMode="singleTask" android:icon="@drawable/fbreader" android:label="ANDROID_APP_NAME" android:theme="@style/FBReader.Activity" android:configChanges="orientation|keyboardHidden|screenSize">
step2:將要替換的值放在map裡
final HashMap<String, String> keywords = new HashMap<String, String>();
keywords.put("ANDROID_APP_NAME",config.getProjectName());
step3:呼叫installFullPathTemplate方法,覆蓋原始檔,第一個引數就是你的template檔案地址,第二個引數是要生成的原始檔,第三個引數就是step2中的map物件private void installFullPathTemplate(String sourceFilePath, File destFile,
Map<String, String> placeholderMap) {
try {
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(destFile),"UTF-8"));
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(sourceFilePath),"UTF-8"));
String line;
while ((line = in.readLine()) != null) {
if (placeholderMap != null) {
for (Map.Entry<String, String> entry : placeholderMap.entrySet()) {
line = line.replace(entry.getKey(), entry.getValue());
}
}
out.write(line);
out.newLine();
}
out.close();
in.close();
} catch (Exception e) {
}
}
要讓FBReader初次進入的時候開啟的是自定義的書籍,修改一個方法即可,在FBReader的原始碼中查詢BookUtil.java中的getHelpFile()方法,FBReader第一次載入的時候,會開啟幫助文件,在哪裡呼叫的呢,是在FBReaderApp.java中的openBookInternal()方法中,
//book = Collection.getBookByFile(BookUtil.getHelpFile());
book = Collection.getBookByFile(BookUtil.getUserUploadFile());
我們新增自己的方法getUserUploadFile(),在第一次閱讀的時候,將我們定義在assets中的epub書籍拷貝到sdcard的書籍目錄下。為什麼要有這麼多次一舉的拷貝呢?原因有兩個:
1. 因為在assets裡的不支援中文名的檔案,如果assets資料夾中有中文,在編譯階段,會報錯,不支援中文。
2. 放在assets裡的epub在FBReader中不能顯示封面資訊,只能顯示書籍內容,如果你不介意這個,可以不用拷貝到sdcard中,注意不要使用中文名哦。
public static ZLFile getUserUploadFile(){
ZLFile srcFile = ZLResourceFile.createFileByPath("data/book/mybook.epub");
boolean isSrcExist = srcFile.exists();
if(isSrcExist){
String bookDir = checkSdCardBookFolder();
if(bookDir.trim().length() > 1){
String destPath = bookDir+File.separator+"mybook.epub";
try {
copyEpubToSdCard(srcFile.getInputStream(), destPath);
FileUtils.del("data/book/");
} catch (IOException e) {
return srcFile;
}
return ZLResourceFile.createFileByPath(destPath);
}else{
return srcFile;
}
}else{
return getHelpFile();
}
}
private static String checkSdCardBookFolder(){
String sdDir = "";
boolean sdCardExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
if(sdCardExist){
sdDir = Environment.getExternalStorageDirectory().getPath();//獲取跟目錄
if(sdDir.endsWith(File.separator)){
sdDir += "Books";
}else{
sdDir += File.separator + "Books";
}
File bookDir = new File(sdDir);
if(!bookDir.exists()){
bookDir.mkdir();
}
}
return sdDir;
}
原始檔準備好了,下一步就可以編譯原始碼,生成apk並簽名和優化了,之前的文章中已經介紹了關鍵工具的使用,那麼這次就需要自動完成,所以用ant就可以完成這個工作了,編輯FBReader中的build.xml檔案,替換機制和上面說的相同,主要是介紹一下要在各個已有工程中的build.xml新增哪些打包相關的target.
step1. 在build.properties檔案中準備好使用引數,主要是簽名密碼,sdk的路徑,jdk的路勁
android.tools=${sdk.folder}tools
android_version=4.2.2
apk.sdk.home=ANDROID_SDK_HOME
apk.tools=${apk.sdk.home}tools
jdk.home=JDK_HOME
password=123456
step2:在build.xml中新增打包相關的target。
<?xml version="1.0" encoding="UTF-8"?>
<project name="ANDROID_APP_NAME" default="help">
<loadproperties srcFile="local.properties" />
<property file="ant.properties" />
<property file="build.properties" />
<loadproperties srcFile="project.properties" />
<loadproperties srcFile="local.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'"
unless="sdk.dir"
/>
<property name="outdir" value="bin" />
<property name="out-unsigned-package" value="${outdir}/${ant.project.name}-release-unsigned.apk" />
<property name="out-signed-package" value="${outdir}/${ant.project.name}-release-signed.apk" />
<condition property="zipalign-package-ospath" value="${basedir}/${outdir}/ANDROID_APP_NAME_for_android_${android_version}_${temp.dir}.apk" else="${basedir}/${output.dir}">
<os family="windows" />
</condition>
<condition property="keytool" value="${jdk.home}/bin/keytool.exe" else="${jdk.home}/bin/keytool.exe">
<os family="windows" />
</condition>
<condition property="zipalign" value="${apk.tools}/zipalign.exe" else="${apk.tools}/zipalign">
<os family="windows" />
</condition>
<condition property="jarsigner" value="${jdk.home}/bin/jarsigner.exe" else="${jdk.home}/bin/jarsigner">
<os family="windows" />
</condition>
<condition property="out-unsigned-package-ospath" value="${basedir}/${out-unsigned-package}" else="${basedir}/${out-unsigned-package}">
<os family="windows" />
</condition>
<condition property="out-signed-package-ospath" value="${basedir}/${out-signed-package}" else="${basedir}/${out-signed-package}">
<os family="windows" />
</condition>
<import file="${sdk.dir}/tools/ant/build.xml" />
<target name="keytool" depends="release">
<if>
<condition>
<not>
<resourceexists>
<file file="${outdir}/keystore.store"/>
</resourceexists>
</not>
</condition>
<then>
<exec executable="${keytool}" failonerror="true">
<arg value="-genkey" />
<arg value="-v" />
<arg value="-alias" />
<arg value="puma-alias" />
<arg value="-keyalg" />
<arg value="RSA" />
<arg value="-keysize" />
<arg value="2048" />
<arg value="-validity" />
<arg value="10000" />
<arg value="-keystore" />
<arg value="${outdir}/keystore.store" />
<arg value="-keypass" />
<arg value="${password}" />
<arg value="-storepass" />
<arg value="${password}" />
<arg value="-dname" />
<arg value="CN=puma,OU=cn,O=cn,L=cn,ST=cn,C=cn" />
</exec>
</then>
<!--else>
<fail message="keyfile already exist." />
</else-->
</if>
</target>
<target name="jarsigner" depends="keytool">
<exec executable="${jarsigner}" failonerror="true">
<arg value="-verbose" />
<arg value="-keypass" />
<arg value="${password}" />
<arg value="-storepass" />
<arg value="${password}" />
<arg value="-sigalg" />
<arg value="MD5withRSA" />
<arg value="-digestalg" />
<arg value="SHA1" />
<arg value="-keystore" />
<arg value="${outdir}/keystore.store" />
<arg value="-signedjar" />
<arg value="${out-signed-package}" />
<arg value="${out-unsigned-package}" />
<arg value="puma-alias" />
</exec>
</target>
<target name="zipalign" depends="jarsigner">
<exec executable="${zipalign}" failonerror="true">
<arg value="-v" />
<arg value="-f" />
<arg value="4" />
<arg value="${out-signed-package-ospath}" />
<arg value="${zipalign-package-ospath}" />
</exec>
</target>
</project>
step3. 在程式中呼叫ant api執行這個build.xml中的zipalign target就可以生成簽好名的apk了
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;
private void releaseProject(String projectSrcDir) throws Exception {
Project project = new Project();
project.init();
String _buildFile = new String(projectSrcDir + File.separator + "build.xml");
ProjectHelper.configureProject(project, new File(_buildFile));
if (project == null)
throw new Exception("No target can be launched because the project has not been initialized. Please call the 'init' method first !");
String _target = "zipalign";
project.executeTarget(_target);
}
OK,apk就這麼生成了,sdk提供的工具還是蠻強大的。
至於如何將docx轉換成epub,目前比較好的開源專案有adobe提供的EPUBGen,這個工程支援rtf2epub, word2epub, fb2epub等格式的轉換,考慮到大家用word比較多,就之用了word2epub了。使用方法很簡單:
ConversionService service = (ConversionService) it.next();
if(service.canConvert(srcFile)){
StringWriter log = new StringWriter();
PrintWriter plog = new PrintWriter(log);
destFile = service.convert(srcFile, null, this, plog);
}
如何修改已有epub的書籍資訊,可以使用epublib類庫,該類庫提供了EpubReader和EpubWritter。可以使用該類庫將txt或者其他文字型別的檔案轉成epub,嫌麻煩,就沒有做了。畢竟txt中不支援圖片,docx檔案中可以新增自己喜歡的圖片,還是比較好用的。
以上就是做這個轉換工具使用的一些方法,如果你看到了,希望對你有所幫助!
JUST DO IT。