Android开发学习(内容提供者)

安卓组件之内容提供者

1、什么是内容提供者

内容提供者,我们从字面上认识,就是一个提供内容的东西!

这东西常用吗?用得比较少,有一个项目用上了,就是蓝牙电话。为什么会用上呢?因为需要拿到联系人的数据!

内容提供者,就是向第三方暴露自己的数据库的!目前来说,只有google的短信/电话是这么做的,其他应用基本上不会这么做!所以呢,使用场景也少了!比如说微信/支付宝,在第一次使用的时候会询问你是否同意让它读取你的联系人/通讯录,就是通过内容提供者来读取的。

2、为什么要使用内容提供者呢?

我们可以看出,数据库的权限是:
在这里插入图片描述
熟悉Linux的同学都知道,Linux的权限是这样分的,(-rw)前面个是用户的权限,(-rw)中间三个是同一个用户组的权限,(—)后面三个是其他用户的权限。

在android里面,每一个应用可以看做是一个应用,那么我们可以认为,其他应用是没办法访问到这个数据库的。有些情况下,需要访问别人的数据库,这个时候就需要我们的内容提供者了!

比如说今日头条需要访问淘宝的内容,根据淘宝的用户访问习惯来给使用今日头条的用户推荐定向广告,比如说我们开发一个蓝牙电话,需要向手机的联系人这个应用拿到手机的所有联系人,etc.

内容提供者,就是给别人暴露我们本应用里的数据库的,至于想暴露那些数据库,怎么样的其他应用才能访问,就看后面的内容吧!

3、内容提供者

创建内容提者的步骤:

第一步:编写一个类 继承自内容提供者(ContentProvider)这个例子的目的是让大家知道这个内容提供者的工作流程就够了!实际的使用我们后面再详细说明吧!

四大组件都是要继承自XXX的,总结到了吗?为什么呢?因为它要由系统去创建,生命周期由系统去管理呀!

package com.sunofbeaches.providerdemo.provider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

/**
 * Created by TrillGates on 18/7/15.
 * God bless my code!
 */
public class StudentScoreProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
        return 0;
    }
}

第二步:注册一下呗!

我们四大组件都要注册,这个知道吧!所以我们需要在AndroidManifest.xml里进行注册!
在这里插入图片描述
我们可以发现,有两个参数必填的哦!name我们知道,是这填写空上类的全路径名称。

authorities是什么呢?其实就是令牌,口令,暗号!对得上,匹配得了的,才有权限来操作数据库。

一般来说,这个我们填写报名就OK了!如下:
在这里插入图片描述
provider最好加多一项:

android:exported=”true”

我们内容提供者的代码怎么写呢?

我们先暂时写一个查询的代码:

package com.sunofbeaches.providerdemo.provider;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;


import com.sunofbeaches.providerdemo.db.StudentScoreDBHelper;

/**
 * Created by TrillGates on 18/7/15.
 * God bless my code!
 */
public class StudentScoreProvider extends ContentProvider {


    StudentScoreDBHelper mStudentScoreDBHelper;
    //定义一个Uri匹配器,参数表示不匹配的时候返回什么值,这里返回的是-1,也就是说-1表示不匹配
    private static UriMatcher mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    //这个表示匹配
    private static final int CODE_MATCH = 1;

    static {
        //添加匹配规则,前面是authority,这个其实就是我们在配置文件里配置的那个认证字符串
        //第二个参数是path,一般表示表名(可以为*或者#,表示所有的字母或者数字)
        //第三个表示the code that is returned when a URI is matched,也就是说规则匹配则会返回后面那个code
        // 否则返回前面我们指定的默认 UriMatcher.NO_MATCH
        mUriMatcher.addURI("com.sunofbeaches.providerdemo","sob_score",CODE_MATCH);
    }

    @Override
    public boolean onCreate() {
        //注意,这里getContext()
        //Only available once
        //     * {@link #onCreate} has been called
        //里面的注释是:只有当onCreate方法被调用以后,getContext这个方法才可用。
        mStudentScoreDBHelper = new StudentScoreDBHelper(getContext());
        return false;
    }


    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        int match = mUriMatcher.match(uri);
        if (match==CODE_MATCH) {
            SQLiteDatabase readableDatabase = mStudentScoreDBHelper.getReadableDatabase();
            return readableDatabase.query(StudentScoreDBHelper.TABLE_NAME, strings, s, strings1, s1, null, null);
        }else{
            throw new IllegalArgumentException("Uri not matching.");
        }
    }


    @Override
    public String getType( Uri uri) {
        return null;
    }

    
    @Override
    public Uri insert( Uri uri,  ContentValues contentValues) {
        return null;
    }

    @Override
    public int delete( Uri uri,  String s,  String[] strings) {
        return 0;
    }

    @Override
    public int update( Uri uri,  ContentValues contentValues,  String s,  String[] strings) {
        return 0;
    }
}

