BroadCastReceiver&ContentProvider&运行时权限

现在的你在哪座城市?生活着还是生存着,愿每一个人都被岁月温柔以待!!!

  1. BroadCastReceiver的讲解以及实践
  2. android6.0运行时权限的请求
  3. ContentProvider的亲身实践

全局大喇叭—android广播机制

android中的每一个引用都可以对自己感兴趣的广播进行注册,这样程序就只会接收自己所关心的内容,这些广播可能是来自系统的,也可以是来在其他的应用程序。android提供了一套完整的API,允许应用程序自由的发送和接收广播。

广播的类型分为两种

1.标准广播(Normal broadcasts)是以中完全异步执行的广播,在广播发出之后,所有的广播接收器几乎在同一时间接收到这条广播消息,因为他们之间没有先后顺序,所以这种广播的效率是比较高的。
2.有序广播(Ordered broadcasts)则是一种同步执行的广播,在广播发送之后,同一时刻只会有一个广播接收器接收到这条广播,当这个广播接收器的逻辑执行完成之后,广播才会继续传递。所以此时广播是由传递顺序的,优先级高的广播先接收到消息,并且有权利截断广播的传递。


使用广播,需要注册广播,注册广播有两种:

1.在代码中动态注册。

2.在manifest.xml中静态注册。

实例:接收系统广播(动态注册)

android系统内置了很多系统级别的广播,比如网络的变化,电量的变化,开机启动会发送广播等等,那我们先做个实例,监听手机网络的变化

1:先创建一个广播接收器NetworkChangeReceiver并集成BroadCastReceiver,需要实现onReceive()方法

public class NetworkChangeReceiver extends BroadcastReceiver {
    public NetworkChangeReceiver() {
        super();
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        ConnectivityManager connectivityManager = ((ConnectivityManager)
        context.getSystemService(Context.CONNECTIVITY_SERVICE));

        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();

        if(networkInfo != null && networkInfo.isAvailable()){
            Toast.makeText(context,"network is available",Toast.LENGTH_SHORT).show();
        }else{
            Toast.makeText(context,"network is unavailable",Toast.LENGTH_SHORT).show();
        }


    }
}

如上所述,我们只在广播接收器中提示网络的变化而已

2.新建BoardCastActivity来进行广播的动态注册

private IntentFilter intentFilter;
    NetworkChangeReceiver networkChangeReceiver;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_board_cast);
        intentFilter = new IntentFilter(); 
        //添加接收的类型intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetworkChangeReceiver();

//注册广播        registerReceiver(networkChangeReceiver,intentFilter);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        //进行注销
        unregisterReceiver(networkChangeReceiver);
    }

解释:
我们首先创建 IntentFilter实例,并添加一个值为android.net.conn.CONNECTIVITY_CHANGE的action,因为当手机网络发生没变化时,系统会主动发送一条值为android.net.conn.CONNECTIVITY_CHANGE的广播,而我们也正是接收这样的广播。

//注册广播的方法
registerReceiver(
        BroadcastReceiver receiver, IntentFilter filter) 

提示:动态注册的广播接收器移动都要进行取消注册的 操作才可以
所以我们在onDestory()方法中加入

unregisterReceiver(networkChangeReceiver);

3.android为保护用户设备的安全和隐私,做了严格的规定,如果程序需要进行一些对用户来说比较敏感的操作,就必须在配置文件中进行权限声明,所以监听网络的动态变化我们需要声明权限

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

实例:实现开机启动(静态注册)

1.创建广播接收器BootCompleteReceiver,继承BroadCastReceiver并重写onReceiver()方法

public class BootCompleteReceiver extends BroadcastReceiver {
    public BootCompleteReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context,"Boot Complete",Toast.LENGTH_SHORT).show();
    }
}

2.在manifest.xml进行广播的注册

<receiver
            android:name=".broadcast.BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>

        </receiver>

看起来跟Activity类似,相应的解释就不说了,可以百度。
添加android.intent.action.BOOT_COMPLETED的action是因为手机启动之后发送的正式名为android.intent.action.BOOT_COMPLETED的广播。

3.同理,监听手机开机启动也是需要相应的权限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

