Android App 開發使用 Expansion File

Android的開發者在開發時其實都會遇到一個問題

就是開發的APP檔案如果超過50M時,Google Play 就不會讓你上架了

對,Google將APP的檔案大家限制為50M,官方的文件是說,50M能適用很多的APP開發都已經夠用了,

現在現在的APP如果是需要一個音樂或是真的很容易破表,所以老實說我不知道他限制50M是為了什

(iOS 都沒這個限制,真是..)

好,那如果真的超過50M怎麼辦呢, Google官方提供的解決方案,就是他會提供兩個Expansion File讓你使用

一個叫做 “main expansion”,一個稱為 “patch expansion”

每個的大小不超過2G,再上傳APK到Google Play時候,同時上傳Expansion File,

他對這兩個Expansion File的說明如下:

The main expansion file is the primary expansion file for additional resources required by your application.
(main expansion 是主要的擴充檔,是為了增加你應用程式所需要的資源)

The patch expansion file is optional and intended for small updates to the main expansion file.
(patch expansion 是可選擇使用的,功能為了是如果你的main expansion file有需要小更新時使用)

所以這個解決方案,就是你必須將一個大的檔案,例如:動畫,音樂檔,甚至是大的圖片檔案獨立出來,將它放入Expansion File裏面

雖然文件說明檔案的格式可以ZIP, PDF, MP4, etc.,但是如果是好幾個檔案的話,就必須將這些檔案都壓縮到一個ZIP裏面

然後使用者在Google Play安裝APP之後,通常會自動下載Expansion File,文件是說通常,所以有可能不會自動下載,我測試過真的有可能會沒

下載到,那如果真的沒下載到,那如果缺少Expansion File,使用者開啟APP,或爆掉,那如果要預防這個情況怎麼辦,就是要在APP開啟時要去檢查一下Expansion File有沒有下載成功,那如果沒下載成功的話呢?Google就不管你了(- -),你必須透過認證的方式,自己去將此Expansion File下載下來

然後自己將下載完的Expansion File放到外部儲存裝置(External Storage)上,因為在下載的時候,為了讓用者有好的使用體驗,要使用非同步的方式,Google他有提供一個Downloader Service的Lib讓你方便去實作這個Expansion File下載

可以參考 http://developer.android.com/google/play/expansion-files.html

Expansion File下載之後該做的事情呢?如果是ZIP檔案的話,有兩種方式:

@將它解壓縮到一個地方,之後在直接去讀取解壓縮的檔案

據我所知很多APP都是這樣做

使用以下的Fucntion去取得路徑,再用ZIP解壓縮LIB去解壓縮

“`java
// The shared path to all app expansion files
private final static String EXP_PATH = “/Android/obb/”;

static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
    String packageName = ctx.getPackageName();
    Vector<String> ret = new Vector<String>();
    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
        // Build the full path to the app's expansion files
        File root = Environment.getExternalStorageDirectory();
        File expPath = new File(root.toString() + EXP_PATH + packageName);

        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                        ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." +
                        mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                        ret.add(strPatchPath);
                }
            }
        }
    }
    String[] retArray = new String[ret.size()];
    ret.toArray(retArray);
    return retArray;
}

“`
getAPKExpansionFiles取得的路徑Array,[0]是main expansion的路徑,[1]是patch expansion file

至於ZIP解壓的LIB我試過好幾個覺得zip4j這個LIB速度最快,最好用 可參考 http://www.lingala.net/zip4j/

使用方法很簡單:
“`java
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;

try {
    ZipFile zipFile = new ZipFile(source);
    zipFile.extractAll(destination);
} catch (ZipException e) {
    e.printStackTrace();
}

“`
@直接讀取ZIP壓縮檔裡面的檔案

方式一:傳appContext和mainVersion或是 patchVersion去取得ZIP的 fileStream
“`java
// Get a ZipResourceFile representing a merger of both the main and patch files
ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext,
mainVersion, patchVersion);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

“`

方式二:如果知道ZIP路徑用這個Function去取得去取得ZIP的 fileStream
“`java
// Get a ZipResourceFile representing a specific expansion file
ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip);

// Get an input stream for a known file inside the expansion file ZIPs
InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);

“`
這兩個方式主要都是透過 pathToFileInsideZip 這個參數去指向到ZIP裡面你要的檔案

再來記得在AndroidManifest.xml加上以下幾個權限
java
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

因為官方文件沒有寫,後來發現這幾個權限沒加,是完全會被擋掉的,是不會工作的

個人覺得Google這個方法,因為Expansion File的路徑編碼是根據APP的Version去做的,所以APP Version升級之後,如果Expansion File也要升級,就要再傳一次Expansion File,如果前一版Expansion File沒有刪除,就會留下越來越多Expansion File垃圾

且使用者將APP刪除後Expansion File的檔案也不會自動刪除,iOS的做法所有的檔案都是包在一起的,如果刪除,所有檔案都會刪除,開發者開發大檔案的APP也不用搞得這麼麻煩,不然我也不用寫這篇文章了

所以iOS的開發真的是太幸福了,但是使用Android的使用者那麼多,也沒辦法,還是得寫Android版本XD

寫這篇文章有兩個目的:

一個是中文的這方面資料沒有

一個是我稍微抱怨一下