分类: LINUX
2009-03-25 22:56:42
1. 何謂Android的嫡系組件
Android有4項一等公民(或稱為嫡系親屬),包括:Activity、ContentProvider、IntentReceiver與Service。它們都必須宣告於AndroidManifest.xml檔案裡,如下:
package="com.misoo.SQ03">
android:name="android.permission.INTERNET">
<provider android:name="DataProvider"
android:authorities="com.misoo.provider.SQ03">
provider>
<activity android:name=".ac01" android:label="@string/app_name">
activity>
<activity android:name=".DispActivity" android:label="DispActivity">
activity>
這讓Android知道我們城市裡定義了多少個嫡系組件類別;Android可以在啟動時就將它們執行起來,成為共享的(Shared)服務組件。這些嫡系服務組件間的溝通,通常是透過「意圖」(Intent)物件來請Android轉達給對方,Android則會依據意圖而找出最佳的配對。配對成功,就展開相互的溝通與服務了。
2. 什麼是ContentProvider嫡系組件
---- 以SQLite為例
在Android裡,SQLite資料庫是最典型的ContentProvider,負責儲存各式各樣的內容。除了資料庫之外,還有許多其他種類的ContentProvider。在這裡並不是要介紹這些ContentProvider,而是要透過SQLite認識ContentProvider介面,然後將舶來Linter組件,配上這種ContentProvider介面,讓它搖身一變成為Android的嫡系組件。
2.1 一般(即非嫡系)SQLite的範例
沒有透過ContentProvider介面來使用SQLite,就是對SQLite的「非嫡系」用法。此時,應用程式透過JDBC介面和SQL語句來與SQLite溝通,以存取資料庫裡的內容。先認識這種傳統用法。此範例將從SQLite讀取資料。首先建立一個程式專案,其含有兩個Java程式檔:ac01.java和DataProvider.java。其中,ac01.java 是典型的Activity類別,負責UI畫面的顯示工作,而DataProvider則負責與SQLite溝通。其詳細程式碼為:
/* ----- ac01.java 程式碼 ------*/
package com.misoo.pklx;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class ac01 extends ListActivity {
private static final String[] PROJECTION = new String[] { "stud_no", "stud_name" };
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DataProvider dp = new DataProvider(this);
Cursor cur = dp.query(PROJECTION, null, null, null);
ArrayList
= new ArrayList
Map
cur.moveToFirst();
while(!cur.isAfterLast()) {
item = new HashMap
item.put("c1", cur.getString(0) + ", " + cur.getString(1));
coll.add(item);
cur.moveToNext();
}
dp.close();
this.setListAdapter(new SimpleAdapter(this, coll,
android.R.layout.simple_list_item_1, new String[] { "c1" },
new int[] {android.R.id.text1}));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
finish();
}}
指令:
DataProvider dp = new DataProvider(this);
這和一般類別之用法是一樣的。ac01物件指名要誕生一個DataProvider的物件。然後呼叫它,如下指令:
Cursor cur = dp.query(PROJECTION, null, null, null);
這要求SQLite從資料庫查詢出某些資料。詳細的DataProvider.java程式碼如下:
/* ----- DataProvider.java 程式碼 ------*/
package com.misoo.pklx;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class DataProvider {
private static final String DATABASE_NAME = "StudDB";
private static final String TABLE_NAME = "Student";
private final int DB_MODE = Context.MODE_PRIVATE;
private SQLiteDatabase db=null;
public DataProvider(Context ctx) {
try { db = ctx.openOrCreateDatabase(DATABASE_NAME, DB_MODE, null); }
catch (Exception e) { Log.e("ERROR", e.toString()); return; }
try { db.execSQL("drop table "+ TABLE_NAME); }
catch (Exception e) { Log.e("ERROR", e.toString()); }
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + "stud_no" + " TEXT,"
+ "stud_name" + " TEXT" + ");");
String sql_1 = "insert into "+ TABLE_NAME +
" (stud_no, stud_name) values('S101', 'Lily');";
String sql_2 = "insert into " + TABLE_NAME +
" (stud_no, stud_name) values('S102', 'Linda');";
String sql_3 = "insert into " + TABLE_NAME +
" (stud_no, stud_name) values('S103', 'Bruce');";
try { db.execSQL(sql_1); db.execSQL(sql_2); db.execSQL(sql_3); }
catch (SQLException e) { Log.e("ERROR", e.toString()); return; }
}
public Cursor query(String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor cur = db.query(TABLE_NAME, projection, null, null, null, null, null);
return cur;
}
public void close(){ db.close(); }
}
這種用法屬於非嫡系的用法:在ac01.java程式碼裡,其指令:
DataProvider dp = new DataProvider(this);
明確指定由DataProvider物件來提供服務。反之,嫡系用法則是透過意圖(Intent)來請Android代為配對,進而找出適當的ContentProvider物件來為aco1物件提供服務。
2.2 嫡系SQLite的範例
剛才的範例裡,我們直接使用DataProvider類別的介面來與SQLite溝通。本節的範例,將替DataProvider配上ContentProvider介面,讓ac01物件能透過ContentProvider新介面來溝通。此範例也是從SQLite資料庫讀取3筆資料;請仔細看看其程式碼的微妙差異:
/* ----- ac01.java 程式碼 ------*/
package com.misoo.pkrr;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import android.app.ListActivity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class ac01 extends ListActivity {
public static int g_variable;
public static final String AUTHORITY = "com.misoo.provider.rx09-02";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/Student");
private static final String[] PROJECTION
= new String[]{ "stud_no", "stud_name"};
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
if (intent.getData() == null) intent.setData(CONTENT_URI);
Cursor cur = getContentResolver().query(getIntent().getData(),
PROJECTION, null, null, null);
ArrayList
Map
cur.moveToFirst();
while (!cur.isAfterLast()) {
item = new HashMap
item.put("c1", cur.getString(0) + ", " + cur.getString(1));
coll.add(item);
cur.moveToNext();
}
this.setListAdapter(new SimpleAdapter(this, coll,
android.R.layout.simple_list_item_1, new String[] { "c1" },
new int[] { android.R.id.text1 }));
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) { finish();}
}
指令:
Cursor cur = getContentResolver().query(getIntent().getData(),
PROJECTION, null, null, null);
要求Android代為尋找適合的ContentProvider來提供服務,並不刻意指定由DataProvider物件來擔任。只要合乎ConentProvider介面,且符合意圖條件的物件皆可以來為ac01物件提供服務。於是,ac01程式碼就不再直接呼叫DataProvider類別的函數了,而是呼叫ContentProvider介面所提供的函數。再來仔細看看DataProvider類別與ContentProvider介面的搭配情形:
/* ----- DataProvider.java 程式碼 ------*/
package com.misoo.pkrr;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;
public class DataProvider extends ContentProvider {
private static final String DATABASE_NAME = "StudNewDB";
private static final int DATABASE_VERSION = 2;
private static final String TABLE_NAME = "StudTable";
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); }
@Override public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + " (" + "stud_no"
+ " TEXT," + "stud_name" + " TEXT" + ");");
String sql_1 = "insert into " + TABLE_NAME
+ " (stud_no, stud_name) values('S1001', 'Pam');";
String sql_2 = "insert into " + TABLE_NAME
+ " (stud_no, stud_name) values('S1002', 'Steve');";
String sql_3 = "insert into " + TABLE_NAME
+ " (stud_no, stud_name) values('S1003', 'John');";
try { db.execSQL(sql_1); db.execSQL(sql_2); db.execSQL(sql_3); }
catch (SQLException e) { Log.e("ERROR", e.toString()); }
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
// ---------------------------------------------------------------------------------
private DatabaseHelper mOpenHelper;
@Override public boolean onCreate() {
mOpenHelper = new DatabaseHelper(getContext()); return true; }
@Override public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = db.query(TABLE_NAME, projection, null, null, null, null, null);
return c;
}
@Override public String getType(Uri uri) { return null; }
@Override public Uri insert(Uri uri, ContentValues initialValues) { return uri; }
@Override public int delete(Uri uri, String where, String[] whereArgs) { return 0; }
@Override public int update(Uri uri, ContentValues values, String where,
String[] whereArgs)
{ return 0; }
}
類別定義:
public class DataProvider extends ContentProvider {
// …..…..
}
DataProvider類別繼承ContentProvider父類別,也繼承了它的介面定義。ContentProvider介面定義了多個函數,主要包括:
l query()函數---- 它查詢出合乎某條件的資料。
l insert()函數---- 它將存入一筆新資料。
l delete()函數---- 它刪除合乎某條件的資料。
l update()函數---- 更新某些筆資料的內容。
在這個DataProvider類別裡,撰寫了query()函數內的指令,來實現query()介面,這個query()函數實際呼叫SQLite資料庫的功能。也就是說,ac01等應用程式透過ContentProvider介面間接呼叫到DataProvider的query()函數,然後此query()函數才使用SQLite的服務。
由於此範例的DataProvider已經是ContentProvider嫡系身份了,必須由Android來啟動它,而不是有ac01等應用程式來直接啟動它,所以必須在AndroidManifest.xml文檔裡給Android一些指示,如下:
/* ----- AndroidManifest.xml 文檔 ------*/
package="com.misoo.pkrr"
android:versionCode="1"
android:versionName="
android:label="@string/app_name">
android:authorities="com.misoo.provider.rx09-02">
這特別說明DataProvider是一個ContentProvider,於是Android就會來啟動它。
(by Misoo團隊)