这中监听手机系统而且属于静态注册的广播不需要执行任何触发操作 ,只要你的程序打开一次,这种广播便会一直 存在的。


发送自定义广播

上面我们说的都是监听系统的广播,所以我们只需要创建广播接收器便可以了,然后进行注册。

但是,按照正常的逻辑想一下。广播应该是有一个接收者(广播接收器),一个发送者(发送广播)。但是上面的介绍中并没有任何触发发送的代码,是因为我们监听的都是android系统本身的,所以只要相应的动作触发之后,你的广播自然会接收到信息。

接下来我们需要说的是 自定的广播

1.创建自定义的广播接收器MyBroadCastReceiver继承BroadCastReceiver并实现onReceiver()方法

public class MyBroadCastReceiver extends BroadcastReceiver {
    public MyBroadCastReceiver() {
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context,"This is MyPrivateProject",Toast.LENGTH_SHORT).show();
    }
}

2.使用静态注册

<receiver
            android:name=".broadcast.MyBroadCastReceiver"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="wedfrend.wang.privateproject.MYBROADCASTRECEIVER"/>
            </intent-filter>

        </receiver>

看看代码中,现在的

action标签中的值

wedfrend.wang.privateproject.MYBROADCASTRECE

使我们自己定义的,为了发送广播的时候能够被监听到

3.发送广播

findViewById(R.id.btn_MyBroadCastReceiver).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("wedfrend.wang.privateproject.MYBROADCASTRECEIVER");
                sendBroadcast(intent);
            }
        });

同样的我们也是使用Intent进行发送广播,现在看看当初自己设置的

<action android:name="wedfrend.wang.privateproject.MYBROADCASTRECEIVER"/>

刚好用上,是吧。

当点击触发之后,我们就发送了一条广播。并且进行吐司通知。

如果你仔细的观察,我们目前发送的广播使用的是sendBroadcast(intent)方法,这个方法属于发送无序广播,也就是标准广播。所以如果你现在重新创建一个新的项目,并创建一个广播接接收器,在注册的时候将<action/>标签设置为

<action android:name="wedfrend.wang.privateproject.MYBROADCASTRECEIVER"/>

只要你触发发送这条广播的按钮,两个程序都会接收到广播并且都会进行吐司通知。

对于这部分可以亲手试验一下(这里就略过了)

那么我们如何做到只在本程序中发送并且只在本程序中接收这条广播呢?

有两种方法:

1.发送有序广播

step1:以上面的例子为例,将触发广播按钮的代码改一下

 findViewById(R.id.btn_MyBroadCastReceiver).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("wedfrend.wang.privateproject.MYBROADCASTRECEIVER");
//                sendBroadcast(intent);
                sendOrderedBroadcast(intent,null);
            }

sendBroadcast(intent);修改为sendOrderedBroadcast(intent,null);

sendOrderedBroadcast的第一个参数为Intent,第二个参数是一个与权限相关的字符串,一般情况下自己定义的广播只需传入null就可以了,如果需要用到权限的情况根据具体需求进行传值。

step2:还需要将你的manifest.xml中的注册部分进行修改如下:

<receiver
            android:name=".broadcast.MyBroadCastReceiver"
            android:enabled="true"
            android:exported="true">

            <intent-filter android:priority="100">
                <action android:name="wedfrend.wang.privateproject.MYBROADCASTRECEIVER"/>
            </intent-filter>

        </receiver>

这里和上面的基本类似,只是添加了android:priority="100"属性,意思是设置优先级,优先级比较高的先接收到广播,有权对广播进行截断处理。

step3:然后根据需求进行处理

如果你想要只在本程序中执行这条广播,那么在广播的接收器中添加截断代码如下:

@Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
        Toast.makeText(context,"This is MyPrivateProject",Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }

只需要在逻辑处理完成之后添加abortBroadcast();便可以进行截断。

2.使用本地广播

方法1呢是可以实现只接受自己的广播,关键在于设置的优先级别,但是你根本就无法预判别的程序的优先级是否比你的低,如果别人的优先级更高,并且进行了截断,那么你的程序就永远也接收不到广播,所以android提供了另外一种方法,本地广播

为了解决广播的安全问题,android引入了一套本地广播机制,使用这个机制发出的广播只能够在应用程序内部进行传递,并且广播接收器也只能接收来自本应用程序发出的广播。

