編輯:關於Android編程
前言:最近苦思冥想一個問題,熱愛技術的人怎麼能夠在一個充滿規章制度的氛圍裡好好的生存下去並能初心不改呢?這就像為啥拍一集電視劇的報酬就是你好幾年的收入一樣,畢竟現實就是這麼個情況,想不明白就來寫寫文章吧。
不登高山不知天之大,不臨深谷不知地之厚。
一、場景
在數據庫升級時,不同的數據庫版本表結構是不同的,那麼怎麼確保數據庫升級後用戶的數據不會丟失呢?比如 V1.0表A有10列,V1.1由於業務需求表A需要增加兩列,在升級時我們該怎麼做?
二、傳統寫法
/**
* @author lh 2016/9/1
* @deprecated
*/
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "video.db";//數據庫名
private static final int DB_VERSION = 1;//數據庫當前版本號
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
//第一次安裝app會執行這個方法
//創建表A
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//數據庫版本升級時會執行這個方法
//第一步將表A重命名為temp_A
//第二步創建新表A,此時表結構已加了2列
//第三步講temp_A表中的數據插入到表A
//第四步刪除臨時表temp_A
}
}
代碼很簡單,但是這樣就真解決問題了嗎?數據庫從1升級到2要寫一個業務變化,從2升級到3也要寫一個業務變化,那是否考慮過數據庫版本從1升級到3應該先升級到2再升級到3這種情況呢?不論正確與否,這種寫法毫無擴展性可言,每次表結構發生變化都要改動這個類。下面看下模板寫法。
三、模板寫法
/**
* @author lh 2016/9/1
*/
public class DBHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "video.db";
private static final int DB_VERSION = VersionFactory.getCurrentDBVersion();
private static volatile DBHelper instance = null;
private DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public static DBHelper getDBHelper(Context context) {
if (instance == null) {
synchronized (DBHelper.class) {
if (instance == null)
instance = new DBHelper(context);
}
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
/**創建視頻信息表*/
db.execSQL(SqlUtil.createSqlForVideo());
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
update(db, oldVersion, newVersion);
}
/**
* 數據庫版本遞歸更新
*
* @param oldVersion 數據庫當前版本號
* @param newVersion 數據庫升級後的版本號
* @author lh
* @retrun void
*/
public static void update(SQLiteDatabase db, int oldVersion, int newVersion) {
Upgrade upgrade = null;
if (oldVersion < newVersion) {
oldVersion++;
upgrade = VersionFactory.getUpgrade(oldVersion);
if (upgrade == null) {
return;
}
upgrade.update(db);
update(db, oldVersion, newVersion);
}
}
}
DBHelper.update方法是一個遞歸實現,主要是解決數據庫跨版本升級。下面看一下Upgrade類
/**
* @author lh 2016/9/1
*/
public abstract class Upgrade {
public abstract void update(SQLiteDatabase db);
}
Upgrade是一個抽象類,就定義了一個數據庫升級的方法update(SQLiteDatabase db),以後數據庫版本每升級一次,就創建一個子類繼承Upgrade類,實現update方法,在方法內執行你的業務邏輯,比如表結構變更;
接著我們來看一下工廠類VersionFactory,他的作用就是提供一個工廠方法構建對象。
原始工廠寫法:
/**
* @author lh 2016/9/1
* @deprecated
*/
public class VersionFactory {
/**
* 根據數據庫版本號獲取對象
*
* @param i
* @return upgrade
*/
public static Upgrade getUpgrade(int i) {
Upgrade upgrade = null;
switch (i) {
case 2:
upgrade = new VersionSecond();
break;
case 3:
upgrade = new VersionThird();
break;
case 4:
upgrade = new VersionFourth();
break;
}
return upgrade;
}
}
這樣寫雖然達到了分別創建對象的效果,但是每次升級還是要來修改這個類,我就遇到一次,我同事在寫分支語句的時候漏掉了break,導致對象創建出錯,從而對應版本號的數據庫升級邏輯未能執行導致app升級安裝出錯。自那以後我就決定要優化這玩意兒,減少出錯的風險。我先貼一個Upgrade的實現類VersionSecond幫助大家理解
/**
* @author lh 2016/9/1
*/
public class VersionSecond extends Upgrade {
@Override
public void update(SQLiteDatabase db) {
//數據庫版本升級時會執行這個方法
//第一步將表A重命名為temp_A
//第二步創建新表A,此時表結構已加了2列
//第三步講temp_A表中的數據插入到表A
//第四步刪除臨時表temp_A
}
}
緊接著我們來看一下優化後的工廠模式寫法
/**
* @author lh 2016/9/1
*/
public class VersionFactory {
/**
* 根據數據庫版本號獲取對應的對象
* @param i
* @return
*/
public static Upgrade getUpgrade(int i) {
Upgrade upgrade = null;
// List> list = ClassUtil.getClasses("com.upsoft.ep.app.module.dbupdate");
if (null != list && list.size() > 0) {
try {
for (String className : list) {
Class cls = null;
cls = Class.forName(className);
if (Upgrade.class == cls.getSuperclass()) {
VersionCode versionCode = cls.getAnnotation(VersionCode.class);
if (null == versionCode) {
throw new IllegalStateException(cls.getName() + "類必須使用VersionCode類注解");
} else {
if (i == versionCode.value()) {
upgrade = (Upgrade) cls.newInstance();
break;
}
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new IllegalStateException("沒有找到類名,請檢查list裡面添加的類名是否正確!");
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return upgrade;
}
static Set list = new LinkedHashSet<>();
static {
list.add("com.upsoft.ep.app.module.dbupdate.VersionSecond");
list.add("com.upsoft.ep.app.module.dbupdate.VersionThird");
list.add("com.upsoft.ep.app.module.dbupdate.VersionFourth");
list.add("com.upsoft.ep.app.module.dbupdate.VersionFifth");
}
/**
* 得到當前數據庫版本
*
* @return
*/
public static int getCurrentDBVersion() {
return list.size() + 1;
}
}
先看getUpgrade方法,我注釋了一句代碼// List這句代碼的作用是獲取包名下的所有java文件,試想一下,數據庫表結構發生更新,我們就創建一個對應的Upgrade的子類去實現數據庫操作的相關邏輯,而這個子類是在固定的包名下進行創建,我們如果獲取了這個包的所有java文件,就意味著我們獲取到了數據庫更新的次數,這個次數再加上1是不是就是我們當前的數據庫版本號呢?如果是這樣,那麼我們的數據庫版本號就做到了根據算法自動疊加,想想就有點小激動呢~我可以負責任的告訴大家答案就是這樣的。不過:> list = ClassUtil.getClasses("com.upsoft.ep.app.module.dbupdate");
android畢竟是android,包建強(App研發錄作者,10幾年移動開發經驗)說過Android程序員做不好Java,Java程序員也做不好Android,因為各自都有自己的一套規則,不深入是不能夠理解的。當然也不排除有全能的~
在Java裡是可以獲取到某個包名下的java文件,但是在Android裡卻不行,因為Android虛擬機把java文件編譯成了dex文件,所以運行時狀態下就不存在這樣的文件了,這也是我注釋掉這句代碼的原因,因為沒用,獲取到的永遠都為空~~~
接著往下看:
遍歷存放Upgrade子類全路徑的集合,
通過反射的方式獲取類對象
判斷是否是Upgrade的子類
接著獲取VersionCode注解
VersionCode versionCode = cls.getAnnotation(VersionCode.class);我們來看看VersionCode是個什麼鬼?
/**
* @author lh 2016/9/1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface VersionCode {
int value() default 1;
}
該注解的作用是定義Upgrade子類的數據庫版本號,在子類使用,例如:
/**
* @author lh 2016/9/1
*/
@VersionCode(2)
public class VersionSecond extends Upgrade {
@Override
public void update(SQLiteDatabase db) {
//數據庫版本升級時會執行這個方法
//第一步將表A重命名為temp_A
//第二步創建新表A,此時表結構已加了2列
//第三步講temp_A表中的數據插入到表A
//第四步刪除臨時表temp_A
}
}
回到getUpdate方法
獲取到Update子類的注解後,我們將其和傳入的數據庫版本號進行比較,如果相等,則返回該對象,否則繼續遍歷直到獲取到正確的對象為止。
if (Upgrade.class == cls.getSuperclass()) {
VersionCode versionCode = cls.getAnnotation(VersionCode.class);
if (null == versionCode) {
throw new IllegalStateException(cls.getName() + "類必須使用VersionCode類注解");
} else {
if (i == versionCode.value()) {
upgrade = (Upgrade) cls.newInstance();
break;
}
}
}
getCurrentDBVersion這個方法就比較簡單,直接返回的Upgrade子類列表的數量加1作為數據庫的當前版本號1、首先采用了單例模式,如果不懂什麼單例以及線程安全請自行google。
2、采用遞歸的方式解決了數據庫跨越升級的問題
3、采用工廠模式達到了易擴展
4、采用注解和反射優化了工廠模式的短板
5、數據庫當前的版本也是由VersionFactory.getCurrentDBVersion()動態獲得,再也不用擔心數據庫升級直接操作DBHelper類導致的代碼錯誤風險。
另每次創建Upgrade子類都需要添加到list列表裡面去,這樣的話還是會修改到VersionFactory類(雖然做了異常檢查處理),你們具體實現的時候可以做成可配置的比如寫在XML文件裡,使用時進行XML解析即可。
那眼尖的童鞋又會問Android裡不是應該少用注解嗎?會影響性能~我可以放心的告訴大家,經過測試,我模擬了200次的數據庫更新操作,用正常的方式實現和用反射+注解的方式實現相差時間在毫秒級別(10毫秒左右),基本可以忽略。
Android UI設計之AlertDialog彈窗控件
有關android的彈窗界面相信大家見過不少了,手機上很多應用軟件都涉及到彈窗控件,比如典型的每次刪除一個圖片或者卸載一個等都會彈出一個窗口詢問是否刪除/卸載等,還有我們
Android 從StackTraceElement反觀Log庫
一、概述大家編寫項目的時候,肯定會或多或少的使用Log,尤其是發現bug的時候,會連續在多個類中打印Log信息,當問題解決了,然後又像狗一樣一行一行的去刪除剛才隨便添加的
Android開發之日期、時間選擇器(DatePicker和TimePicker)的功能和用法
日期、時間選擇器(DatePicker和TimePicker)的功能和用法 DatePicker和TimePicker是兩個比較常用的控件,它們都從FrameLa
微信如何避免提現被收取手續費 微信提現避免收取手續費方法
微信前不久已想告知大眾從3月1日起微信提現就要收取手續費。但是很多高富帥白富美在微信錢包中存在很多錢,如何避免提現被收取手續費?怎麼做到提現不收取手續費呢?