Android 應用程序的向後兼容(譯)

(英語水平有限,翻譯的不好請多多指教與諒解)
通過交通運輸,消費者可以有機會使用到來自全世界的各種各樣的Android設備,在衆多的設備中,也運行着不同版本的Android系統平臺,一些設備上運行着新版本的系統,一些設備上運行着舊版本的系統。作爲一個開發者,在你的應用程序中,需要實現向下兼容――取決於你想在所有設備上運行你的程序,還是隻想運行在最新的系統平臺上?在有些時候,如果可以兼容較舊版本的設備同時,在支持新版本系統的設備上採用新的API將是有幫助的。
設置最低的Sdk版本
如果使用新的API對應用程序有利――假設你使用Android1.5(API Level 3)中的API刻錄視頻文件――你應該在應用程序的功能描述文件中添加一個<android:minSdkVersion>標籤,來確保你的程序不會被安裝在更低版本的設備上。舉例說明,如果在你的應用程序中,引用的API 爲API Level 3,你應該設置“3”爲最低Sdk版本。
<manifest>
...
<uses-sdk android:minSdkVersion="3" />
...
</manifest>
然而,如果你想加入一個實用的但是非必要的特性,就像如果硬件支持的時候在屏幕上彈出一個鍵盤,你可以允許你的應用使用新的特點,而同時不會在低版本的設備上失去作用(就是指應用程序,在所有的版本的設備上都可以使用,在支持新特性的設備上就可以使用新特性,不支持就不使用)。
使用反射
假設有一個新的函數你想調用,像android.os.Debug.dumpHprofData(String filename)。Debug類在1.0版本中就已經存在了,但是這個方法確實1.5版本才推出的。如果直接調用這個方法,你的應用程序在1.1或者更早的版本上將無法運行。
想要調用這個方法,最簡單的做法是通過反射。這需要一次查找然後在Method對象中匹配結果。調用方法通過Method.invoke方法並且不會把結果保存起來,像下面這樣:
public class Reflect {
private static Method mDebug_dumpHprofData;

static {
initCompatibility();
};

private static void initCompatibility() {
try {
mDebug_dumpHprofData = Debug.class.getMethod(
"dumpHprofData", new Class[] { String.class } );
/* success, this is a newer device */
} catch (NoSuchMethodException nsme) {
/* failure, must be older device */
}
}

private static void dumpHprofData(String fileName) throws IOException {
try {
mDebug_dumpHprofData.invoke(null, fileName);
} catch (InvocationTargetException ite) {
/* unpack original exception when possible */
Throwable cause = ite.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
} else if (cause instanceof Error) {
throw (Error) cause;
} else {
/* unexpected checked exception; wrap and re-throw */
throw new RuntimeException(ite);
}
} catch (IllegalAccessException ie) {
System.err.println("unexpected " + ie);
}
}

public void fiddle() {
if (mDebug_dumpHprofData != null) {
/* feature is supported */
try {
dumpHprofData("/sdcard/dump.hprof");
} catch (IOException ie) {
System.err.println("dump failed!");
}
} else {
/* feature not supported, do something else */
System.out.println("dump not supported");
}
}
}
使用了一個靜態代碼塊作爲初始化器調用initCompatibility方法,initCompatibility方法做了查找的動作。如果查找成功了,將會調用一個私有的和原來參數、返回值、異常檢查都一樣的方法。
返回值(如果有返回值)和異常沒有被打包,它們以最原始的形式返回。fiddle方法展示了程序在調用新的方法時,根據方法是否存在而做出不同的處理。
對於每一個你想調用的新增的方法,你應該添加一個Method字段,一個靜態初始化代碼塊,並且調用包裝後的類。
對於調用一個新的方法來說,這樣的實現有點複雜,調用Method.invoke方法也比直接調用方法本身要慢。通過使用包裝類,這些問題可以被緩解。
使用包裝類
這個思想就是通過一個類把一個已經存在的類或者一個新類,中的新方法包裝起來。包裝類中的每一個方法,只是通過調用真實的方法並且返回相同的結果。
如果目標類和方法已經存在,直接調用存在的方法就會得到你想要的結果,這時通過包裝類這樣調用時會有一點點代碼重複。如果目標類或者方法不存在,包裝類的初始化就會失敗,你的應用程序就會知道它應該避免使用新的方法。
假設添加這樣一個新的類:
public class NewClass {
private static int mDiv = 1;

private int mMult;

public static void setGlobalDiv(int div) {
mDiv = div;
}

public NewClass(int mult) {
mMult = mult;
}

public int doStuff(int val) {
return (val * mMult) / mDiv;
}
}
我們回爲它創建一個包裝類:
class WrapNewClass {
private NewClass mInstance;

/* class initialization fails when this throws an exception */
static {
try {
Class.forName("NewClass");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

/* calling here forces class initialization */
public static void checkAvailable() {}

public static void setGlobalDiv(int div) {
NewClass.setGlobalDiv(div);
}

public WrapNewClass(int mult) {
mInstance = new NewClass(mult);
}

public int doStuff(int val) {
return mInstance.doStuff(val);
}
}
相對於原來的構造方法和方法而言,這有一個方法,提供了一個靜態的初始化代碼塊用來測試新類(NewClass),如果新類不能訪問,那麼WrapNewClass類的初始化就失敗了,證明了包裝類不能被使用。checkAvailable方法用來暴力初始化,我們可以這樣使用:
public class MyApp {
private static boolean mNewClassAvailable;

/* establish whether the "new" class is available to us */
static {
try {
WrapNewClass.checkAvailable();
mNewClassAvailable = true;
} catch (Throwable t) {
mNewClassAvailable = false;
}
}

public void diddle() {
if (mNewClassAvailable) {
WrapNewClass.setGlobalDiv(4);
WrapNewClass wnc = new WrapNewClass(40);
System.out.println("newer API is available - " + wnc.doStuff(10));
} else {
System.out.println("newer API not available");
}
}
}
如果調用checkAvailable方法成功了,我們知道新的類在系統中存在。如果失敗了,我們知道系統中不存在這個類,我們需要調整我們的期望目標。值得注意的是,即使在匹配字節碼開始之前調用checkAvailable方法也會失敗,如果它不接受一個對不存在的類的引用。這樣的代碼結構,無論是字節碼匹配還是調用Class.forName過程中出異常,結果都是一樣的。
當一個已經存在的類增加新方法時,我們包裝這個類時,只需要把新方法放在包裝類中。直接執行方法即可。包裝類中的靜態初始化代碼塊會通過反射執行一次查找操作。
測試是關鍵
你必須在應用程序想要運行的平臺版本上都進行測試。當然,在不同的平臺版本上,應用程序的行爲可能有所不同。記住這個咒語:如果你沒測試過它,他將不會工作。
你可以通過改變模擬器的平臺版本來測試你應用程序的向後兼容性。通過創建不同API版本的Android 模擬器,Android SDK 上可以很容易的實現測試。創建了不同的模擬器後,你可以在每個版本的平臺上都進行一次測試,來觀察有什麼不同,在AVD文檔中或者通過emulator -help-virtual-device.命令可以查看更多的關於模擬器得信息。
發佈了40 篇原創文章 · 獲贊 2 · 訪問量 3304
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章