使用方法就是使用LocalBroadCastManager来对广播进行管理。

另外需要注意的是,这种方式下的广播需要在代码中进行动态的注册。

public class LocalBroadCastActivity extends AppCompatActivity {

    private LocalBroadcastManager localBroadcastManager;
    LocalReceiver localReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_loacl_broad_cast);
        localBroadcastManager = LocalBroadcastManager.getInstance(this);
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction("wedfrend.wang.privateproject.LOCALBROADCAST");
        localReceiver = new LocalReceiver();
        //注册广播接收器
        localBroadcastManager.registerReceiver(localReceiver,intentFilter);

        //发送本地广播
        Intent intent = new Intent("wedfrend.wang.privateproject.LOCALBROADCAST");
        localBroadcastManager.sendBroadcast(intent);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        localBroadcastManager.unregisterReceiver(localReceiver);
    }

    class LocalReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context,"wedfrend.wang.privateproject.LOCALBROADCAST",Toast.LENGTH_SHORT).show();
        }
    }
}

看看上述代码,其实坦白讲和动态注册Receiver类似,只是在注册的时候我们使用的

localBroadcastManager.registerReceiver(localReceiver,intentFilter);

两个参数也时一样的,另外还有在销毁的时候,取消注册

上述的代码是重新构建了一个广播接收器,其实你可以再上述自定的以广播中使用这种方法,看看效果如何?

说说运行权限的那些事

android运行权限也就是在6.0系统之后发布的。在之前的版本中,开发时需要用到什么全选只要在Manifest.xml中进行权限声明就好了,但是也有用户觉得我就是不愿意开启这些权限,导致程序会出现闪退等等的问题。

所以6.0的系统加入了运行时权限,也就是说当你的某一个功能需要获取系统一些敏感的权限才能正常使用时就会弹出提示框,将选择权交给用户,即使用户不同意你的程序也不会出现崩溃的情况。

android系统的权限有好几百个,但是对于要进行运行权限管理的并不多,这是查看android权限官网可以查看。

[android系统权限列表]请自行百度查看。

我们来看看实例

在6.0编译系统之前,我们书写一个拨打电话功能的按钮,看代码:

findViewById(R.id.btn_usePhone).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        } catch (SecurityException e) {
            e.printStackTrace();
        }

                }
            }
        });

然后在Manifest.xml中添加权限请求:

  <uses-permission android:name="android.permission.CALL_PHONE" />

因为我目前的编译系统是6.0,所以需要加入异常处理,6.0系统之前的不需要,那我们看看会出现什么样的效果:

运行权限异常

出现异常,权限异常。

所以这里需要说明的是:6.0之后的系统,必须对危险权限进行运行时权限的处理,处理方法如下:

1.我们先将拨打电话的功能单独写一个方法放在外部。

private void call(){
        try {
            Intent intent = new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
            startActivity(intent);
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }

2.点击按钮的时候先判断是否有拨打电话的权限

findViewById(R.id.btn_usePhone).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED){

                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},0);

                }else{
                    call();
                }
            }
        });

step1:先判断用户是否已经受过权限,借助的是

ContextCompat.checkSelfPermission()

两个参数,第一个常见,第二个为我们需要获取的权限

将返回值与PackageManager.PERMISSION_GRANTED进行比较,相等表示已经受过权,不相等我们需要进行权限请求。

step 2:权限请求使用的方法是

requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final int requestCode)

三个参数,第一个不多说了,第二个参数表示所有需要请求的权限数组,第三个参数为请求标识符。

step3:当我们调用请求权限的方法时,系统就会弹出一个对话框,如下

请求权限

当我们点击按钮 允许 或者拒绝时,系统会掉用方法onRequestPermissionsResult()并处理相应逻辑。

所以我们需要重写onRequestPermissionsResult()方法:

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 0:

 Log.i(TAG, "onRequestPermissionsResult: "+permissions[0]);
 Log.i(TAG, "onRequestPermissionsResult: "+grantResults[0]);               if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    call();
                }else{
                    Toast.makeText(MainActivity.this,"您已经拒绝的权限请求",Toast.LENGTH_SHORT).show();
                }

                break;
        }
    }

