2012年5月9日 星期三

Android 開發筆記 - 使用 SQLite / SQLiteDatabase / SQLiteOpenHelper

MyDBHelper


SQLite 這幾年來已經成了一種標準用法,無論是嵌入式或是 PC 上的軟體,很容易看的到 SQLite 的蹤跡了。


對 Android 系統架構我還沒那麼熟,粗略知道它有 SQLiteDatabase 和 SQLiteOpenHelper 兩個 class 可供 SQLite 操作使用。網路上翻到的資料,大概要先撰寫一隻 SQLiteOpenHelper,此角色是用來初始化資料庫,包含建 Table 、資料庫升級等對應動作。有些人習慣把資料庫的操作都寫在同一個 class (extends SQLiteOpenHelper)中,由於我經驗尚淺,我不曉得這樣的做法是不是正確的,因為觀看一些物件特性及機制似乎不太恰當。以下用簡易範例當作筆記。


新增 MyDBHelper 並繼承 android.database.sqlite.SQLiteOpenHelper,接著實作 void onCreate(SQLiteDatabase db) 和 void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) 後,就算完成 SQLiteOpenHelper 的工作了:


package com.example.study;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;


public class MyDBHelper extends SQLiteOpenHelper {
        final private static int _DB_VERSION = 1;
        final private static String _DB_DATABASE_NAME = "MyDatabases.db";
        public MyDBHelper(Context context) {
                super(context,_DB_DATABASE_NAME,null,_DB_VERSION);
        }
        public MyDBHelper(Context context, String name, CursorFactory factory, int version) {
                super(context, name, factory, version);
                // TODO Auto-generated constructor stub
        }


        @Override
        public void onCreate(SQLiteDatabase db) {
                // TODO Auto-generated method stub
                db.execSQL(
                        "CREATE TABLE MyTable (" +
                                " _ID INTEGER PRIMARY KEY, " +
                                " _DATA VARCHAR(50) NOT NULL, " +
                                " _DATETIME DATETIME NULL " +
                        ")"
                );
        }


        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                // TODO Auto-generated method stub
                db.execSQL("DROP TABLE IF EXISTS MyTable");
                onCreate(db);
        }
}


接著在 MyTestingActivity 中,就可以操作:


package com.example.study;


import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;


public class SQLiteDBTestingActivity extends Activity {
        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);

                // Init
                MyDBHelper mHelper = new MyDBHelper(this);
                SQLiteDatabase mDB = null;

                // Insert by raw SQL
                mDB = mHelper.getWritableDatabase();
                mDB.execSQL( "INSERT INTO MyTable (_DATA,_DATETIME) VALUES ('Hello World', datetime('now'))");
                mDB.close();

                // Insert by object method
                mDB = mHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                values.put("_DATA","YoHO");
                values.put("_DATETIME","2012-05-09 20:30:00");
                mDB.insertOrThrow("MyTable",null,values);
                mDB.close();

                // Query by raw SQL
                mDB = mHelper.getWritableDatabase(); // mDB = mHelper.getReadableDatabase();
                Cursor cursor = mDB.rawQuery("SELECT _ID, _DATA, _DATETIME FROM MyTable", null);
                cursor.moveToFirst();
                while(!cursor.isAfterLast()) {
                        Log.e("SQLiteDBTestingActivity","_ID = "+cursor.getInt(0));
                        Log.e("SQLiteDBTestingActivity","_DATA = "+cursor.getString(1));
                        Log.e("SQLiteDBTestingActivity","_DATETIME = "+cursor.getString(2).substring(0, 16));
                        cursor.moveToNext();
                }
                startManagingCursor(cursor);
                cursor.close();
                mDB.close();
        }
}


執行結果:


output


從上述片段程式來看,可以看到一開始 SQLiteOpenHelper 初始化必須傳入一個 Context,並且執行資料庫操作是透過 getWritableDatabase() 或 getReadableDatabase() 而來的,且查詢時會透過 Cursor 甚至 Activity 的 startManagingCursor 等動作,結束時還需做一些 close 的資源回收,因此,個人覺得把這一系列的動作都包進 SQLiteOpenHelper 似乎不太恰當。目前經驗尚淺,或許需要多累積一些實作經驗才會明瞭吧!


此外,可以透過 DDMS 將模擬器內的 SQLiteDB 取出來 (/data/data/com.example.study/databases/MyDatabases.db),並且用 sqlite3 等相仿的 cmd 去操作:


