记录学习Android基础的心得04:Android五大存储&P2


前言

明年岂无年,心事恐蹉跎。-----苏轼《守岁》

接上文,总结ContentProvider内容提供器的用法。

五、ContentProvider内容提供器

1.简介

查阅API文档:
public abstract class ContentProvider
extends Object implements ComponentCallbacks2
封装数据并通过单个ContentResolver接口将其提供给应用程序。 只有在需要在多个应用程序之间共享数据时才需要内容提供者。 例如,联系人数据由多个应用程序使用,并且必须存储在内容提供者中。 如果您不需要在多个应用程序之间共享数据,则可以通过SQLiteDatabase直接使用数据库。

Android四大组件之一ContentProvider,是跟数据存取有关的组件。ContenProvider 作为中间接口,本身并不直接保存数据,而是通过SQLiteOpenHelper与SQLiteDatabase间接操作底层的数据库。完整的内容组件由三大部分组成:内容提供器ContentProvider,内容解析器ContentResolver、内容观察器ContentObserver。 如果某个应用租序通过ContentProvider暴露了自己的数据操作接口,那么不管该应用程序是否启动,其他应用程序都可通过该接口来操作来增删查改该应用程序的内部数据。

ContentProvider只是一个服务端的数据存取接口,我们需要在其基础上实现一个具体类,并重写几个关于数据库管理的方法。这几个方法就是暴露给客户端ContentResolver来调用的。
onCreate: 创建数据库并获得数据库连接。
query: 查询数据。
inser: 插入数据.
update: 更新数据。
delete:删除数据。
geType:获取数据类型。

2.ContentProvider使用方式

创建一个ContentProvider子类很简单,在指定目录,鼠标右键–new–other–Content Provider,出现如下配置界面:(再次说明:以鼠标右键创建的组件AS会自动在配置文件中配置)
在这里插入图片描述
其中唯一标识符URI Authority代表的是该应用程序所管理的数据地址,其他应用程序可以根据这个URI标识符来访问该接口中的方法。其余项见名知意。
定义一个常量类,保存常用的字符串,方便ContentResolver和ContentProvider使用:

public class UserInfoContent implements BaseColumns {
    // 这里的名称必须与AndroidManifest.xml里的android:authorities保持一致
    public static final String AUTHORITIES = "UserInfoProvider";
    // 表名
    public static final String TABLE_NAME = UserDBHelper.TABLE_NAME;
    // 访问该内容提供器的URI
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITIES + "/user");
    // 下面是该表的各个字段名称
    public static final String USER_NAME = "name";
    public static final String USER_PHONE = "phone";
    // 默认的排序方法
    public static final String DEFAULT_SORT_ORDER = "_id desc";
}

然后重写ContentProvider的几个方法:

public class UserInfoProvider extends ContentProvider {
    private final static String TAG = "UserInfoProvider";
    private UserDBHelper userDB; // 声明一个用户数据库的帮助器对象
    public static final int USER_INFO = 1; // Uri匹配时的代号
    public static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static { // 往Uri匹配器中添加指定的数据路径
        uriMatcher.addURI(UserInfoContent.AUTHORITIES, "/user", USER_INFO);
    }

    // 根据指定条件删除数据
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int count = 0;
        if (uriMatcher.match(uri) == USER_INFO) {
            // 获取SQLite数据库的写连接
            SQLiteDatabase db = userDB.getWritableDatabase();
            // 执行SQLite的删除操作,返回删除记录的数目
            count = db.delete(UserInfoContent.TABLE_NAME, selection, selectionArgs);
            db.close(); // 关闭SQLite数据库连接
        }
        return count;
    }

    // 插入数据
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Uri newUri = uri;
        if (uriMatcher.match(uri) == USER_INFO) {
            // 获取SQLite数据库的写连接
            SQLiteDatabase db = userDB.getWritableDatabase();
            // 向指定的表插入数据,返回记录的行号
            long rowId = db.insert(UserInfoContent.TABLE_NAME, null, values);
            if (rowId > 0) { // 判断插入是否执行成功
                // 如果添加成功,利用新记录的行号生成新的地址
                newUri = ContentUris.withAppendedId(UserInfoContent.CONTENT_URI, rowId);
                // 通知监听器,数据已经改变
                getContext().getContentResolver().notifyChange(newUri, null);
            }
            db.close(); // 关闭SQLite数据库连接
        }
        return uri;
    }

    // 创建ContentProvider时调用,可在此获取具体的数据库帮助器实例
    @Override
    public boolean onCreate() {
        userDB = UserDBHelper.getInstance(getContext(), 1);
        return false;
    }

    // 根据指定条件查询数据库
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        Cursor cursor = null;
        if (uriMatcher.match(uri) == USER_INFO) {
            // 获取SQLite数据库的读连接
            SQLiteDatabase db = userDB.getReadableDatabase();
            // 执行SQLite的查询操作
            cursor = db.query(UserInfoContent.TABLE_NAME,
                    projection, selection, selectionArgs, null, null, sortOrder);
            // 设置内容解析器的监听
            cursor.setNotificationUri(getContext().getContentResolver(), uri);
        }
        return cursor;
    }

    // 获取Uri数据的访问类型,暂未实现
    @Override
    public String getType(Uri uri) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    // 更新数据,暂未实现
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

