Android第一行程式碼-第七章探究內容提供器

2020-09-20 02:00:21

7.1 簡介

         內容提供器主要用於在不同應用程式之間實現資料共用的功能,他可以選擇只對那一部分資料進行共用,從而保證我們程式中的隱私資料不會有洩漏的風險。
例子:微信讀取手機聯絡人資訊

7.2 執行時許可權

7.2.1 許可權機制詳解

         Android開發團隊在6.0系統中加入了執行時許可權功能,即使用者不需要在安裝程式時一次性授權所有許可權,可以在軟體執行過程中再對某一許可權進行授權。
         Android吧所有的許可權大致分為兩類,一類是普通許可權,一類是危險許可權。
          普通許可權指不會威脅到使用者的安全和隱私的許可權,對於這部分許可權申請,系統自動幫我們進行授權。
         危險許可權表示那些可能觸及使用者隱私或者對裝置安全造成影響的許可權,對於這部分許可權,必須使用者動手點選才可授權。
         Android危險許可權:

在這裡插入圖片描述
危險許可權即作用
         如果要使用以上危險許可權,除了需要在AndroidManifest檔案中設定,還需要在程式碼中對這些許可權進行動態申請

7.2.2 在程式執行時申請許可權

CALL_PHONE時撥打電話功能時需要的許可權,是個危險許可權。
撥打電話的邏輯:

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

接下來新增許可權

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.revise_7_1">
    <uses-permission android:name="android.permission.CALL_PHONE"/>
    ...
</manifest>

         如果在低於6.0的版本使用此方法編寫打電話功能,是可以正常執行的,但在高於6.0的系統執行,會出錯。提示我們"Permission Denial"。
對6.0後作出的修改:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //第一步:判斷使用者是不是給過我們許可權
       //ContextCompat.checkSelfPermission接收兩個引數,第一個是上下文,第二個是許可權名
        if(ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){
   //第二步:沒有授權就呼叫ActivityCompat.requestPermissions()方法來向使用者申請授權
  //接收三個引數,第一個是上下文,第二個是String[]陣列,把要申請的許可權名放在陣列中,第三個是請求碼
         	ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1);
        }else
            call();
    }
     //第三步:不管我們是否給予許可權,都回撥到此方法中,授權結果封裝在grantResults引數中
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode){
            case 1:
                if(grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED)
                    call();
                else
                    finish();
        }
    }

這樣動態新增許可權後,程式就可正常執行。

7.3 存取其他程式中的資料

內容URI

內容URI為內容提供器中的資料建立了唯一的識別符號,就是根據他知道要存取那個資料,由兩部分組成:
         authority:用於對不同的app做區分,一般寫法:包名.provider
          path:用於對同一應用程式中不同的表做區分
為了看出這是URI還要加上頭部協定,標準格式如下
          content://com.example.app.provider/table
還有一種常用格式
          content://com.example.app.provider/table/ID
這種表示存取com.example.app程式下table表中id為ID(int)的資料

建立自己的內容提供器

  1. 建立一個SQL資料庫
             要使用內容提供器肯定需要程式中有SQL資料庫,可以用SQLiteDatabase建立,也可以用LitePal,不懂的點這裡,我用LitePal建立了一個Demo庫,litepal.xml和Demo1表如下
<?xml version="1.0" encoding="UTF-8" ?>
<litepal>
   <dbname value="demo"/>
   <version value="1"/>
   <list>
       <mapping class="com.example.revise_sqlitedatabase.Demo1"></mapping>
   </list>
</litepal>
package com.example.revise_sqlitedatabase;

import org.litepal.crud.LitePalSupport;
public class Demo1 extends LitePalSupport {
    private int id;
    private String name;
    private int age;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

  1. 建立自己的內容提供器
             右擊包名->new->Other->ContentProvider
    在這裡插入圖片描述
    class name就是你的內容提供器的類名,URI Authorities就是URI的authority,一般寫成 包名.provider

MyContentProvider 要重寫6個方法
注意事項:
1.使用萬用字元匹配兩種格式的內容URI

符號含義
*匹配任意長度的任意字元
#匹配任意長度的數位

         例如:匹配表中的一行資料content://com.example.app.provider/table/#
2.UriMatcher類
         UriMatcher類可看做匹配URI的工具類,addURI方法傳入authority,path,和一個自定義程式碼當呼叫UriMatcher的match()方法時,可根據傳入的uri返回相應的自定義程式碼
          用自定義程式碼去判斷到底存取的是那個表
3.Uri物件所對應的MIME型別
         以vnd開頭
         如果URI以路徑結尾,後接android.cursor.dir/,如果以id結尾,後接android.cursor.item/
         最後接上vnd.authority.path

package com.example.revise_sqlitedatabase;

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 org.litepal.LitePal;

public class MyContentProvider extends ContentProvider {
    public static final int Demo1_DIR = 0;          //存取所有資料
    public static final int Demo1_ITEM = 1;        //存取單條資料
    public static final String AUTHORITY=  "com.example.revise_sqlitedatabase.provider";
    private static UriMatcher uriMatcher;
    private Demo1 demo1;
    SQLiteDatabase db;			
	
