Android笔记2——通讯录实例注释及分析

本实例完成了一个手机通讯录,可以实现添加、删除、编辑、查看联系人等功能。本实例的代码来自《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当中注册。






  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值