首先,还是展示一下部分目录结构:
在节日短信送祝福的功能实现方面,为了能够方便直观展示实现过程,小编我以Java文件为基础,一个一个来展示,免得到时候这个java文件写点,一下又跳到另外一个java文件写点,毕竟这不像教学视频那样直观。
因为在功能方面涉及到了显示已经发送短信的历史记录,那么,毫无疑问,要用到数据库,但是在定义SQLiteOpenHelper之前先定义一个”已经发送的短信的实体类”(SendedMsg):
SendedMsg.java
public class SendedMsg { private int id; private String content; private String numbers;//发送的联系人号码(可能有多个联系人的号码,拼接成一个String) private String names;//发送的联系人名单(可能有多个联系人,拼接成一个String) private String festivalName; private Date date; private String dateStr;//主要为了方便 private DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm"); public static final String TABLE_NAME="tb_sended_msg"; public static final String COLUMN_CONTENT="content"; public static final String COLUMN_NUMBERS="numbers"; public static final String COLUMN_NAMES="names"; public static final String COLUMN_FESTIVAL_NAME="festival_name"; public static final String COLUMN_DATE="date_str"; public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getNumbers() { return numbers; } public void setNumbers(String numbers) { this.numbers = numbers; } public String getNames() { return names; } public void setNames(String names) { this.names = names; } public String getFestivalName() { return festivalName; } public void setFestivalName(String festivalName) { this.festivalName = festivalName; } public String getDataStr() { dateStr=df.format(date); return dateStr; } }
可以注意到,在实体类中还定义了一些常量,这是为了避免在对数据库进行操作时出错,可以直接引用。
SmsDBOpenHelper.java(单例模式)
public class SmsDBOpenHelper extends SQLiteOpenHelper{ private static final String DB_NAME="sms.db"; private static final int DB_VERSION=1; private static SmsDBOpenHelper mHelper; private SmsDBOpenHelper(Context context) { super(context.getApplicationContext(), DB_NAME, null, DB_VERSION); } //传入的context有可能是一个Activity //所以在构造方法中用context.getApplicationContext()尽量得到Application的context //避免造成内存泄露的问题 public static SmsDBOpenHelper getInstance(Context context) { if(mHelper==null) { synchronized (SmsDBOpenHelper.class) { if(mHelper==null) { mHelper=new SmsDBOpenHelper(context); } } } return mHelper; } @Override public void onCreate(SQLiteDatabase db) { String sql="create table "+ SendedMsg.TABLE_NAME+" ( "+ "_id integer primary key autoincrement, "+ SendedMsg.COLUMN_DATE+" integer, "+ SendedMsg.COLUMN_FESTIVAL_NAME+" text,"+ SendedMsg.COLUMN_CONTENT+" text,"+ SendedMsg.COLUMN_NAMES+" text,"+ SendedMsg.COLUMN_NUMBERS+" text )"; db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
注意:在建表的时候,有一个”_id”的主键,这是因为在后面会讲到一个用于展示短信历史记录的Fragment,里面会涉及CursorAdapter,而CursorAdapter里面会用到一个cursor,该cursor必须要有个名为 _id
的列
在私有构造方法中的context.getApplicationContext()这点是值得注意与学习的。
接着是自定义的ContentProvider.java
public class SmsProvider extends ContentProvider{ private static final String AUTHORITY="com.just.sms.provider.SmsProvider"; public static final Uri URI_SMS_ALL=Uri.parse("content://"+AUTHORITY+"/sms"); private static UriMatcher mMatcher; private static final int SMS_ALL=0;//表示访问表中的所有数据 private static final int SMS_ONE=1;//表示访问表中的单条数据 //在静态代码块中完成mMatcher的初始化及uri的添加 static { mMatcher=new UriMatcher(UriMatcher.NO_MATCH); //addURI接收三个参数,可以分别把权限、路径和一个自定义代码传进去 //*:表示匹配任意长度的任意字符;#:表示匹配任意长度的数字 mMatcher.addURI(AUTHORITY,"sms",SMS_ALL); mMatcher.addURI(AUTHORITY,"sms/#",SMS_ONE); } private SmsDBOpenHelper mHelper; private SQLiteDatabase mDB; @Override public boolean onCreate() { mHelper=SmsDBOpenHelper.getInstance(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { int match=mMatcher.match(uri); switch (match) { case SMS_ALL: break; case SMS_ONE://本案例用不到,但展示一下代码逻辑 long id=ContentUris.parseId(uri);//从路径中中获取id selection="_id = ?"; selectionArgs=new String[]{String.valueOf(id)}; break; default: throw new IllegalArgumentException("Wrong URI:"+uri.toString()); } mDB=mHelper.getReadableDatabase(); Cursor cursor=mDB.query(SendedMsg.TABLE_NAME,projection,selection,selectionArgs,null,null,sortOrder); //用来在后台检测数据的变化,如果有变化就会有返回(因为在SmsHistoryFragment中使用了Loader) cursor.setNotificationUri(getContext().getContentResolver(),URI_SMS_ALL); return cursor; } @Override public Uri insert(Uri uri, ContentValues values) { int match=mMatcher.match(uri); if(match!=SMS_ALL) { throw new IllegalArgumentException("Wrong URI:"+uri.toString()); } mDB=mHelper.getWritableDatabase(); //第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null 即可 long rowId=mDB.insert(SendedMsg.TABLE_NAME,null,values); if(rowId>0) {//如果添加数据成功 notifyDataSetChanged(); return ContentUris.withAppendedId(uri,rowId);//为传入的uri加上id } return uri; } private void notifyDataSetChanged() { getContext().getContentResolver().notifyChange(URI_SMS_ALL,null);//通知监听器关于数据更新的信息 } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { return 0; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; } @Override public String getType(Uri uri) { return null; } }
一定要记得在AndroidMainfest.xml中注册
<provider android:name=".db.SmsProvider" android:authorities="com.just.sms.provider. SmsProvider"> </provider>
在ContentProvider中呢,需要注意两行代码(是不可或缺的)。
一个是query方法中的:
cursor.setNotificationUri(getContext().getContentResolver(),URI_SMS_ALL);
另一个是notifyDataSetChanged方法中的:
getContext().getContentResolver().notifyChange(URI_SMS_ALL,null);
这两行代码的作用可以先暂时搁置一下,等到后面讲SmsHistoryFragment的时候再联系起来说明一下。