    static{
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHORITY,"Demo1",Demo1_DIR);
        uriMatcher.addURI(AUTHORITY,"Demo1/#",Demo1_ITEM);// #匹配數位
    }
    public MyContentProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
    //刪除資料
        int deletedRows = 0;
        switch (uriMatcher.match(uri)){
            case Demo1_DIR:
                deletedRows = db.delete("Demo1",selection,selectionArgs);    //返回受影響的行數
                break;
            case Demo1_ITEM:
                String Id = uri.getPathSegments().get(1);
                //getPathSegments()方法吧URI許可權後的部分以「/」分割,0位置是路徑,1位置是id
                deletedRows = db.delete("Demo1","id = ?",new String[]{Id});
                break;
            default:
                break;
        }
        return deletedRows;
    }

    @Override
    public String getType(Uri uri) {
    
        switch (uriMatcher.match(uri)){
            case Demo1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.revise_sqlitedatabase.Demo1";
            case Demo1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.revise_sqlitedatabase.Demo1";
            default:
                break;
        }
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
    //新增資料返回的是新新增的資料的Uri
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)){
            case Demo1_DIR:
            case Demo1_ITEM:
                long newId = db.insert("Demo1",null,values);
                uriReturn = Uri.parse("content://"+AUTHORITY+"/Demo1/"+newId);
                break;
            default:
                break;
        }
        return uriReturn;
    }

    @Override
    public boolean onCreate() {
    //獲取SQLiteDatabase因為這些方法全是用SQLiteDatabase存取資料庫的
        db = LitePal.getDatabase();		
       return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
         //查詢資料返回Cursor            
        Cursor cursor = null;
        switch (uriMatcher.match(uri)){
            case Demo1_DIR:
                cursor = db.query("Demo1",projection,selection,selectionArgs,null,null,sortOrder);
                break;
            case Demo1_ITEM:
                String Id = uri.getPathSegments().get(1);
                cursor = db.query("Demo1",projection,"id = ?",new String[]{Id},null,null,sortOrder);
                break;
            default:
                break;
        }
        return cursor;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
          //更新資料            
        int updatedRows = 0;
        switch (uriMatcher.match(uri)){
            case Demo1_DIR:
               updatedRows = db.update("Demo1",values,selection,selectionArgs);     //返回受影響的行數
                break;
            case Demo1_ITEM:
                String Id = uri.getPathSegments().get(1);
                updatedRows = db.update("Demo1",values,"id = ?",new String[]{Id});
                break;
            default:
                break;
        }
        return updatedRows;
    }
}

使用現有的內容提供器來讀取和操作相應程式中的資料

存取剛才的程式中的資料
Context中的getContentResolver()中提供了一系列方法用於對資料的CRUD

Uri uri = Uri.parse("content://com.example.revise_sqlitedatabase.provider/Demo1");
@Override
   public void onClick(View view) {
       switch (view.getId()){
           case R.id.button1 :
           //新增
               ContentValues values = new ContentValues();
               values.put("name","aaa");
               values.put("age",17);
               values.put("hight",70);
               getContentResolver().insert(uri,values);
               values.clear();
               values.put("name","bbb");
               values.put("age",19);
               values.put("hight",100);
               getContentResolver().insert(uri,values);
               values.clear();
               values.put("name","ccc");
               values.put("age",21);
               values.put("hight",180);
               getContentResolver().insert(uri,values);
               break;
           case R.id.button2 :
           //更新
               ContentValues values1 = new ContentValues();
               values1.put("age",9);
               getContentResolver().update(uri,values1,"name = ?",new String[]{"aaa"});
               break;
           case R.id.button3 :
           //刪除
               getContentResolver().delete(uri,"name = ?",new String[]{"bbb"});
               break;
           case R.id.button4 :
           //查詢
               Cursor cursor = null;
               try{
                   cursor = getContentResolver().query(uri,null,null,null,null);
                   if(cursor != null){
                       while (cursor.moveToNext()){
                           Log.d("MainActivity",cursor.getString(cursor.getColumnIndex("name"))
                           +cursor.getInt(cursor.getColumnIndex("age")));
                       }
                   }
               }catch (Exception e){
                   e.printStackTrace();
               }finally {
                   if(cursor != null){
                       cursor.close();
                   }
               }
               break;
           case R.id.button5 :
               break;
       }

   }