其中UriMatcher 是一个操作Uri的工具类,用来确认每个ContentProvider能处理的Uri,以及确定每个方法中Uri所操作的数据。
有两个方法:

void addURI(String authority, String path, int code)
添加要匹配的URI,以及匹配此URI时返回的标识码。
int match(Uri uri)
尝试匹配URL中的路径,以此获得指定Uri对应的标识码。

这样就完成了ContentProvider服务端的数据库操作的封装。但是,我们自己的程序几乎不会用到ContentProvider来暴露自己的数据,反而是一些系统应用如短信,电话,照片等才需要暴露自己的数据。好家伙,合着就是一个工具人。。
在这里插入图片描述

3.ContentResolver使用方式

前面提到了利用ContentProvider实现服务端的数据操作封装,如果其他应用程序想使用此程序的内部数据操作方法,就要通过内容解析器 ContentResolver来访问。要获取ContentResolver对象,用Context对象的getContentResolver方法即可。
查阅API文档:
public abstract class ContentResolver
extends Object
不出意外的发现其提供了类似ContentProvider中操作数据的方法:
query: 查询数据。
inser: 插入数据.
update: 更新数据。
delete:删除数据。
geType:获取数据类型。
毫无疑问,使用ContentResolver这些一一对应的方法便能间接的调用ContentProvider中的对应的方法,其中ContentResolver中的方法参数Uri便是在创建ContentProvider时指定的唯一标识符。

4.ContentObserver使用方式

ContentObserver内容观察器采用主动查询方式,有查询就有数据,没有查询就没数据。如果要实时获取最新的数据,就要给ContentResolver注册一个观察器,ContentResolver中的数据一旦发生变化,观察器就会回调相应的方法。
查阅API文档:
public abstract class ContentObserver
extends Object
使用方法如下:
1.创建一个继承自ContentObserver的子类,然后重写其onChange方法实现回调。
2.为ContentResolver注册一个上面的观察器,即调用其
void registerContentObserver (Uri uri,boolean notifyForDescendants, ContentObserver observer)方法。
3.监听完毕,准备注销ContentObserver,可以调用
void unregisterContentObserver(ContentObserver observer)。

5.读取短信里的流量信息的DEMO