解释:我们在回掉的方法中进行判断,如果用户点击了允许,我们看看输出的内容:

允许

grantResults[0]为0的时候,表示用户允许,那么进行拨打电话。

那我们看看点击拒绝的时候会是什么情况(重新运行程序,或者在应用管理界面将拨打电话权限关闭):

拒绝

此时

grantResults[0]-1的时候,表示用户拒绝,那么进行吐司通知。

吐司通知

上面就是关于6.0的系统中关于运行权限的处理了。那么有时候你的程序中可能在某一功能中不止一个私密权限需要请求,你该怎么办?

其实在权限请求中的那个数组参数String[]就支持多权限的请求,而且重写方法里面的参数也时String[]数据,所以你需要做的只是进行遍历就好了。

ContentProvider

ContentProvider的汉语解释是:跨程序共享数据—-内容提供者

内容提供者的用法一般有两种:

1.使用现有的内容提供器来读取数据

2.创建自己的内容提供器供外部程序访问

ContentResolver的基本用法

对于每一个应用程序,如果想要访问内容提供器中的共享数据,就必须借助ContentResolver类,并且可以通过Context.getContentResolver()方法获取该实例。ContentResolver同样提供CRUD方法对数据进行操作。这些方法跟SQLiteDatabase相似,只是参数上有些区别。(如果你对SQLiteDatabase不熟悉,还麻烦先看看我的上一篇文章)

ContentResolver中的增删改查方法不接收表明,而是使用一个Uri参数代替,该参数被称为内容URI

内容URI给内容提供器中建立了唯一标识符,主要由两部分组成:authoritypath
authority 是对于不同的应用做区分,一般为了避免冲突,使用程序包名,com.example.demo.provider

path 表示对同一程序中的不同表作区分。/table1

下面提供两个标准的URI:

content://com.example.demo.provider/table1

content://com.example.demo.provider/table2

看看查增删改四种方法。

Retrieve(查询)


Cursor cursor = getContentResolver().
                query(@NonNull Uri uri, @Nullable String[] projection,
            @Nullable String selection, @Nullable String[] selectionArgs,
            @Nullable String sortOrder)

对于上面的参数,我们和数据库SQLite进行对比说明

//        uri        --------》from tableName:指定查询某个应用程序下的某张表
//        projection --------》select column1:指定查询的列名
//        selection  --------》 where:指定约束条件
//        selectionArgs -----》 ···:为where中的占位符提供具体的值
//        sortOrder  --------》order by :指定查询结果排列方式

返回的仍然是一个Cursor对象,同数据库一样将数据逐个读取出来。

看看代码

//知道内容URI之后,需要将它解析成Uri对象才可以作为参数传入
//使用的方法是  Uri.parse()方法就可以实现
Cursor cursor = getContentResolver().query(Uri.parse("content://com.example.demo/table1"),null,null,null,null);
if(cursor != null){
    while(cursor.moveToNext()){
        String column1 = cursor.getString(cursor.getColumnIndex(cursor.getColumnIndex("column1")));
}
cursor.close();
}

create(增加)

ContentValues values = new ContentValues();
values.put("column1","text");
values.put("userId","1");
getContentResolver().insert(uri,values);

Update(更新)

ContentValues values = new ContentValues();
values.put("column1","wangxiaobo");
getContentResolver().update(uri,values,"userId=?",new String[]{"1"});

Delete(删除)

getContentResolver.delete(uri,"column1=?",new String[]{"wangxiaobo"});

以上的的操作就是关于ContentResolver的基本操作,这些说起来都很简单,因为它的思想基本和数据库一致。下面我会通过自己的实践将ContentResolver进行实践。


实例1:读取手机联系人

在日常的项目开发中,我们最常用也是最常见的使用系统的内容提供者获取电话联系人的功能,那我就以读取手机联系人信息来进行整理:

准备工作:如果你使用的是模拟机,还麻烦在你的手机中创建3-5个联系人。真机的话就不多说了,相信你一定有几个联系人吧!

其实在这一步我们做的事情并不多,只是进行读取而已。

创建ContentProviderActivity如下:

package wedfrend.wang.privateproject.contentprovider;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