相关的细节已经在注释里了!

接下来,我们要写另外一个应用,通过内容提供者的方式来获取到当前应用的数据了。

package com.sunofbeaches.providerdemoteacher;

import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void getScore(View view) {
        ContentResolver contentResolver = this.getContentResolver();
        Uri uri = Uri.parse("content://com.sunofbeaches.providerdemo/sob_score");
        Cursor cursor = contentResolver.query(uri, null, null, null, null);
        while (cursor.moveToNext()) {
            Log.d(TAG, "id is -- > " + cursor.getInt(0));
            Log.d(TAG, "name is -- > " + cursor.getString(1));
            Log.d(TAG, "Chinese is -- > " + cursor.getInt(2));
            Log.d(TAG, "Math is -- > " + cursor.getInt(3));
            Log.d(TAG, "English is -- > " + cursor.getInt(4));
        }
        cursor.close();
    }
}

执行结果怎么样的呢?
在这里插入图片描述
OK,到这里的话,我们成功地读取到了另外一个应用的数据库内容了。

我们稍微总结一下:

首先是有一个数据库,但是这个数据库是别人家的呀,但是这个别人家的数据库有一个内容提供者呢!

我们只要知道对应的认证就可以读取到数据了!

所以,接下来我们就要仿今日头条,仿腾讯QQ,微信这些应用获取到手机号码,也就是通讯录的内容。

4、内容观察者

https://blog.csdn.net/qq_39826207/article/details/128204996

5、ContentProvider(内容提供者)的应用

