該怎麼說呢?開始感覺到時間的不夠用了,人生不像大學期中期末考前,一夜不睡,至少可以追到七八成。
2012年5月21日 星期一
Android 開發筆記 - 為自己的 app 建立 Content Provider
整個流程在 Android 官網(Create Your Content Provider)已經有很豐富的介紹,在此筆記一下 :P 加快自己往後回憶。
新增自己的 content provider 的用亦是供別人來存取自身 app 的資料(大多是 SQLite 操作),而實做流程並不難,但有一些小事要留意:
- 假設你的 app 的 package name 為 com.example.study,實做為 Content Provider Class 為 MyContentProvider。
- 首先,建立一個 app 名為 MyContentProvider,但不需要 Activity,新增完後,在新增一個 class 名為 MyContentProvider 並繼承 android.content.ContentProvider。
- 由於 content provider 給予外頭存取時,用到的格式為 "content://AUTHORITY/DBTable" 的型態,故決定 Content Provider 的 AUTHORITY 數值及使用的 SQLite db table name,此例定為 "com.example.study" 和 "MyTable1"、"MyTable2" 兩者。
- 實做 SQLiteOpenHelper,此例為 MyDBHelper,過程中建立兩張表,分別為 MyTable1 和 MyTable2 兩張表
CREATE TABLE MyTable1 ( _ID INTEGER PRIMARY KEY, _DATE DATETIME NULL )
CREATE TABLE MyTable2 ( _ID INTEGER PRIMARY KEY, _DATA VARCHAR(50) NULL )- 決定 content provider 所提供的 URI 服務後,使用 UriMatcher 來實做判斷方式
private static final UriMatcher mUriMatcher;
private static final int URI_TYPE_TABLE1 = 1;
private static final int URI_TYPE_TABLE2 = 2;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, MyDBHelper._DB_TABLE1, URI_TYPE_TABLE1);
mUriMatcher.addURI(AUTHORITY, MyDBHelper._DB_TABLE2, URI_TYPE_TABLE2);
}- 實做 content provider 的相關函數,此例僅實做 String getType(Uri uri) 和 Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 兩個函數,前者用來告知 URI 所回傳的資料型態,後者則是 URI Query 時,將取得的 Cursor 操作方式(此為 SQLite 常用方式)
- 設定 AndroidManifest.xml,跟系統註冊使用 content provider。AndroidManifest.xml -> Application 分頁 -> Application Nodes -> Add -> Provider -> Name 欄位瀏覽 -> 選擇 MyContentProvider 後,並輸入 Authorities 欄位資訊(此例為com.example.study)
- 最後,實做一個 TestingActivity 來執行 content provider 的 query 動作,並從 Log.w 觀察情況。
程式碼:
MyContentProvider.java:
package com.example.study;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
public class MyContentProvider extends ContentProvider {
private static final String AUTHORITY = "com.example.study";
private static final UriMatcher mUriMatcher;
private static final int URI_TYPE_TABLE1 = 1;
private static final int URI_TYPE_TABLE2 = 2;
static {
mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mUriMatcher.addURI(AUTHORITY, MyDBHelper._DB_TABLE1, URI_TYPE_TABLE1);
mUriMatcher.addURI(AUTHORITY, MyDBHelper._DB_TABLE2, URI_TYPE_TABLE2);
}
private class MyDBHelper extends SQLiteOpenHelper {
public static final String _DB_NAME = "MyDatabases.db";
public static final String _DB_TABLE1 = "MyTable1";
public static final String _DB_TABLE2 = "MyTable2";
public static final int _DB_VERSION = 1;
public MyDBHelper(Context context) {
super(context, _DB_NAME, null, _DB_VERSION);
}
public MyDBHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL( "CREATE TABLE "+_DB_TABLE1+" ( _ID INTEGER PRIMARY KEY, _DATE DATETIME NULL )" );
db.execSQL( "CREATE TABLE "+_DB_TABLE2+" ( _ID INTEGER PRIMARY KEY, _DATA VARCHAR(50) NULL )" );
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS "+_DB_TABLE1);
db.execSQL("DROP TABLE IF EXISTS "+_DB_TABLE2);
}
}
MyDBHelper mHelper = null;
private void genTesting() {
SQLiteDatabase mDB = mHelper.getWritableDatabase();
// MyDBHelper._DB_TABLE1
mDB.execSQL("INSERT INTO "+MyDBHelper._DB_TABLE1+" (_DATE) VALUES (datetime('now'))");
// MyDBHelper._DB_TABLE2
ContentValues values = new ContentValues();
values.put("_DATA", "Hello World!");
mDB.insertOrThrow(MyDBHelper._DB_TABLE2, null, values);
mDB.close();
}
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
mHelper = new MyDBHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
switch(mUriMatcher.match(uri)) {
case URI_TYPE_TABLE1:
return "vnd.android.cursor.item/vnd."+AUTHORITY+"."+MyDBHelper._DB_TABLE1; // one row
case URI_TYPE_TABLE2:
return "vnd.android.cursor.dir/vnd."+AUTHORITY+"."+MyDBHelper._DB_TABLE2; // multiple rows
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
genTesting();
Cursor out = null;
SQLiteDatabase mDB = null;
switch(mUriMatcher.match(uri)) {
case URI_TYPE_TABLE1:
mDB = mHelper.getWritableDatabase();
out = mDB.rawQuery("SELECT * FROM "+MyDBHelper._DB_TABLE1+" ORDER BY _ID DESC LIMIT 1", null);
out.moveToFirst();
break;
case URI_TYPE_TABLE2:
mDB = mHelper.getWritableDatabase();
out = mDB.rawQuery("SELECT * FROM "+MyDBHelper._DB_TABLE2, null);
out.moveToFirst();
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
return out;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
// TODO Auto-generated method stub
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
return 0;
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
return 0;
}
}
MyContentProviderTestingActivity.java:
package com.example.study.test;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
public class MyContentProviderTestingActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Cursor c = getContentResolver().query(
Uri.parse("content://com.example.study/MyTable1"),
null,null,null,null ); // get all
if( c != null ) {
c.moveToFirst();
while(c.isAfterLast()==false) {
Log.e("CP","MyTalbe1 field1:"+c.getString(0)+", field2:"+c.getString(1));
c.moveToNext();
}
c.close();
c = getContentResolver().query(
Uri.parse("content://com.example.study/MyTable2"),
null,null,null,null ); // get all
c.moveToFirst();
while(c.isAfterLast()==false) {
Log.e("CP","MyTalbe2 field1:"+c.getString(0)+", field2:"+c.getString(1));
c.moveToNext();
}
c.close();
}
}
}
2012年5月13日 星期日
2012年5月9日 星期三
Android 開發筆記 - 使用 ListView 和 ListActivity
對 HTML 來說,所謂的 ListView 大概稱得上網頁上常看到的 ul、ol、dt 或是 table 吧!這東西真的是萬用的好工具,最直觀的排版,正所謂規規矩矩,不美也不會太醜。最近我終於開始在 Android 上使用這個東西了,光這功能就能讓你完成九成的工作了。更多 ListView 跟 ListActivity 的資訊,請參考官網的介紹,這邊僅簡易的筆記。
在 MyActivity 上呼叫自訂的 MyListActivity 的方式:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent mIntent = new Intent();
mIntent.setClass(this, MyListActivity.class);
startActivity( mIntent );
}
在 MyListActivity 中,使用簡易的排版。範例一,使用系統內建 android.R.layout.simple_list_item_1 排版:
程式碼:
package com.example.study;
import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class MyListActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] values = new String[] { "Android", "iPhone", "WindowsMobile", "Blackberry", "WebOS", "Ubuntu", "Windows7", "Max OS X", "Linux", "OS/2" };
setListAdapter(
new ArrayAdapter<String>(
this,
android.R.layout.simple_list_item_1,
values)
);
}
}
範例二,使用系統內建 android.R.layout.simple_list_item_2 排版:
程式碼:
package com.example.study;
import android.app.ListActivity;
import android.os.Bundle;
import java.util.ArrayList;
import java.util.HashMap;
import android.widget.SimpleAdapter;
public class ProgramListActivity extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final String ID_TITLE = "TITLE", ID_SUBTITLE = "SUBTITLE";
ArrayList<HashMap<String,String>> myListData = new ArrayList<HashMap<String,String>>();
String[] titles = new String[]{ "Title1" , "Title2", "Title3" };
String[] subtitles = new String[]{ "SubTitle1" , "SubTitle2", "SubTitle3" };
for( int i=0;i<titles.length ; ++i) {
HashMap<String,String> item = new HashMap<String,String>();
item.put(ID_TITLE,titles[i]);
item.put(ID_SUBTITLE,subtitles[i]);
myListData.add(item);
}
setListAdapter( new SimpleAdapter(
this,
myListData,
android.R.layout.simple_list_item_2,
new String[] { ID_TITLE, ID_SUBTITLE },
new int[] { android.R.id.text1, android.R.id.text2 } )
);
}
}
其中 android.R.layout.simple_list_item_1.xml 和 android.R.layout.simple_list_item_2.xml 的資料,可以查閱自己用的 SDK 囉,例如使用 Android 2.2 時,在 Eclipse 的 Package Explorer 中,可以瀏覽 Android 2.2 -> android.jar -> res.layout 並在裡頭找到。
此處提的範例都是 ListActivity 的,如果你在你的 Activity 的 layout 上擺了一個 ListView 的話,那只需要先用 findViewById 後,就可以仿照 ListActivity 把資料餵進去,如:
ListView mListView = (ListView)findViewById(R.id.listView1);
if( mListView != null ) {
mListView.setAdapter(
new SimpleAdapter(
this,
mDataSource ,
android.R.layout.simple_list_item_2,
new String[] { ID_TITLE, ID_DETAL },
new int[] { android.R.id.text1, android.R.id.text2 }
)
);
}
Android 開發筆記 - 使用 Content Provider
來源:Calendar Provider data model
由於安全設計關係,預設 app 操作使用的 sqlite db 都是在 sandbox 環境,換句話說,也就是僅 app 自身可獨立存取得而已,其他 app 是不能存取的。因此,如果想要提供自身的 sqlite db 資料給其他程式使用,除了寫 Android Service 架構來達到 IPC 或寫 Socket 程式溝通資料外,還可以採用 Content Provider 的架構。當然,在此不討論 root 後突破權限管理的機制 XD 舉例來說,Android 系統內建 Calendar app,而使用 Android 會綁定一個 Gmail 帳號,那有沒辦法取得此帳號的行事曆呢?此時可以透過 Content Provider 來進行操作。
首先,想要讀寫 Calendar 資料,那在 app 的 AndroidManifest.xml 權限上,需要加上 android.permission.READ_CALENDAR 和 android.permission.WRITE_CALENDAR,這樣的設計是告知使用者此程式將會存取 calendar 資料。
接著 app 中,可以透過 ContentResolver 物件,向 Calendar app 進行存取,如 content://CONTENT_URI/DBTable,此例為 Calendars.CONTENT_URI 等於 content://calendar/calendars,但建議使用 Calendars.CONTENT_URI 變數來存取,因為在各個 Android 版本中,可能因為軟體升級或架構改變而儲存位置變更,因此透過 Calendars.CONTENT_URI 存取最為恰當(有些版本為 content://com.android.calendar/calendars)。
片段程式碼:
// Projection array. Creating indices for this array instead of doing
// dynamic lookups improves performance.
public static final String[] EVENT_PROJECTION = new String[] {
Calendars._ID, // 0
Calendars.ACCOUNT_NAME, // 1
Calendars.CALENDAR_DISPLAY_NAME, // 2
Calendars.OWNER_ACCOUNT // 3
};
// The indices for the projection array above.
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
// Run query
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;
String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND ("
+ Calendars.ACCOUNT_TYPE + " = ?) AND ("
+ Calendars.OWNER_ACCOUNT + " = ?))";
String[] selectionArgs = new String[] {"sampleuser@gmail.com","com.google","sampleuser@gmail.com"};
// Submit the query and get a Cursor object back.
cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
此例 SQL 語法 = "SELECT Calendars._ID, Calendars.ACCOUNT_NAME, Calendars.CALENDAR_DISPLAY_NAME, Calendars.OWNER_ACCOUNT FROM Calendars WHERE Calendars.ACCOUNT_NAME = sampleuser@gmail.com AND Calendars.ACCOUNT_TYPE = com.google AND Calendars.OWNER_ACCOUNT = sampleuser@gmail.com";。
剩下的就跟操作 SQLite 一樣:
// Use the cursor to step through the returned records
while (cur.moveToNext()) {
long calID = 0;
String displayName = null;
String accountName = null;
String ownerName = null;
// Get the field values
calID = cur.getLong(PROJECTION_ID_INDEX);
displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
// Do something with the values...
...
}
此外,如果要更新某一筆資料,也一樣透過 URI 來進行操作,僅需在最後加上 Record ID,如 content://CONTENT_URI/DBTable/RecordID,在進行資料的操作:
long calID = 2;
ContentValues values = new ContentValues();
// The new display name for the calendar
values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar");
Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID);
int rows = getContentResolver().update(updateUri, values, null, null);
此例 SQL 約略為 "UPDATE Calendars SET Calendars.CALENDAR_DISPLAY_NAME = Trevor's Calendar WHERE Calendars._ID = 2";。
當然也免不了新增資料:
long calID = 3;
long startMillis = 0;
long endMillis = 0;
Calendar beginTime = Calendar.getInstance();
beginTime.set(2012, 9, 14, 7, 30);
startMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(2012, 9, 14, 8, 45);
endMillis = endTime.getTimeInMillis();
...
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(Events.DTSTART, startMillis);
values.put(Events.DTEND, endMillis);
values.put(Events.TITLE, "Jazzercise");
values.put(Events.DESCRIPTION, "Group workout");
values.put(Events.CALENDAR_ID, calID);
values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles");
Uri uri = cr.insert(Events.CONTENT_URI, values);
// get the event ID that is the last element in the Uri
long eventID = Long.parseLong(uri.getLastPathSegment());
//
// ... do something with event ID
//
//
以上在 Android Developers 官網都有非常詳細的描述,在此僅簡易筆記一下,更多的 Content Provider 請參閱 package android.provider,裡頭可以看到系統內建有哪些資料可以存取使用囉!