上节说到当我们遇到成百上千的语句时查找数据库内容该怎么办,Android提供了一个更加好的办法,那就是Data Binding,它允许我们从数据库到试图仅仅少量的代码。为了演示Data Binding我们来修改上次的代码。
1. 首先来修改Event.java,让其继承ListActivity类代替Activity类。
2. 我们需要修改showEvent方法,代码如下:
private static int[] TO ={R.id.rowid,R.id.time,R.id.title};
private void showEvents(Cursor cursor){
//Set up data binding
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.item, cursor, FROM, TO);
setListAdapter(adapter);
}
你会发现代码比以前少了很多,我们为Cursor方法创建了SimpleCursorAdapter,然后告诉ListActivity来用这个新的adapter,这个adapter把View和数据库连接了一来。SimpleCursorAdapter中有五个参数:
context:是指现在所用的Activity。
Layout:试图(View)所在的资源,也就是路径。
Cursor:数据库连接用的cursor。
From:列表每个的名字,也就是数据是从哪里来的
To:这些数据是到那里的试图
3. 新增一个列表定义在layout/item.xml中,你会发现这里的row ID,time,title将会被TO引用。代码如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal"
android:padding="10sp">
<TextView
android:id="@+id/rowid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/rowidcolon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"
android:layout_toRightOf="@id/rowid"/>
<TextView
android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/rowidcolon"/>
<TextView
android:id="@+id/timecolon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=":"
android:layout_toRightOf="@id/time"/>
<TextView
android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textStyle="italic"
android:layout_toRightOf="@id/timecolon"/>
</RelativeLayout>
4. 修改layout/main.xml中的代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/empty"/>
</LinearLayout>
由于这个activity是继承ListActivity的,android寻找两个特别的layout文件,如果列表中已经存在文件,则会调用android:id/list来展示view,否则会调用android:id/empty来显示No events.
5. 添加strings.xml文件中的内容如下:
<string name="app_name">Events</string>
<string name="empty">No events!</string>
这时你就会看见不一样的效果了,新的问题又出现了,当用户选择一条数据,当他选择想看其中的内容的时候或者想要删除这条数据,将要怎么办呢?但是没有什么应用程序是来进行这样的操作的,所以我们需要用到ContentProvider。
在Android安全模型中,一个应用程序编写的文件无法被其他任何应用程序所读写。每个程序都有自己的Linux用户ID和数据目录(data/data/包名),以及其受保护的内存空间。Android程序可通过下面两种方式进行彼此间的通信。
a. IPC(Inter-Process Communication,进程间通信):一个进程使用AIDL(接口定义语言)和Ibinder接口声明一个任意的API。调用该API时,将在进程间安全且有效地对参数进行编组,这项先进技术用于对后台Service线程进行远程过程调用。
b. ContentProvider:进程中系统中将它们本身注册为某些数据类型的提供者。请求信息时,Android就会通过一个固定的API调用这些进程,以它们认为合适的方式查询或修改内容。
任何信息在被ContentProvider处理时,都会通过URI格式为 content://authority/path/id 。
其中的参数为:
content://是标准要求的前缀;
authority:是提供者的名称,建议你使用完全限定包名称,避免出现名称冲突;
path:是提供者内部的一个虚拟目录,用于标识被请求的数据类型;
id:是被请求的特定记录的主键,要请求获得具有特定类型的所有记录,可以省略此参数以及后面的斜杠。
6.所以我们需要多增加两个常量到Constants.java中。
7. 改变主程序中onCreate()代码如下:
package com.zy.events;
import static com.zy.events.Constants.TIME;
import static com.zy.events.Constants.TITLE;
import static android.provider.BaseColumns._ID;
import static com.zy.events.Constants.CONTENT_URI;
import android.app.ListActivity;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.widget.SimpleCursorAdapter;
public class Events extends ListActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
addEvent("Hell,Android!");
Cursor cursor = getEvents();
showEvents(cursor);
}
private void addEvent(String string){
//Insert a new record into the Events data source.
//You would do something similar for delete and update.
ContentValues values = new ContentValues();
values.put(TIME, System.currentTimeMillis());
values.put(TITLE, string);
getContentResolver().insert(CONTENT_URI, values);
}
private static String[] FROM ={_ID,TIME,TITLE};
private static String ORDER_BY = TIME + " DESC";
private Cursor getEvents(){
//Perform a managed query. The Activity will handle cloding
//and re-querying the cursor when needed.
return managedQuery(CONTENT_URI, FROM, null, null, ORDER_BY);
}
private static int[] TO ={R.id.rowid,R.id.time,R.id.title};
private void showEvents(Cursor cursor){
//Set up data binding
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.item, cursor, FROM, TO);
setListAdapter(adapter);
}
}
8. 新增EventsProvider继承于ContentProvider:
ContentProvider是一个类似于Activity的高级对象,需要向系统进行声明。因此,实现ContentProvider的第一步是将其添加到AndroidManifest.xml文件中的<activity>标签之前(作为<application>的子标签)。
<provider android:name=”EventsProvider”
android:authorities=”com.zy.events” />
android:name是类名,android:authorities是在内容URI中使用的字符串。
EventsProvider.java代码如下:
package com.zy.events;
import static android.provider.BaseColumns._ID;
import static com.zy.events.Constants.AUTHORITY;
import static com.zy.events.Constants.CONTENT_URI;
import static com.zy.events.Constants.TABLE_NAME;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
public class EventsProvider extends ContentProvider {
private static final int EVENTS = 1;
private static final int EVENTS_ID = 2;
/** The MIME type of a directory of events */
private static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.example.event";
/** The MIME type of a single event */
private static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.example.event";
private EventsData events;
private UriMatcher uriMatcher;
// ...
@Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "events", EVENTS);
uriMatcher.addURI(AUTHORITY, "events/#", EVENTS_ID);
events = new EventsData(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy) {
if (uriMatcher.match(uri) == EVENTS_ID) {
long id = Long.parseLong(uri.getPathSegments().get(1));
selection = appendRowId(selection, id);
}
// Get the database and run the query
SQLiteDatabase db = events.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, projection, selection,
selectionArgs, null, null, orderBy);
// Tell the cursor what uri to watch, so it knows when its
// source data changes
cursor.setNotificationUri(getContext().getContentResolver(),
uri);
return cursor;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case EVENTS:
return CONTENT_TYPE;
case EVENTS_ID:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = events.getWritableDatabase();
// Validate the requested uri
if (uriMatcher.match(uri) != EVENTS) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Insert into database
long id = db.insertOrThrow(TABLE_NAME, null, values);
// Notify any watchers of the change
Uri newUri = ContentUris.withAppendedId(CONTENT_URI, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}
@Override
public int delete(Uri uri, String selection,
String[] selectionArgs) {
SQLiteDatabase db = events.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case EVENTS:
count = db.delete(TABLE_NAME, selection, selectionArgs);
break;
case EVENTS_ID:
long id = Long.parseLong(uri.getPathSegments().get(1));
count = db.delete(TABLE_NAME, appendRowId(selection, id),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify any watchers of the change
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
SQLiteDatabase db = events.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case EVENTS:
count = db.update(TABLE_NAME, values, selection,
selectionArgs);
break;
case EVENTS_ID:
long id = Long.parseLong(uri.getPathSegments().get(1));
count = db.update(TABLE_NAME, values, appendRowId(
selection, id), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify any watchers of the change
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
/** Append an id test to a SQL selection expression */
private String appendRowId(String selection, long id) {
return _ID + "=" + id
+ (!TextUtils.isEmpty(selection)
? " AND (" + selection + ')'
: "");
}
}
这个显示效果如上图,现在我们已经有了事件存储的框架了,这样就可以被其他的应用程序所用,甚至被其他的程序员来进行开发。