动态获取权限(示例)

 private void checkPermission() {
        //先判断有没有权限
        int i = checkSelfPermission("android.permission.READ_EXTERNAL_STORAGE");
        if(i != PackageManager.PERMISSION_GRANTED){
            //没有权限
            // 申请权限
            // 定义需要申请的权限数组
            String[] permission = new String[] {
                    Manifest.permission.READ_EXTERNAL_STORAGE
            };
            // 定义这次申请权限的唯一请求编码
            int requestCode = 2023;
            // 申请权限
            requestPermissions(permission, requestCode);
        }
    }


    /**
     * 申请权限的回调方法
     * @param requestCode 申请权限时的请求码
     * @param permissions   所有权限的数组
     * @param grantResults  返回获取权限的状态码数组(-1:获取失败----0:获取成功)
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if(requestCode == 2023){
            Log.d(TAG, "grantResults: "+grantResults[0]);
            Log.d(TAG, "grantResults: "+grantResults.length);
            if(grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                //有权限
                Toast.makeText(this, "已经获得所有权限!", Toast.LENGTH_SHORT).show();
            }else {
                //没有权限
                //根据交互数据
                Toast.makeText(this, "没有获得权限!,程序将退出!", Toast.LENGTH_SHORT).show();
            }
        }
    }

前面代码中我有所说道,一般情况下我们不会自定义内容提供者,谁会白白的访问一个没有任何用途的app去操纵数据库中的数据,相反我们往往会使用系统提供的内容提供者,比如说日历联系人短信等对这些数据库中的表进行数据的操作。通过前面使用自定义的内容提供者,接下里我们可以使用系统内容提供者,使用步骤相似。
在下面使用系统提供的内容提供者,操作步骤无非就是看上层应用源码,Manifest中找authorities,Provider类中找url和path,注册权限,对表进行操作 !因为下面的案例都需要动态获取权限,我没有贴相应权限的代码,关于权限的动态获取,请转到Android6.0+应用权限

案例一 获取日历事件,向日历中添加提醒事件

日历内容提供者的相关信息,都可以以从android开发平台查到。
在这里插入图片描述
常用表:红线标出
在这里插入图片描述

  1. Calendars表
 ContentResolver contentResolver = getContentResolver();
    // 1.获取日历相关的Uri,content://authorities/path,从android日历源码Manifest中查找authorities
    //Uri uri = Uri.parse("content://" + CalendarContract.AUTHORITY + "/calendars");
    Uri uri = CalendarContract.Calendars.CONTENT_URI;
    Cursor cursor = contentResolver.query(uri,null,null,null,null);
    String[] columnNames = cursor.getColumnNames();
    while(cursor.moveToNext()) {
        Log.d(TAG,"=============================================");
        for(String columnName : columnNames) {
            Log.d(TAG,"field --- > " + columnName + "value -- > " + cursor.getString(cursor.getColumnIndex(columnName)));
        }
        Log.d(TAG,"=============================================");
    }
    cursor.close();

查询到的都是日历表相关的信息,比如日历所属id,日历账户名称…

  1. Events表

获取日历中相关事件,这里我们查询所有,只需替换上文Calendars代码中uri
Uri uri = CalendarContract.Events.CONTENT_URI; 查询事件结果如下:

D/MainActivity: =============================================
D/MainActivity: field --- > originalAllDay;  value -- > null
D/MainActivity: field --- > account_type;  value -- > com.android.huawei
D/MainActivity: field --- > exrule;  value -- > null
D/MainActivity: field --- > mutators;  value -- > com.android.calendar
D/MainActivity: field --- > originalInstanceTime;  value -- > null
D/MainActivity: field --- > allDay;  value -- > 0
D/MainActivity: field --- > allowedReminders;  value -- > 0,1
D/MainActivity: field --- > rrule;  value -- > null
D/MainActivity: field --- > location_longitude;  value -- > c37d19942d3d
D/MainActivity: field --- > canOrganizerRespond;  value -- > 1
D/MainActivity: field --- > lastDate;  value -- > 1582896600000
D/MainActivity: field --- > visible;  value -- > 1
D/MainActivity: field --- > calendar_id;  value -- > 1
D/MainActivity: field --- > hasExtendedProperties;  value -- > 0
D/MainActivity: field --- > calendar_access_level;  value -- > 700
D/MainActivity: field --- > selfAttendeeStatus;  value -- > 0
D/MainActivity: field --- > version;  value -- > 2
D/MainActivity: field --- > allowedAvailability;  value -- > 0,1
D/MainActivity: field --- > eventColor_index;  value -- > null
D/MainActivity: field --- > isOrganizer;  value -- > 1
D/MainActivity: field --- > _sync_id;  value -- > null
D/MainActivity: field --- > event_image_type;  value -- > null
D/MainActivity: field --- > calendar_color_index;  value -- > null
D/MainActivity: field --- > _id;  value -- > 199
D/MainActivity: field --- > guestsCanInviteOthers;  value -- > 1
D/MainActivity: field --- > allowedAttendeeTypes;  value -- > 0,1,2
D/MainActivity: field --- > dtstart;  value -- > 1582893000000
D/MainActivity: field --- > guestsCanSeeGuests;  value -- > 1

D/MainActivity: field --- > eventTimezone;  value -- > Asia/Shanghai
D/MainActivity: field --- > availability;  value -- > 0
D/MainActivity: field --- > title;  value -- > 创新创业项目
D/MainActivity: field --- > ownerAccount;  value -- > Phone

D/MainActivity: field --- > duration;  value -- > null
D/MainActivity: field --- > event_calendar_type;  value -- > 0
D/MainActivity: field --- > lastSynced;  value -- > 0
D/MainActivity: field --- > guestsCanModify;  value -- > 0
D/MainActivity: field --- > rdate;  value -- > null
D/MainActivity: field --- > maxReminders;  value -- > 5
D/MainActivity: field --- > account_name;  value -- > Phone
D/MainActivity: field --- > calendar_color;  value -- > -16733458
D/MainActivity: field --- > cal_sync9;  value -- > null
D/MainActivity: field --- > cal_sync8;  value -- > null
D/MainActivity: field --- > dirty;  value -- > 1
D/MainActivity: field --- > calendar_timezone;  value -- > Asia/Shanghai
D/MainActivity: field --- > accessLevel;  value -- > 0
D/MainActivity: field --- > eventLocation;  value -- > null
D/MainActivity: field --- > location_latitude;  value -- > c37d19942d3df
D/MainActivity: field --- > hasAlarm;  value -- > 1
D/MainActivity: field --- > uid2445;  value -- > null
D/MainActivity: field --- > deleted;  value -- > 0
D/MainActivity: field --- > eventColor;  value -- > null
D/MainActivity: field --- > organizer;  value -- > Phone
D/MainActivity: field --- > eventStatus;  value -- > 1
D/MainActivity: field --- > customAppUri;  value -- > null
D/MainActivity: field --- > canModifyTimeZone;  value -- > 1
D/MainActivity: field --- > eventEndTimezone;  value -- > null
D/MainActivity: field --- > customAppPackage;  value -- > null
D/MainActivity: field --- > original_sync_id;  value -- > null
D/MainActivity: field --- > hasAttendeeData;  value -- > 1
D/MainActivity: field --- > displayColor;  value -- > -16733458
D/MainActivity: field --- > dtend;  value -- > 1582896600000
D/MainActivity: field --- > original_id;  value -- > null
D/MainActivity: field --- > sync_data10;  value -- > null
D/MainActivity: field --- > calendar_displayName;  value -- > Phone
D/MainActivity: =============================================

向日历中插入提醒事件,这里结合Reminders表进行插入带有提醒的事件

  1. Reminders表

插入提醒事件,官方说明在这里插入图片描述

 /**
     * 插入新事件时,必须包含以下字段:
     * dtstart
     * dtend如果事件不是重复发生
     * 事件持续发生的持续时间
     * rrule或rdate(如果事件重复发生)
     * eventTimezone
     * 一个calendar_id
     * @param view
     */
    public void addNewEvents(View view) {
        ContentResolver contentResolver = getContentResolver();
        Uri uri = CalendarContract.Events.CONTENT_URI;
        ContentValues values = new ContentValues();
        putValues(values);
        Uri insert = contentResolver.insert(uri, values);
        String eventId = insert.getLastPathSegment();
        Log.d(TAG, "event id -->" + eventId);
        addEventAlert(eventId);
        Log.d(TAG, "insert result --->" + insert);
    }

    private void putValues(ContentValues values) {
        // 日历ID 必填项,通过查询Events表,在log—cat下查看calendar_idvalue=1
        long calID = 1;
        // java.util下的Calendar,月份基准为0
        Calendar beginTime = Calendar.getInstance();
        // 2020 4-5 12:00
        beginTime.set(2020, 3, 5,12,00);
        Calendar endTime = Calendar.getInstance();
        endTime.set(2020, 3, 5, 13, 00);
        long startMillis = beginTime.getTimeInMillis();
        long endMills = endTime.getTimeInMillis();

        values.put(CalendarContract.Events.CALENDAR_ID, 1);
        // 开始时间
        values.put(CalendarContract.Events.DTSTART, startMillis);
        // 结束时间
        values.put(CalendarContract.Events.DTEND, endMills);
        // 标题
        values.put(CalendarContract.Events.TITLE, "QQ回复");
        // 描述
        values.put(CalendarContract.Events.DESCRIPTION, "QQ收到消息 ");
        // 时间时区
        String timeZone = TimeZone.getDefault().getID();
        Log.d(TAG, "EventTimeZone-->"+ timeZone);
        values.put(CalendarContract.Events.EVENT_TIMEZONE, timeZone);
    }