$ sqlite3 MyDatabases.db
SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .table
MyTable android_metadata
sqlite> SELECT * FROM MyTable;
1|Hello World|2012-05-09 12:41:07
2|YoHO|2012-05-09 20:30:00
sqlite>


2012年5月7日 星期一

[Linux] 免費編輯 PDF 文件 (刪除、新增頁面) @ Ubuntu 10.04

pdfeditor


最近偶爾會編輯一下 PDF 檔案,由於在 Windows 上似乎沒有幾套免費的 PDF 編輯軟體,有的免費的不支援存檔或是增減頁數,於是我只好默默開啟 Ubuntu 後,安裝 PDF Editor 啦 XD


$ sudo apt-get install pdfedit


接著就可以開啟想要更改的 pdf 檔案,例如把其中一頁給刪掉 (Remove Page)


remove page


如果編輯文件時,底下的視窗出現類似的資訊:


PDFedit
Loaded file : test.pdf
> removePageWithConditionalRefresh()
! In script '/usr/share/pdfedit/page.qs', line 118:
! Error. Exception in Pdf.removePage : Document is read-only


此時要先透過 [Tools] -> [Delinearize] 重新選這個檔案(此例為test.pdf),再另存起來(如 test2.pdf),接著開啟 test2.pdf 就能夠編輯。


pdfedit_add_pages


此外,也可以新增 pdf 檔案進來 [Tools] -> [Insert Pages from other document],接著開啟想要匯入的檔案後,可以把想要的 page 匯入,並調整頁數位置。雖然匯入的格式必須是 PDF 檔案,但也還算方便啦。


2012年5月5日 星期六

簡易拆解、組裝 iPhone 3G

15


幾個月前,同事的 iPhone 3GS 的電池開始出現充不飽、急速用完的問題,因此透過網拍管道找到換修電池的服務,當時跟他去看看到底怎樣拆 iPhone 3GS ,看完一遍後,就一直想要親手試看看,如今有空,我也拆了一台 iPhone 3G (不是 iPhone 3GS)或許哪天我也可以只買電池幫老家的 3G 手機換一下 :D (由於電池是貼黏在 iPhone 3G 背殼,所以此例只拆到看的見電池為此) 但我是建立在家中手機已經快 3 年的態度 XD 實在不建議其他人隨便看看文章就親手拆!這風險應該比 JB 還高吧 XDD 就當作個筆記笑笑吧!


老話一句:


拆機存在機器損壞風險,並喪失保固(貼紙損壞),請交給專業的
不要隨意嘗試,需自行承擔責任


01


首先是準備的工具,拆的話至少需要一個小吸盤跟螺絲起子,組裝的話需要多一支小夾子(那支尺只是比例參考)。我特地跑去五金賣場花了 32 元買了一支夠小的螺絲起子,實在是 iPhone 的螺絲大小約 2mm 而已!賣場還滿容易看到賣一盒小工具 110 ~ 210 的價錢,但我只需要最小的那支而已,且不能當場測試我也不知是不是夠小,去傳統五金賣場,還帶著 iPhone 去那邊試喔,這時才會發現傳統的美好 :D


02


拆掉接頭旁邊的兩個螺絲後,用手握手機的方式,把手握住靠近電源那頭,把吸盤擺在靠近插頭那端,須要給一點點力氣,就能夠使之分離。


03


此時螢幕與機身還有三處連結,這時可以看到一些標記處,這大概是提醒拆裝者的資訊。依編號來看,共有六個號碼,其中 1~3 個編號拆開後,就能使螢幕與機身分離,編號 4 應該是插頭與板子的連接處,編號5猜測是電池連接頭,編號6是一個圓形接頭,但我就沒研究了 XD


04


首先編號1算是很輕鬆就扳開,甚至可能把螢幕拉高一點就會自動分離,而編號2稍微緊了點,從下面扳開有點難,我就藉由它的帶子輕輕地分離它。


05 06 07


接著是很重要的編號3,這個接法算是此次最最麻煩且小心的地方,因為他就像兒時任天堂跟遊戲卡帶的接法,只是編號3是很柔的帶子,拆除是很簡單的,但若沒透過夾子,想要把它插回去是十分困難的,並且我隨意找的網路影片,都很自然地略過把它接回去的片段 orz 不知是不是要偷留一手。