import wedfrend.wang.privateproject.R;

public class ContentProviderActivity extends AppCompatActivity {

    List<String> contactsList = new ArrayList<>();

    ArrayAdapter<String> arrayAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_content_provider);
        ListView listView = ((ListView) findViewById(R.id.lv_contacts));
        arrayAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactsList);
        listView.setAdapter(arrayAdapter);
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},0);
        }else{
            readContacts();
        }
    }

    /**
     * 读取手机联系人的方式
     */
    private void readContacts(){

        Cursor cursor = null;
        try {
            cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
            if(cursor != null){
                while (cursor.moveToNext()){
                    String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(name+"\n"+phone);
                }
                arrayAdapter.notifyDataSetChanged();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(cursor != null){
                cursor.close();
            }
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 0:
                if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    readContacts();
                }else{
                    Toast.makeText(this,"因为涉及您的通讯录读取,需要获得授权",Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}

代码很简单,这里主要说明一下:readContacts()方法:

使用ContentResolver方法需要传入参数Uri,但是这里我们传入的参数与上面叙述的并不太相同,那么来看看:

ContactsContract.CommonDataKinds.Phone已经帮我们做了封装:

ContactsContract.CommonDataKinds.Phone.CONTENT_URI表示Uri路径位置content://com.android.contacts/data/phones

相应查询的列ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME表示display_name

ContactsContract.CommonDataKinds.Phone.NUMBER表示的是data1

最后 就是将Cursor进行关闭。

最后一步就是在manifest.xml中声明权限就好。

相应的.xml布局

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_content_provider"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="wedfrend.wang.privateproject.contentprovider.ContentProviderActivity">


    <ListView
        android:id="@+id/lv_contacts"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </ListView>

</RelativeLayout>

实例:我们来创建属于自己的内容提供器

创建内容提供器:换一句话说就是将自己的数据共享给其他的App进行使用。在换句话说就是:你的App应用频率要高,某些数据具有共享的价值。在换句话说就是:你公司的软件产品就多,而且在用户手机的安装率较高,那么App之间数据共享。(比如 tencent,还有美团这些公司的App较多,是可以进行这样的操作)。

如果你所处的互联往公司只有一款产品,而且价值在你开发期间一般,安装率其实也不见得很好,那么对于ContentProvider的使用时不频繁的,甚至你不知道这是个什么东西也是可以正常的完成你的工作。

所以下面的内容就当我是随便说说,只是做一个自己的整理。(可以忽略)

说正题:

实现跨程序共享数据的功能,google官方推荐的方法也是创建自己的内容提供器,实现方法就是我们自定义一个MyContentProvider类去继承官方的ContentProvider类并实现类中的6个抽象方法。

public class MyContentProvider extends ContentProvider {

    private static final String TAG = "MyContentProvider";
    /*
    初始化内容提供器时候调用,通常完成数据库的创建和升级等操作
    只有当存在ContentResolver尝试访问我们程序当中的数据的时候,内容提供器才会被初始化。
     */
    @Override
    public boolean onCreate() {
        Log.i(TAG, "onCreate: ");
        return false;
    }

    /*
    从内容提供器中查询数据。使用uri参数来确定查询哪张表,查询结果会存放在Cursor对象中
     */
    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.i(TAG, "query: ");
        return null;
    }


    /*
    向内容提供器中添加一条数据,添加完成之后,返回一个用于表示这条记录的URI
     */
    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.i(TAG, "insert: ");
        return null;
    }


    /*
    更新内容提供器中已有的数据,返回受影响的行数
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.i(TAG, "update: ");
        return 0;
    }

    /*
    删除一条数据,返回被删除的行数
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.i(TAG, "delete: ");
        return 0;
    }

     /*
    根据传入的内容URI来返回相应的MIME类型

    android规定一个内容URI所对应的MIME字符串主要由3部分组成

    1.必须以vnd开头

    2.如果URI是以路径结尾,则后面接 android.cursor.dir/
      如果URI是以id结尾,则后面接   android.cursor.item/

    3.最后需要接 vnd.<authority>.<path>

     */
    @Nullable
    @Override
    public String getType(Uri uri) {
        Log.i(TAG, "getType: ");
        return null;
    }
}

以上的类只是一个空类,对应的解释也是在上面注明。顺便我在方法中加入了打印的方法来实际的看看调用的顺序。

可以看到,基本每一个方法中都需要uri这个参数,参数uri是我们进行增删改查的时候传递进来的,看看:

标准Uri

content://com.example.demo.provider/table1

标准Uri+id

content://com.example.demo.provider/table1/1

解释:调用com.example.demo这个应用程序中table1表中id=1的数据。

另外可以通过通配符来进行URI的通配:规则如下:

*:表示任意字符
#:表示任意数字

所以上面的URI可以写成:

content://com.explame.demo.provider/*
content://com.example.demo.provider/table1/#

在自己定义的ContentProvider类中可以借助UriMatcher类来实现匹配内容URI的功能。

方法:

new UriMatcher(UriMatcher.NO_MATCH). addURI(String authority, String path, int code);

这三个参数 authoritypath和上述的一致,关于code我们进行设置,在之后的方法调用:

uriMatcher.match(uri)

可以与我们之前设定的值进行匹配。

下面我们结合代码进行说明

创建MyContentProvider如下:

/**
 * Created by welive on 2017/2/23.
 *
 * 将自己创建的数据进行共享,那么其他的应用程序也是可以进行访问的。
 *
 *
 */

public class MyContentProvider extends ContentProvider {


    private static final String TAG = "MyContentProvider";

    private ComputerDataBase computerDateBase;

    public static final String authority = "wedfrend.wang.privateproject.provider";

    public static final String path = "computer";

    public static final int COMPUTER_DIR = 0;

    public static final int COMPUTER_ITEM = 1;

    private static UriMatcher uriMatcher;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(authority,path,COMPUTER_DIR);
        uriMatcher.addURI(authority,path+"/#",COMPUTER_ITEM);
    }



    /*
    初始化内容提供器时候调用,通常完成数据库的创建和升级等操作
    只有当存在ContentResolver尝试访问我们程序当中的数据的时候,内容提供器才会被初始化。
     */
    @Override
    public boolean onCreate() {
        Log.i(TAG, "onCreate: ");
        computerDateBase = new ComputerDataBase(getContext(),"computerData.db",null,1);
        return true;
    }

    /*
    从内容提供器中查询数据。使用uri参数来确定查询哪张表,查询结果会存放在Cursor对象中
     */
    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Log.i(TAG, "query: ");

        SQLiteDatabase sqliteDatabase = computerDateBase.getReadableDatabase();
        Cursor cursor = null;
        switch (uriMatcher.match(uri)){
            case COMPUTER_DIR:
                cursor = sqliteDatabase.query("computer",projection,selection,selectionArgs,null,null,sortOrder);
                break;
            case COMPUTER_ITEM:
                String id = uri.getPathSegments().get(1);
                Log.i(TAG, "query: "+id);
                cursor = sqliteDatabase.query("computer",projection,"id=?",new String[]{id},null,null,sortOrder);
                break;
        }
        return cursor;
    }


    /*
    向内容提供器中添加一条数据,添加完成之后,返回一个用于表示这条记录的URI
     */
    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.i(TAG, "insert: ");
        SQLiteDatabase sqliteDatabase = computerDateBase.getWritableDatabase();
        Uri uriId = null;
        switch (uriMatcher.match(uri)){
            case COMPUTER_DIR:
            case COMPUTER_ITEM:
                long computerId = sqliteDatabase.insert("computer",null,values);
                uriId = Uri.parse("content://"+authority+"/computer/"+computerId);
                break;
        }
        return uriId;
    }


    /*
    更新内容提供器中已有的数据,返回受影响的行数
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Log.i(TAG, "update: ");
        SQLiteDatabase sqliteDatabase = computerDateBase.getWritableDatabase();
        int upDateRows=0;
        switch (uriMatcher.match(uri)){
            case COMPUTER_DIR:
                upDateRows = sqliteDatabase.update("computer",values,selection,selectionArgs);
                break;
            case COMPUTER_ITEM:
                String upId = uri.getPathSegments().get(1);
                Log.i(TAG, "update: "+upId);
                upDateRows = sqliteDatabase.update("computer",values,"id=?",new String[]{upId});
        }
        return upDateRows;
    }

    /*
    删除一条数据,返回被删除的行数
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Log.i(TAG, "delete: ");
        SQLiteDatabase sqliteDatabase = computerDateBase.getWritableDatabase();
        int deletesRows = 0;
        switch (uriMatcher.match(uri)){
            case COMPUTER_DIR:
                deletesRows = sqliteDatabase.delete("computer",selection,selectionArgs);
                break;
            case COMPUTER_ITEM:
                String deleteId = uri.getPathSegments().get(1);
                Log.i(TAG, "delete: "+deleteId);
                deletesRows = sqliteDatabase.delete("computer","id=?",new String[]{deleteId});
                break;
        }
        return deletesRows;
    }

    /*
    根据传入的内容URI来返回相应的MIME类型

    android规定一个内容URI所对应的MIME字符串主要由3部分组成

    1.必须以vnd开头

    2.如果URI是以路径结尾,则后面接 .android.cursor.dir/
      如果URI是以id结尾,则后面接   .android.cursor.item/

    3.最后需要接 vnd.<authority>.<path>

     */
    @Nullable
    @Override
    public String getType(Uri uri) {
        Log.i(TAG, "getType: ");
        switch (uriMatcher.match(uri)){
            case COMPUTER_DIR:
                return "vnd.android.cursor.dir/vnd.wedfrend.wang.privateproject.provider.computer";
            case COMPUTER_ITEM:
                return "vnd.android.cursor.item/vnd.wedfrend.wang.privateproject.provider.computer";
        }
        return null;
    }
}

