本实例完成了一个手机通讯录,可以实现添加、删除、编辑、查看联系人等功能。本实例的代码来自《Android应用开发揭秘》一书,文章仅仅在此做具体分析。
本实例主要涉及的知识点:
1、SQLite数据库的使用以及Content Providers数据共享功能的实现。
2、ListView(ListActivity)使用。
3、长按菜单的使用。
4、系统短信、电话功能的使用。
本工程包含的子代码如下图:
首先分析数据库以及Content Providers的使用。二者的关系是,Content Providers实例的方法中包含了对于数据库的操作。Content Providers实例中要实现的方法有delete、insert、getType、query、update等等。
下面贴出ContactsProvider.java代码。
public class ContactsProvider extends ContentProvider
{
private static final String TAG= "ContactsProvider";
private DBHelper dbHelper;
private SQLiteDatabase contactsDB;
public static final String AUTHORITY = "com.yarin.android.provider.ContactsProvider";
public static final String CONTACTS_TABLE = "contacts";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"+CONTACTS_TABLE);
/*=====================================================*/
//下面是自定义的类型
public static final int CONTACTS = 1;
public static final int CONTACT_ID = 2;
private static final UriMatcher uriMatcher;
static
{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY,"contacts",CONTACTS);
//单独列
uriMatcher.addURI(AUTHORITY,"contacts/#",CONTACT_ID);
}
/*=====================================================*/
@Override
public boolean onCreate()
{
dbHelper = new DBHelper(getContext());
//执行创建数据库
contactsDB = dbHelper.getWritableDatabase();
return (contactsDB == null) ? false : true;
}
// 删除指定数据列
@Override
public int delete(Uri uri, String where, String[] selectionArgs)
{
int count;
switch (uriMatcher.match(uri))
{
case CONTACTS:
count = contactsDB.delete(CONTACTS_TABLE, where, selectionArgs);
break;
case CONTACT_ID:
String contactID = uri.getPathSegments().get(1);
count = contactsDB.delete(CONTACTS_TABLE,
ContactColumn._ID
+ "=" + contactID
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);//需要通知content provider
return count;
}
// URI类型转换
public String getType(Uri uri)
{
switch (uriMatcher.match(uri))
{
case CONTACTS:
return "vnd.android.cursor.dir/vnd.yarin.android.mycontacts";
case CONTACT_ID:
return "vnd.android.cursor.item/vnd.yarin.android.mycontacts";
//这里的路径都是固定的,一般如果是表,则必须为vnd.android.curosr.dir,如果是一项,则为后者。这两条的定义都在
//AndroidManifest.xml中需要注册,属于自定义类型。
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
// 插入数据
public Uri insert(Uri uri, ContentValues initialValues)
{
if (uriMatcher.match(uri) != CONTACTS)
{
throw new IllegalArgumentException("Unknown URI " + uri);
}
ContentValues values;
if (initialValues != null)
{
values = new ContentValues(initialValues);
Log.e(TAG + "insert", "initialValues is not null");
}
else
{
values = new ContentValues();
}
// 设置默认值
if (values.containsKey(ContactColumn.NAME) == false)
{
values.put(ContactColumn.NAME, "");
}
if (values.containsKey(ContactColumn.MOBILENUM) == false)
{
values.put(ContactColumn.MOBILENUM, "");
}
if (values.containsKey(ContactColumn.HOMENUM) == false)
{
values.put(ContactColumn.HOMENUM, "");
}
if (values.containsKey(ContactColumn.ADDRESS) == false)
{
values.put(ContactColumn.ADDRESS, "");
}
if (values.containsKey(ContactColumn.EMAIL) == false)
{
values.put(ContactColumn.EMAIL, "");
}
if (values.containsKey(ContactColumn.BLOG) == false)
{
values.put(ContactColumn.BLOG, "");
}
Log.e(TAG + "insert", values.toString());
long rowId = contactsDB.insert(CONTACTS_TABLE, null, values);
//这里contactsDB是SQLiteDatabase类型。说明ContentProvider中的方法都是使用到了SQLiteDatabase的方法,进行了封装。
if (rowId > 0)
{
Uri noteUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
Log.e(TAG + "insert", noteUri.toString());
return noteUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
// 查询数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
{
Log.e(TAG + ":query", " in Query");
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();//该类型专门有方法query,需要使用。
qb.setTables(CONTACTS_TABLE);
switch (uriMatcher.match(uri))
{
case CONTACT_ID:
qb.appendWhere(ContactColumn._ID + "=" + uri.getPathSegments().get(1));
break;
default:
break;
}
String orderBy;
if (TextUtils.isEmpty(sortOrder))
{
orderBy = ContactColumn._ID; //即默认情况下以ID进行排序
}
else
{
orderBy = sortOrder;
}
Cursor c = qb.query(contactsDB, projection, selection, selectionArgs, null, null, orderBy);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
// 更新数据库
public int update(Uri uri, ContentValues values, String where, String[] selectionArgs)
{
int count;
Log.e(TAG + "update", values.toString());
Log.e(TAG + "update", uri.toString());
Log.e(TAG + "update :match", "" + uriMatcher.match(uri));
switch (uriMatcher.match(uri))
{
case CONTACTS:
Log.e(TAG + "update", CONTACTS + "");
count = contactsDB.update(CONTACTS_TABLE, values, where, selectionArgs);
break;
case CONTACT_ID:
String contactID = uri.getPathSegments().get(1);
Log.e(TAG + "update", contactID + "");
count = contactsDB.update(CONTACTS_TABLE, values, ContactColumn._ID + "=" + contactID
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
对于关键点进行了注释。
可以看出,其中insert,update,delete等方法的实现需要用到SQLiteDatebase类型,也就是说其实Content Providers包含了对SQLite的操作,只不过封装了起来。而query的实现则需要用到SQLiteQueryBuilder类型。
每一种ContentProvider类型的实例化都需要定位信息,主要是Uri,片段如下:
public static final String AUTHORITY = "com.yarin.android.provider.ContactsProvider";
public static final String CONTACTS_TABLE = "contacts";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/"+CONTACTS_TABLE);
AUTHORITY前面的字段"com.yarin.android"要同包名相同。
ContactColumn.java则是定义了数据库需要存储哪些项目、项目类型、名称、编号等,以及查询的projection常量,便于后续查询使用。
public class ContactColumn implements BaseColumns
{
public ContactColumn()
{
}
//列名
public static final String NAME = "name"; //姓名
public static final String MOBILENUM = "mobileNumber";//移动电话
public static final String HOMENUM = "homeNumber"; //家庭电话
public static final String ADDRESS = "address"; //地址
public static final String EMAIL = "email"; //邮箱
public static final String BLOG = "blog"; //博客
//列 索引值
public static final int _ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public static final int MOBILENUM_COLUMN = 2;
public static final int HOMENUM_COLUMN = 3;
public static final int ADDRESS_COLUMN = 4;
public static final int EMAIL_COLUMN = 5;
public static final int BLOG_COLUMN = 6;
//查询结果
public static final String[] PROJECTION ={
_ID,
NAME,
MOBILENUM,
HOMENUM,
ADDRESS,
EMAIL,
BLOG,
};
}
接下来是MyContacts.java:
public class MyContacts extends ListActivity
{
private static final String TAG = "MyContacts";
private static final int AddContact_ID = Menu.FIRST;
private static final int EditContact_ID = Menu.FIRST+1;
private static final int DELEContact_ID = Menu.FIRST+2;
private static final int EXITContact_ID = Menu.FIRST+3;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(ContactsProvider.CONTENT_URI); //由此就将此intent同contact provider联系起来了
}
getListView().setOnCreateContextMenuListener(this); //绑定ContextMenu菜单,长按的时候会出现
getListView().setBackgroundResource(R.drawable.bg);
// Cursor cursor = managedQuery(getIntent().getData(), ContactColumn.PROJECTION, null, null,null);
Cursor cursor=getContentResolver().query(getIntent().getData(),ContactColumn.PROJECTION,null,null,null);
//这里可以使用managedQuery方法,也可以使用ContentProvider中的query方法。
//注册每个列表表示形式 :姓名 + 移动电话
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_2,
cursor,
new String[] {ContactColumn.NAME, ContactColumn.MOBILENUM },
new int[] { android.R.id.text1, android.R.id.text2 });
setListAdapter(adapter);
}
public boolean onCreateOptionsMenu(Menu menu)
{
super.onCreateOptionsMenu(menu);
//添加联系人
menu.add(0, AddContact_ID, 0, R.string.add_user)
.setShortcut('3', 'a')
.setIcon(R.drawable.add);
Intent intent = new Intent(null, getIntent().getData());
intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0,
new ComponentName(this, MyContacts.class), null, intent, 0, null);
//退出程序
menu.add(0, EXITContact_ID, 0, R.string.exit)
.setShortcut('4', 'd')
.setIcon(R.drawable.exit);
return true;
}
//处理菜单操作
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case AddContact_ID:
//添加联系人
startActivity(new Intent(Intent.ACTION_INSERT, getIntent().getData()));
return true;
case EXITContact_ID:
//退出程序
this.finish();
return true;
}
return super.onOptionsItemSelected(item);
}
public boolean onPrepareOptionsMenu(Menu menu)
{
super.onPrepareOptionsMenu(menu);
// final boolean haveItems = getListAdapter().getCount() > 0;
//
// if (haveItems)
// {
//
// Uri uri = ContentUris.withAppendedId(getIntent().getData(), getSelectedItemId());
//
// Intent[] specifics = new Intent[2];
// specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
// specifics[1] = new Intent(Intent.ACTION_VIEW, uri);
// MenuItem[] items = new MenuItem[2];
//
// //添加满足条件的菜单
// Intent intent = new Intent(null, uri);
// intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
// menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0, items);
//
// if (items[0] != null)
// {
// //编辑联系人
// items[0].setShortcut('1', 'e').setIcon(R.drawable.edituser).setTitle(R.string.editor_user);
// }
// if (items[1] != null)
// {
// //查看联系人
// items[1].setShortcut('2', 'f').setTitle(R.string.view_user).setIcon(R.drawable.viewuser);
// }
// }
// else
// {
// menu.removeGroup(Menu.CATEGORY_ALTERNATIVE);
// }
return true;
}
//动态菜单处理
//点击的默认操作也可以在这里处理
protected void onListItemClick(ListView l, View v, int position, long id)
{
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
// String action = getIntent().getAction();
// if ( Intent.ACTION_EDIT.equals(action) )
// {
// //编辑联系人
// startActivity(new Intent(Intent.ACTION_EDIT, uri));
// }
// else
// {
// //查看联系人
// startActivity(new Intent(Intent.ACTION_VIEW, uri));
// }
startActivity(new Intent(Intent.ACTION_VIEW,uri));
}
//长按触发的菜单
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
AdapterView.AdapterContextMenuInfo info;
try
{
info = (AdapterView.AdapterContextMenuInfo) menuInfo;
}
catch (ClassCastException e)
{
return;
}
//得到长按的数据项
Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
if (cursor == null)
{
return;
}
menu.setHeaderTitle(cursor.getString(1));
//添加删除菜单
menu.add(0, DELEContact_ID, 0, R.string.delete_user);
}
@Override
public boolean onContextItemSelected(MenuItem item) //当点击长按菜单时的操作
{
AdapterView.AdapterContextMenuInfo info;
try
{
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
}
catch (ClassCastException e)
{
return false;
}
switch (item.getItemId())
{
case DELEContact_ID:
{
//删除一条记录
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
getContentResolver().delete(noteUri, null, null);
return true;
}
}
return false;
}
}
这里就涉及到了对前面编写好的ContentProvider的使用。ContentProvider可能被实例化为好几种,那么如何区分到底操作的是哪一种呢?就是使用Uri来区分啦!仔细观察的话,可以看出ContentProvider的方法中,第一个参数几乎都是Uri。摘取如下:
// Cursor cursor = managedQuery(getIntent().getData(), ContactColumn.PROJECTION, null, null,null);
Cursor cursor=getContentResolver().query(getIntent().getData(),ContactColumn.PROJECTION,null,null,null);
//删除一条记录
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
getContentResolver().delete(noteUri, null, null);
该方法中还涉及到长按弹出菜单,为了能够使用,首先绑定该intent:
getListView().setOnCreateContextMenuListener(this); //绑定ContextMenu菜单,长按的时候会出现
然后写如下的方法:
//长按触发的菜单
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)
{
AdapterView.AdapterContextMenuInfo info;
try
{
info = (AdapterView.AdapterContextMenuInfo) menuInfo;
}
catch (ClassCastException e)
{
return;
}
//得到长按的数据项
Cursor cursor = (Cursor) getListAdapter().getItem(info.position);
if (cursor == null)
{
return;
}
menu.setHeaderTitle(cursor.getString(1));
//添加删除菜单
menu.add(0, DELEContact_ID, 0, R.string.delete_user);
}
@Override
public boolean onContextItemSelected(MenuItem item) //当点击长按菜单时的操作
{
AdapterView.AdapterContextMenuInfo info;
try
{
info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
}
catch (ClassCastException e)
{
return false;
}
switch (item.getItemId())
{
case DELEContact_ID:
{
//删除一条记录
Uri noteUri = ContentUris.withAppendedId(getIntent().getData(), info.id);
getContentResolver().delete(noteUri, null, null);
return true;
}
}
return false;
}
}
从该intent出发,还会进入两种界面:edit界面和view界面,分别进行联系人的编辑和查看,采用startActivity方法,并且通过action来进行区分。action都要在AndroidManifest.xml当中注册。