08


當編號 1~3 拆開後,螢幕跟機身就分離了!


09 11


接著就順著流程把編號 4~6 都拆掉後,還要移除 sim 卡槽和右上角鏡頭的螺絲部分跟 7 處的小螺絲。


12


從右往左分別是兩個螢幕螺絲、鏡頭螺絲與 L 片,最後則是七顆小螺絲。


14 15


從插座那端慢慢地扳起來,接著網插座那端輕輕地抽出來,就可以看到電池啦。這次練習就到這裡而已,忘了先買電池,所以就沒繼續研究(因為電池是黏在背殼的,拆掉沒辦法黏的穩定)


20 21 22


接著就是組裝回去,一樣斜斜地把板子擺放到原處,記得要小心編號 6~4 不要被板子壓到。


23 24 25 26


接下來把可以鎖上編號五跟右下角的螺絲,將板子固定好,再來編號 4~6 、鏡頭擺好及 L 板鎖好。


27 28


如此一來,就只剩螢幕跟機身的部分了!最困難的地方就是要把編號 3 接好,當初研究很久,才想起來可以試試看夾子,透過夾子的幫忙,終於把編號3接回去了!大部分螢幕不會顯示,大概都是這處出錯吧 XD 其實我第一次也沒接好,因為沒用夾子,粗糙地接好但螢幕不會顯示!只能喃喃自語地說「反正這台過保也打算換了」,但內心卻在淌血 XDD 後來想到夾子後,在左手努力之下,終於按下開機按鍵可以看到白蘋果啦!


29


網路上可以看到很多討論串,大部分都說螢幕沒顯示的話,大概是把螢幕搞壞掉了 :p 起初我也小小放棄了,但後來還是覺得很怪,這種需要人力組裝的 3C 產品,在製程上應該是有 try 過的,雖然我是生手,不至於這麼容易壞(不然量產根本不可能吧?!),所以多方嘗試,我也把它救回來了 XD


2012年5月2日 星期三

OpenStreetMap API 筆記


From: Bing Maps Tile System


去年做了一整年跟圖資有關的案子,結果一直對這塊很好奇,原理大概知道,但細節卻遲遲沒播出時間來研究,今天毛起來翻了一下資料,就從 OpenStreetMap 下手吧!整體上原理就是把地球三維座標投影成二維座標,接著用一張張 256x256 正方型圖片來組成一張地圖,因此想要顯示地圖就是把目前關注的區域座標範圍,計算一下落在哪些圖上,接著把圖取出來即可。


此處 OpenStreetMap 的 Image API URL = "http://tile.openstreetmap.org/" + Zoom Level + "/" + X + "/" + Y + ".png" ,例如 Level 0 的地圖僅有一張,即為: http://tile.openstreetmap.org/0/0/0.png ,接著到 Level 1 時,變成 2x2 張圖、Level 2 為 4x4 張圖,每張圖仍維持 256x256 的大小,以此類推。


使用上就是要處理 GPS 座標:


在 Level 0 時,就是把座標轉換到 256x256 位置
在 Level 1 時,則為 512x512 位置
在 Level 2 時,計算 GPS 座標在 1024x1024 圖片的哪個位置
... 以此類推


至於座標投影如何轉換,可以參考 Bing Maps Tile System 或 Tiles à la Google Maps: Coordinates, Tile Bounds and Projection(globalmaptiles.py),也可以翻別人的 code 套公式,下次有空再來細細研究 XD


Level 0:








Level 0 

Leve 1:













Leve 1 (0,0)  Level 1 (1,0)
 Level 1 (0,1) Level 1 (1,1)

Leve 2:


 





























Level 2 (0,0) Level 2 (1,0) Level 2 (2,0)  Level 2 (3,0)
 Level 2 (0,1) Level 2 (1,1) Level 2 (2,1) Level 2 (3,1)
 Level 2 (0,2) Level 2 (1,2) Level 2 (2,2) Level 2 (3,2)
 Level 2 (0,3) Level 2 (1,3) Level 2 (2,3) Level 2 (3,3)

參考資料:


OpenStreetMap - Beginners Guide 1.5
OpenStreetMap - Slippy Map
OpenStreetMap - OpenLayers
OpenStreetMap - Google Maps Example
Bing Maps Tile System
Tiles à la Google Maps: Coordinates, Tile Bounds and Projection