关于上面的方法,看着解释应该是可以直接看懂。

之前说过,ContentProvider是和你的数据库结合在一起的,所以我们还需要在创建一个数据库的辅助工具类,当然你也可以使用LitePal来进行创建。

数据库ComputerDataBase如下:

public class ComputerDataBase extends SQLiteOpenHelper {

    public static final String COMPUTER_TABLE = "create table computer(id integer primary key autoincrement,name text,price real)";

    public ComputerDataBase(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

        db.execSQL(COMPUTER_TABLE);

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

最后一步就是在manifest.xml中配置你的provider

<provider
            android:authorities="wedfrend.wang.privateproject.provider"         android:name="wedfrend.wang.privateproject.contentprovider.MyContentProvider"
android:enabled="true"
android:exported="true"></provider>

至此我们的内容提供者创建完成。那么就进行使用一下:


使用wedfrend.wang.privateproject应用提供的ContentProvider,需要新建一个project,然后进行调用:

public class MainActivity extends AppCompatActivity {

    String newId1,NewId2;
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);


        findViewById(R.id.btn_Insert).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://wedfrend.wang.privateproject.provider/computer");
                ContentValues contentValues = new ContentValues();
                contentValues.put("name","Dell");
                contentValues.put("price","5800");
                Uri newUri = getContentResolver().insert(uri,contentValues);
                newId1 = newUri.getPathSegments().get(1);
                Log.i(TAG, "onClick: "+newId1);
                contentValues.clear();
                contentValues.put("name","HP");
                contentValues.put("price","5799");
                Uri newUri2 = getContentResolver().insert(uri,contentValues);
                NewId2 = newUri2.getPathSegments().get(1);
                Log.i(TAG, "onClick: "+NewId2);
            }
        });

        findViewById(R.id.btn_Update).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://wedfrend.wang.privateproject.provider/computer/"+newId1);
                ContentValues contentValues = new ContentValues();
                contentValues.put("name","Lenovo");
                int upRows = getContentResolver().update(uri,contentValues,null,null);
                Log.i(TAG, "onClick: "+upRows);
                //或者我们进行所有的更新