实现一个获取短信里的流量信息的DEMO
其中发送短信方法如下:

   // 短信发送事件
    private String SENT_SMS_ACTION = "SENT_SMS_ACTION";
    // 短信接收事件
    private String DELIVERED_SMS_ACTION = "DELIVERED_SMS_ACTION";
    
    public void sendSmsAuto(String phoneNumber, String message) {
        // 以下指定短信发送事件的详细信息
        Intent sentIntent = new Intent(SENT_SMS_ACTION);
        sentIntent.putExtra("phone", phoneNumber);
        sentIntent.putExtra("message", message);
        PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, sentIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        // 以下指定短信接收事件的详细信息
        Intent deliverIntent = new Intent(DELIVERED_SMS_ACTION);
        deliverIntent.putExtra("phone", phoneNumber);
        deliverIntent.putExtra("message", message);
        PendingIntent deliverPI = PendingIntent.getBroadcast(this, 1, deliverIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        // 获取默认的短信管理器
        SmsManager smsManager = SmsManager.getDefault();
        // 开始发送短信内容。要确保打开发送短信的完全权限,不是那种还需提示的不完整权限
        smsManager.sendTextMessage(phoneNumber, null, message, sentPI, deliverPI);
    }

定义一个内容观察器子类,重写onChange方法:

    // 定义一个短信获取的观察器
    private static class SmsGetObserver extends ContentObserver {
        private Context mContext; // 声明一个上下文对象
        public SmsGetObserver(Context context, Handler handler) {
            super(handler);
            mContext = context;
        }

        // 观察到短信的内容提供器发生变化时触发
        public void onChange(boolean selfChange) {
            String sender = "", content = "";
            // 构建一个查询短信的条件语句,这里使用移动号码测试,故而查找10086发来的短信
            String selection = String.format("address='10086' and date>%d", System.currentTimeMillis() - 1000 * 60 * 60);
            // 通过内容解析器获取符合条件的结果集游标
            Cursor cursor = mContext.getContentResolver().query(
                    mSmsUri, mSmsColumn, selection, null, " date desc");
            // 循环取出游标所指向的所有短信记录
            while (cursor.moveToNext()) {
                sender = cursor.getString(0);
                content = cursor.getString(1);
                break;
            }
            cursor.close(); // 关闭数据库游标
            mCheckResult = String.format("发送号码:%s\n短信内容:%s", sender, content);
            // 依次解析流量校准短信里面的各项流量数值,并拼接流量校准的结果字符串
            String flow = String.format("流量校准结果如下:\n\t总流量为:%s\n\t已使用:%s" +
                            "\n\t剩余:%s", findFlow(content, "总流量为", ","),
                    findFlow(content, "已使用", "MB"), findFlow(content, "剩余", "MB"));
            if (tv_check_flow != null) { // 离开该页面时就不再显示流量信息
                // 把流量校准结果显示到文本视图tv_check_flow上面
                tv_check_flow.setText(flow);
            }
            super.onChange(selfChange);
        }
    }

其中的findFlow方法:

// 解析流量校准短信里面的流量数值
    private static String findFlow(String sms, String begin, String end) {
        int begin_pos = sms.indexOf(begin);
        if (begin_pos < 0) {
            return "未获取";
        }
        String sub_sms = sms.substring(begin_pos);
        int end_pos = sub_sms.indexOf(end);
        if (end_pos < 0) {
            return "未获取";
        }
        if (end.equals(",")) {
            return sub_sms.substring(begin.length(), end_pos);
        } else {
            return sub_sms.substring(begin.length(), end_pos + end.length());
        }
    }

初始化内容观察器:

    private static TextView tv_check_flow;
    private static String mCheckResult;
    private Handler mHandler = new Handler(); // 声明一个处理器对象
    private SmsGetObserver mObserver; // 声明一个短信获取的观察器对象
    private static Uri mSmsUri; // 声明一个系统短信提供器的Uri对象
    private static String[] mSmsColumn; // 声明一个短信记录的字段数组

    // 初始化短信观察器
    private void initSmsObserver() {
        //mSmsUri = Uri.parse("content://sms/inbox");
        //Android5.0之后似乎无法单独观察某个信箱,只能监控整个短信
        mSmsUri = Uri.parse("content://sms");
        mSmsColumn = new String[]{"address", "body", "date"};
        // 创建一个短信观察器对象
        mObserver = new SmsGetObserver(this, mHandler);
        // 给指定Uri注册内容观察器,一旦Uri内部发生数据变化,就触发观察器的onChange方法
        getContentResolver().registerContentObserver(mSmsUri, true, mObserver);
    }

其他的页面控件绑定很简单,就不罗列了。
最后别忘了在配置文件里声明短信的权限:

    <!-- 短信 -->
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.WRITE_SMS" />

总结

复习了Android第二大组件ContentProvider,还有广播和服务两个组件,不知道哪一天才复习得到了。
又是一年结束时,时间是真的快,想回首却发现没有什么可以回首的事情,身边的同龄人大多数已经得到了牛逼的工作,我是真的羡慕。
在这里插入图片描述

希望新的一年,自己能努力向上,更加丰富知识与技能。也祝大家在新的一年阖家团圆,展翅高飞,一飞冲天,平步青云,涨薪加工资。

最后问大家一个问题:不论什么情况,有什么东西能永远伴随我们?

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖工人_0803号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值