在这里插入图片描述

案例二 获取联系人相应信息

在使用之前,我先在模拟器中新建了几个联系人,然后导出databases文件查看contacts2.db其中的表结构,然后在进行分析!
在这里插入图片描述

raw_contacts:包含联系人数据摘要的行,针对特定用户帐户和类型。 mimetypes:数据类型描述表 data:数据表,存储号码,QQ,邮箱,昵称,图片。这些类型都在mimetypes里有。 所以我们要拿一个用户的数据,应该是从raw_contacts里拿到id,再到data拿出数据,根据mimetype来判断数据的类型。 但是现在的android系统不这么玩了。也不直接给你读取mimetypes里的数据了。

但我们可以通过下面的uri获取 content://com.android.contacts/data/phones联系人信息

		ContentResolver contentResolver = getContentResolver();
        Uri contentUri = Uri.parse("content://com.android.contacts/raw_contacts");
        Cursor cursor = contentResolver.query(contentUri, null, null, null, null);
        List<Contact> contacts = new ArrayList<>();
        Contact contact = null;
        while (cursor.moveToNext()) {
            contact = new Contact();
            String id = cursor.getString(cursor.getColumnIndex("_id"));
            contact.setId(id);
            contact.setDisplayName(cursor.getString(cursor.getColumnIndex("display_name")));
            Log.d(TAG,"id -- > " + id);
            contacts.add(contact);
        }
        cursor.close();
        Uri phoneNumUri = Uri.parse("content://com.android.contacts/data/phones");
        for(Contact contact1 : contacts) {
            Cursor phoneCursor = contentResolver.query(phoneNumUri,new String[]{"data1"},"raw_contact_id=?",new String[]{contact1.getId()},null);
            if(phoneCursor.moveToNext()) {
                contact1.setPhoneNum(phoneCursor.getString(phoneCursor.getColumnIndex("data1")).replace("-",""));
            }
            phoneCursor.close();
        }

        for(Contact contact1 : contacts) {
            Log.d(TAG,contact1.getId() + " -- NAME --- > " + contact1.getDisplayName() + " --- PHONE -- > " + contact1.getPhoneNum());
        }