//                Uri uriAll = Uri.parse("content://wedfrend.wang.privateproject.provider/computer");
//                ContentValues contentValuesAll = new ContentValues();
//                contentValuesAll.put("price","6000");
//                int allUpRows = getContentResolver().update(uri,contentValues,"price>?",new String[]{"5000"});
//                Log.i(TAG, "onClick: "+allUpRows);
            }
        });


        findViewById(R.id.btn_select).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //查询的第二个数据
                Uri uri = Uri.parse("content://wedfrend.wang.privateproject.provider/computer/"+NewId2);

//              Uri uri = Uri.parse("content://wedfrend.wang.privateproject.provider/computer");

                Cursor cursor = getContentResolver().query(uri,null,null,null,null);
                if(cursor != null){
                    while (cursor.moveToNext()){
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String price = cursor.getString(cursor.getColumnIndex("price"));
                        Log.i(TAG, "onClick: ------->"+name+":"+price);
                    }
                    cursor.close();
                }else{
                    Toast.makeText(MainActivity.this,"该共享提供者暂时没有数据",Toast.LENGTH_SHORT).show();
                }
            }
        });


        findViewById(R.id.btn_delete).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Uri uri = Uri.parse("content://wedfrend.wang.privateproject.provider/computer/"+NewId2);
                int deleteRows = getContentResolver().delete(uri,null,null);
                Log.i(TAG, "onClick: deleteRows"+deleteRows);

