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的開發者有幫助!

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