Contact.java

package cn.wjx.contentprovider_test.model;

/**
 * @author WuChangJian
 * @date 2020/4/5 13:08
 */
public class Contact {
    //ID
    private String id;
    //手机号
    private String phoneNum;
    //显示名称
    private String displayName;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getPhoneNum() {
        return phoneNum;
    }

    public void setPhoneNum(String phoneNum) {
        this.phoneNum = phoneNum;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(String displayName) {
        this.displayName = displayName;
    }
}

案例三 读取短信验证码

前面自定义内容提供者的时候,我们通过注册ContentObserver,当我们向表中插入一条数据时,可以监听到数据库表的变化,并且可以获取插入后的Uri(table/id);这就是通过注册内容观察者来实现的,接下来我们通过ContentObserver来监听SMS表中数据的变化,实现短信验证码的动态填充功能。 1. 我们想要了解短信相应表中有哪些字段为我们所用,最好的办法就是查询

private void getSMS() { 
	ContentResolver resolver = getContentResolver();
 	Uri uri = Uri.parse("content://sms");
  	Cursor cursor = resolver.query(uri, null, null, null, null); 
  	String[] columnNames = cursor.getColumnNames();
   	while(cursor.moveToNext()) {
    	Log.d(TAG,"=============================================");
     	for(String columnName : columnNames) { 
     		Log.d(TAG,"field --- > " + columnName + "; value -- > " + cursor.getString(cursor.getColumnIndex(columnName)));
   		}
   		Log.d(TAG,"=============================================");
   } 
   cursor.close();
}

从下图可以看到,短信的内容被一个body所索引,body字段是我们需要的一个字段
在这里插入图片描述
2. 注册内容观察者ContentObserver,实现对接受短信的监听
注意点,当observer监听到短信的变化之后,onChange会执行两次,第一次不被所需。
3. 编码实现额外功能
倒计时的实现
// 利用CountDownTimer实现按钮的倒计时 private CountDownTimer mCountDownTimer = new CountDownTimer(总时长毫秒, interval) { @Override public void onTick(long millisUntilFinished) { // 防止用户重复点击,可以将按钮置为禁用等状态 … }

   @Override
        public void onFinish() {
            // 倒计时结束后可做,按钮恢复状态
            ...
        }
    };
    ```
* 正则表达式匹配短信内容的数字
    ```
    // 这里以六位验证码为例
        Pattern p = Pattern.compile("(?<![0-9])([0-9]{6})(?![0-9])");
        Matcher matcher = p.matcher(body);
        boolean contain = matcher.find();
        if (contain) {
            Log.d(TAG, "VerifyCode --> "+matcher.group());
            // 将验证码设置到UI控件上
            ...
        }
    ```

6、用LoaderManger来获取媒体库图片数据

示例:获取本地图片相关参数的写法

		private void initLoaderManger() {
        LoaderManager loaderManager = LoaderManager.getInstance(this);
        loaderManager.initLoader(1, null, new LoaderManager.LoaderCallbacks<Cursor>() {
            @NonNull
            @Override
            public Loader<Cursor> onCreateLoader(int id, @Nullable Bundle args) {
                if(id  == 1){
                    return new CursorLoader(OneActivity.this,
                            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                            new String[]{"_data","_display_name","date_added"},
                            null,null,"date_added DESC");

                }
                return null;
            }

            @Override
            public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
                if(cursor != null){
                    String[] columnNames = cursor.getColumnNames();
                    while (cursor.moveToNext()){
                        Log.d(TAG, "==================================");
                        for (String columnName : columnNames){
                            //可以正常的获取到想要的字段
                            Log.d(TAG,"field --- > " + columnName + "value -- > " + cursor.getString(cursor.getColumnIndex(columnName)));
                        }
                        Log.d(TAG, "==================================");
                    }
                    cursor.close();
                }
            }

            @Override
            public void onLoaderReset(@NonNull Loader<Cursor> loader) {

            }
        });

    }
  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值