//                Uri uri = Uri.parse("content://wedfrend.wang.privateproject.provider/computer");
//                int deleteAllRows = getContentResolver().delete(uri,"price>?",new String[]{"5999"});
//                Log.i(TAG, "onClick: deleteAllRows"+deleteAllRows);
            }
        });






        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

有四个Button,进行插入,查询,修改,删除:

按钮

insert:我们插入两条数据,点击执行,效果如下:

返回两条插入的ID

insert

接下来我们去提供者的数据库看看,是否真的插入了两条数据:

插入数据查询

查询效果是数据库中存在两条数据。

select查询

查询的是数据库中第二条数据:传递的uri为

查询的Uri

查询结果

查询结果

效果正是我们所查询的数据。

Update更新一条数据:

传入的uri为

content://wedfrend.wang.privateproject.provider/computer/1

因为我们代码中写的查询按钮只查询第二条数据。查看是否操作成功:

方法1:修改查询代码,查询整张表,或者查询第一条数据 。
方法2:使用adb shell命令查看。我选择方法二,就不去改代码了。

查询结果

我们确实是将第一条数据的name字段更新为Lenovo。

Delete删除数据

传入的Uri为:

content://wedfrend.wang.privateproject.provider/computer/2

我们要将第二条数据进行删除:看结果

删除的查询结果

此时数据表中只剩下一条数据。操作成功

运行时权限,注意的问题是在android系统中请求权限的时候会出现是否不在提示的勾选,如果用户永久忽略,那么需要做的事情就是你还必须进行判断

 @Override
    public void contactUser(String number) {
        this.number = number;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestNeedPermission();
        } else {
            callPhone();
        }
    }

    private void requestNeedPermission() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            //获取运行权限
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
                //如果用户默认点击了不在进行提示该权限的显示,那么相应功能无法处理,只能提示用户
                showSystemDialog();
            } else {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, PHONEPERMISSION);
            }
        } else {
            callPhone();
        }
    }

    /**
     * 创建系统提示
     */
    private void showSystemDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("提示")
                .setMessage("由于您拒绝电话权限该功能无法使用,是否重新开启?")
                .setCancelable(false)
                .setPositiveButton("开启", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", getPackageName(), null);
                        intent.setData(uri);
                        startActivity(intent);
                        dialog.cancel();
                    }
                })
                .setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });
        builder.show();
    }

    private void callPhone() {
        //拨打电话
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri data = Uri.parse("tel:" + number);
        intent.setData(data);
        startActivity(intent);
    }

    /**
     * 提示
     *
     * @param hint
     */
    @Override
    public void showSnackbar(String hint) {
        S.showShort(recRenter, R.string.no_more_date);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case PHONEPERMISSION:
                for (int grant:grantResults
                     ) {
                    if(grant == PackageManager.PERMISSION_GRANTED){
                        //表示同意权限
                        callPhone();
                    }else{
                        S.show(recRenter, R.string.permissionHint, R.string.know, null);
                    }
                }
                break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
                break;
        }
    }

对于这一部分的实践代码依然上传至个人Github,下载网址:

https://github.com/wedfrendwang/PrivateProject.git

分支为:broadcastContentprovider

终于将数据这一部分内容整理完,工作之余整理一下自己的知识库,也方便自己日后进行查询。也希望对android的开发者有帮助!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章