• 5、Android-跨程序共享数据--内容提供器


    Android数据持久化技术:文件存储、SharedPreferences存储、数据库存储

    使用这些持久化技术保存的数据只能再当前的应用程序中访问

    但是对于不同应用之间的可以实现跨程序数据共享的功能

    此时使用的是内容提供器实现跨程序数据共享

    5.1、内容提供器简介

    内容提供器主要用于再不同的应用程序之间实现数据共享的功能

    提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问的数据的安全性

    使用内容提供器是Android实现跨程序共享数据的标准方式

    不同于文件存储和SharedPreferences存储中的两种全局可读可写操作模式

    内容提供器可以选择对那一部分数据进行共享

    从而保证程序中隐私数据不会有泄露的风险

    权限:

    Android运行时权限,内容提供器中需要使用运行时权限的功能

    5.2、运行时权限

    Android的权限机制从系统的第一个版本已经开始

    之前Android的权限机制在保护用户安全和隐私等方面作用比较有限

    再Android6.0之后系统中引入运行时权限这个功能

    从而更好的保护了用户的安茜和隐私

    5.2.1、权限机制详解

    广播机制时使用过:

    https://www.cnblogs.com/Mrchengs/p/10678609.html

    访问系统的网络状态以及监听开机广播设计了用户设备的安全性

    因此必须再AndroidManifest.xml中加入权限声明,否则程序会崩溃

    加了权限之后,对于用户有什么影响?为什么这样就可以保护设备的安全性?

    用户再一下两个方面得到了保护:

    1、如果用户低于6.0系统的设备上安装该程序

      会弹出安装此应用需要以下权限

    这样用户 就可以清楚的知道该程序一共需要那些权限

    2、用户可以随时在应用程序管理界面查看任意一个程序的权限申请情况

      以保证应用程序不会出现各种滥用权限的情况

    这种权限机制的设计思路其实很简单

    用户如果认可你所申请的权限

    那么就会安装程序

    用户不认可所申请的权限

    那么就会拒绝安装

    限制存在很多的软件普遍纯在滥用权限

    不管权限是否用得到

    都会进行申请权限

    在Android 6.0系统中加入了运行时权限功能

    用户不需要再安装软件时一次性授权所有的申请权限

    而是再软件使用过程中在对某一项权限进行授权

    不是所有的权限都需要在运行时申请

    对于用户来说,不停的授权也很烦

    Android现在将所有的权限归成了两类:

    1、普通权限

    2、危险权限

    普通权限指的是那些不会直接危险到用户的安全和隐私的权限

    对于这部分权限,系统会自动帮助我们进行授权

    危险权限指的是那些可能触及用户隐私,或者对设备安全性造成影响的权限

    这部分的权限则有用户手动授权才可

    Android中共有百种多权限

    危险权限总共就那么几个

    剩余的均是普通权限

    危险权限:(9组24个)

    这里是一个参照表

    每当使用一个权限时可以查询

    如果是属于这张表的权限那么就需要运行时处理

    在AndroidManifest.xml文件中进行注册即可

    表格中每个权限都属于一个权限组

    运行时权限处理使用的权限名

    一旦用户同意了

    那么该权限对应的权限组中其他权限也会被授权

    权限列表:https://blog.csdn.net/HardWorkingAnt/article/details/70952583

     5.2.2、程序运行时申请权限

    测试打电话的权限

    首先定义一个按钮用于打电话的call:

        <Button
            android:id="@+id/call"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Call"
            />

    在AndroidManifest.xml中进行权限注册:

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

    在MainActivity中:

        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.first_layout);
    
            Button call = (Button) findViewById(R.id.call);
            call.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                        if (ActivityCompat.checkSelfPermission(MainActivity.this,
                                Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                            ActivityCompat.requestPermissions(MainActivity.this,
                          new String[]{Manifest.permission.CALL_PHONE},1); }else { try { Intent intent = new Intent(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:10086")); startActivity(intent); }catch (Exception e){ e.printStackTrace(); } } } }); }

    这里的action指定了Intent.ACTION_CALL

    这是系统内置的拨打电话动作,在Data中设置的协议是tel

    这个内置的action时直接拨打电话并不是跳转到拨打电话的页面,因此必须声明权限(上述中以进行申请)

    运行时权限爱的核心在程序运行的过程中由用户进行授权去执行相关的操作

    程序是不可擅自去执行这些操作

    第一步是判断用户是否已经授权借助的是:

    ActivityCompat.checkSelfPermission()方法接受两个参数

    1、COntext

    2、具体的权限名

    使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较

    相等就说明用户已经授权,否则表示没有授权

    如果没有授权则执行 ActivityCompat.requestPermissions()方法,3个参数

    1、要求是Activity的实例

    2、是一个String数组,把要申请的权限名放在数组中即可

    3、请求码,必须是唯一值

    在弹出请求的按钮之后

    不管用户点击那个按钮都会指定onRequestPermissionsResult()

    这个方法的逻辑在下面进行实现

    授权的结果会封装在grantResults参数中

    只需要判断最后的授权结果

    用户同意则拨打电话

    否则就会提示一条未授权的信息

    下面的代码实现:

    此时运行程序点击按钮:

    这里会有的一个权限的申请允许则进行操作

    不允许则进行操作

    在MianActivity中添加代码:

        public void onRequestPermissionsResult(int requestCode, String[] permissions,
                        int[] grantResults) {
    
            switch (requestCode){
                case  1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                        if (ActivityCompat.checkSelfPermission(MainActivity.this,
                                Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
                            ActivityCompat.requestPermissions(MainActivity.this,new  String[]{Manifest.permission.CALL_PHONE},1);
                        }else {
                            try {
                                Intent intent = new Intent(Intent.ACTION_CALL);
                                intent.setData(Uri.parse("tel:10086"));
                                startActivity(intent);
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                        }
                    }else {
                        Toast.makeText(this,"未授权",Toast.LENGTH_LONG).show();
                    }
                    break;
                default:
            }
    
        }

    此时点击拒绝授权:

    系统会进行提示

    5.3、访问其他程序的数据

    内容提供器的用法一般分为两种:

    1、使用 现有的内容提供器来读取和操作相应程序中的数据

    2、创建自己的内容提供器给我们程序的数据提供外部访问的接口

    如果一个程序通过内容提供器对其数据提供了外部访问接口

    那么其他任何应用程序就都可以对这部分数据进行访问

    Android中的电话本、短信、媒体库等程序都提供了类似的访问的接口

    使得第三方应用程序可以充分利用这部分数据来实现更好的功能

    5.3.1、ContentResolver的基本用法

     对于每一个应用程序来说

    如果需要访问内容提供器中共享的数据

    就一定要借助ContentResolver类

    可以通过Context中的getXontentResolver()方法来获取到该类的实例

    ContentResolver中提供了一系列的方法用于对数据进行CRUD

    其中insert()方法用于添加数据

    update()方法用于更新数据

    delete()方法用于删除数据

    query()方法在、用于查询数据

    同SQLiteDatabase中的操作有些类似。

    但是在操作过程中方法都是不接收表明参数的

    使用Uri参数代替

    这个参数被称为:内容URI

    内容URI给内容提供器中数据建立了唯一标识符

    两部分组成:

    1、authority

    用于对不同的应用程序做区分的

    一般为了避免冲突,都会采用包名的方式来命名

    如:com.example.app.activity1

    2、path

    用于对同一个应用程序中不同的表进行区分

    通常在authority的后面

    如:/table1,/table2

    URI就是将authority和path的组合

    URI=com.example.app.activity1/table1

    标准的写法:

    content://com.example.app.activity/table1

    此时的URI可以鲜明的表达要访问的程序的数据表

    ContentResolver中的增删该查方法才都接受URI对象作为参数

    如果使用表名的话,系统无法得知需要访问的应用程序

    得到了URL字符串之后

    还需要将器解析成Uri对象才可以作为参数传入

    Uri uri = Uri.parse("content://com.example.app.activity/table1");

    只需要使用Uri.parse()即可将内容URI字符串解析成Uri对象

    使用这个Uri对象查询table边坡中的数据

    Cursor sursor = getContentResolver().query(

      uri,projection,selection,selectionArgs,sortOrder);

    参数解析:

    查询之后返回的仍然是一个Cursor对象

    这是就可以将数据句从Cursor对象中逐个读取出来

    读取数据是用过移动光标的位置来遍历Cursor:

    if( cursor != null){
      while(cursor.moveToNext()){
        String c1 = cursor.getString(cursor.getColumnIndex("column1"));
        String c2 = cursor.getString(cursor.getColumnIndex("column2"));
      }
    }

    添加

    ContentValues values = new ContentValues();

    values.put("c1","v1");

    values.put("c2","v2");

    getContentResolver().insert(uri,values)

    更新

    ContentValues values = new ContentValues();

    values.put("c1","v1");

    getContentResolver().update(uri,values,"c1 = ? and c2 = ?",new String [] {"v1","v2"})

    使用了selection和selectionArgs参数对其进行约束

    以防止所有的行动都会受影响

    删除

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

    5.3.2、读取联系人的测试 

    使用ListView进行对联系人的好么进行显示;

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

    在MainActivity中:

    public class MainActivity extends AppCompatActivity {
    
        ArrayAdapter<String> adapter;
        List<String> contactList = new ArrayList<>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.first_layout);
    
            ListView contactsView = (ListView) findViewById(R.id.list_view_phone);
            adapter= new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactList);
            contactsView.setAdapter(adapter);
    
            if (ActivityCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(MainActivity.this,
                  new String[]{Manifest.permission.READ_CONTACTS},1); }else { readContacts(); } } public 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 number
    =
              cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); contactList.add(name
    + " " + number); } adapter.notifyDataSetChanged(); } }catch (Exception e){ e.printStackTrace(); }finally { if(cursor != null){ cursor.close(); } } } public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode){ case 1: if (grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){ readContacts(); }else { Toast.makeText(this,"未授权",Toast.LENGTH_LONG).show(); } break; default: } } }

    在onCreate()方法中

    首先获取ListView控件,并且设置适配器

    然后运行调用运行时权限的处理READ_CONTACTS权限属于危险权限

    这里使用readContacts()方法来处理读取联系人的信息

    关于readContacts()方法:

    使用ContentResolver的query()方法来查询系统的联系人数据

    这里传入的ContactsContract.CommonDataKinds.Phone.CONTENT_URI是已经封装好的URI字符串

    在查询之后在对Cursor对象进行遍历

    将数据逐个取出

    此时还需要注册权限:

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

    此时运行程序:

    显示之前会进行权限的授权

    不授权的同时还会进行提示未授权的字样

    5.4、创建自己的内容提供器 

    参考:https://juejin.im/post/5af812966fb9a07ac85a8188#heading-9

    5.4.1、创建内容提供器的步骤 

    实现跨程序共享数据功能

    官方推荐使用内容提供器

    可以创建一类继承ContentProvider的方式创建一个自己的内容提供器

    MyContentProvider.java

    public class MyContentProvider extends ContentProvider {
        @Override
        public boolean onCreate() {
            return false;
        }
        @Nullable
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            return null;
        }
        @Nullable
        @Override
        public String getType(Uri uri) {
            return null;
        }
        @Nullable
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            return null;
        }
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            return 0;
        }
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            return 0;
        }
    }

    6个方法:

    1、onCreate()

    初始化内容提供器时候调用,通常会在这里完成对数据库的创建和升级操作

    返回未true表示内容提供器初始化成功

    返回为false表示失败

    只有当存在ContentResolver尝试访问程序中的数据,内容提供器才会初始化

    2、query()

    从内容提供器中查询数据,使用uri参数确定查询那张表,projection参数用于确定查询那些列

    selection和selectionArgs参数用于约束那些行,sortOrder参数用于对结果进行排序

    查询的结果存放在Cursor对象中

    3、insert()

    向内容提供器添加一天数据

    使用uri参数来确定要添加的表

    待添加的数据保存在values参数中

    添加完成之后,返回一个用于表示这条新纪录的URI

    4、update()

    更新内容提供器中已有的数据

    使用uri参数代表来确定更新那张数据表

    新数据保存在values参数中

    selection和selectionArgs参数用于约束更新那些行

    受影响的行数将作为返回值返回

    5、delete()

    从内容提供器中删除数据

    使用uri参数来确定删除那一张数据表的数据

    selection和selectionArgs参数用于约束删除那些行,被删除的行数将作为返回值返回

    6、getType()

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

    几乎每一个方法都带有Uri这个参数

    这个参数正是调用ContentResolver的增删改查方法时传递过来的

    上文中的URI:

    content://com.example.app.activity/table1

    可以在URI后面加上一个id

    content://com.example.app.activity/table1/1

    表示调用放期望访问的是:

    com.example.app.activity的程序的table1数据表中的id为1的数据

    注意:

    * :表示匹配任意长度的字符

    # :表示匹配任意长度的数字

    com.example.app.activity/table/*

    com.example.app.activity/table/#

    在借助UriMatcher这个类可以轻松匹配内容URI的功能

    提供了addURI()方法接受三个参数

    分别可以把authoritypath自定义的代码传进去

    当调用UriMatcher的match()方法时,就可以把一个Uri对象传入

    返回值是某个匹配这个uri对象所对应的自定义的代码

     

    在其中新增4个整形常量

    TABLE_DIR表示访问table1表中所有数据

    TABLE_ITEM表示访问table1中的单条数据

    在静态代码块中创建了UriMatcher的实例

    调用addURI()方法,将期望匹配的内容传递进入,这里传递的路径参数可以使用通配符的

    在query()方法调用的时候就会通过UriMatcher的match()方法传入的Uri对象进行匹配

    发现UriMatcher中某个URI格式成功匹配了该Uri对象

    则会返回自定义的代码

    在对数据表中进行相应的操作即可

    getType()方法

    是所有的内容提供器必须提供的一个方法

    用于获取Uri对象所对应的MIME类型

    一个内容URI所对应的MIME字符串由3部分组成:

    此时可以进行完善:

     此时一个完整的内容提供器就完成了现在任何一个应用程序都可以使用COntentResolver来访问程序中的数据

    关于:如何保存隐私数据不会泄露的问题?

    内容提供器提供了良好的机制

    所有的CRUD操作都一定要匹配到相应的URI格式才能进行的

    不能向UriMatcher中添加隐私数据URI

    所以这部分数据根本无法被外部程序访问到,安全性问题也就不存在了

    5.4.2、实现跨程序数据共享

     数据持久化过程中的数据进行实现

    通过内容提供器来给他加入外部访问接口

    注意:

    跨程序访问时不能直接使用Toast

    右键--->new --->Other--->Content Provider

    DAtabasetProvider.java

    public class DatabasetProvider extends ContentProvider {
    
    
        public static final int BOOK_DIR = 0;
        public static final int BOOK_ITEM = 1;
    
        public static final String AUTHORITY = "com.example.ccrr.applicationtwo";
        private MyDatabaseHelper myDatabaseHelper;
    
        public static UriMatcher uriMatcher;
    
        static {
            uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
            uriMatcher.addURI(AUTHORITY,"Book",BOOK_DIR);
            uriMatcher.addURI(AUTHORITY,"Book/#",BOOK_ITEM);
    
        }
    
        public DatabasetProvider() {
        }
        @Override
        public boolean onCreate() {
           myDatabaseHelper = new MyDatabaseHelper(getContext(),"bookstore.db",null,1);
            return true;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
                SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
            int deleteRows = 0;
            switch (uriMatcher.match(uri)){
                case BOOK_DIR:
                    deleteRows = db.delete("Book",selection,selectionArgs);
                    break;
                case BOOK_ITEM:
                    String bookId = uri.getPathSegments().get(1);
                    deleteRows=db.delete("Book","id = ?",new String[]{bookId});
            }
            return  deleteRows;
        }
    
        @Override
        public String getType(Uri uri) {
    
            switch (uriMatcher.match(uri)){
                case BOOK_DIR:
                    return "vnd.android.cursor.dir/vnd.com.example.ccrr.applicationtwo.Book";
                case BOOK_ITEM:
                    return "vnd.android.cursor.dir/vnd.com.example.ccrr.applicationtwo.Book";
            }
            return  null;
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues values) {
    
            SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
            Uri uriReturn = null;
            switch (uriMatcher.match(uri)){
                case BOOK_DIR:
                case BOOK_ITEM:
                    long newBookId = db.insert("Book",null,values);
                    uriReturn = Uri.parse("content://"+AUTHORITY+"/Book"+newBookId);
                    break;
            }
            return  uriReturn;
        }
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                            String[] selectionArgs, String sortOrder) {
           //查询数据
            SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();
            Cursor cursor = null;
            switch (uriMatcher.match(uri)){
                case BOOK_DIR:
                    cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
                    break;
                case BOOK_ITEM:
                    String bookId = uri.getPathSegments().get(1);
                    cursor = db.query("Book",projection,"id = ?",new String[]{bookId},null,null,sortOrder);
                    break;
                default:
                    break;
            }
            return cursor;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection,
                          String[] selectionArgs) {
    
            SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
            int updateRows = 0;
            switch (uriMatcher.match(uri)){
                case  BOOK_DIR:
                    updateRows = db.update("Book",values,selection,selectionArgs);
                    break;
                case BOOK_ITEM:
                    String  bookId = uri.getPathSegments().get(1);
                    updateRows = db.update("Book",values,"id = ?" , new String[] {bookId});
                    break;
            }
            return updateRows;
    
        }
    }

    布局:

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/qqq"
        android:text="add"/>
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/query_pro"
            android:text="query"/>
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/update"
            android:text="update"/>
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/delete"
            android:text="delete"/>

    MainActivity:

    public class MainActivity extends AppCompatActivity {
    
        private MyDatabaseHelper myDatabaseHelper;
        private  String bewId;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.first_layout);
    
    
            myDatabaseHelper=new MyDatabaseHelper(this,"bookstore.db",null,1);
            Button button = (Button) findViewById(R.id.qqq);
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                 Uri uri = Uri.parse("content://com.example.ccrr.applicationtwo/Book");
                    ContentValues values = new ContentValues();
                    values.put("author","Mr");
                    values.put("price",12.3);
                    values.put("name","Java");
                    Uri newNui = getContentResolver().insert(uri,values);
    
                }
            });
    
            Button query = (Button) findViewById(R.id.query_pro);
            query.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Uri uri = Uri.parse("content://com.example.ccrr.applicationtwo/Book");
                    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"));
                           String author = cursor.getString(cursor.getColumnIndex("author"));
    
                           Log.d("name",name );
                           Log.d("price",price );
                           Log.d("author", author);
                       }
                        cursor.close();
                    }
    
                }
            });
    
            final Button updata = (Button) findViewById(R.id.update);
            updata.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Uri uri = Uri.parse("content://com.example.ccrr.applicationtwo/Book");
                    ContentValues values = new ContentValues();
                    values.put("name","Android");
                    values.put("price",1.2);
                    getContentResolver().update(uri,values,null,null);
                }
            });
    
            Button delete = (Button) findViewById(R.id.delete);
            delete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Uri uri = Uri.parse("content://com.example.ccrr.applicationtwo/Book");
                    getContentResolver().delete(uri,null,null);
                }
            });
        }
    }

      <provider
                android:name=".DatabasetProvider"
                android:authorities="com.example.ccrr.applicationtwo"
                android:enabled="true"
                android:exported="true"></provider>

    小小的测试:

  • 相关阅读:
    高中数学相关的专业术语
    数学-高数2
    python+unittest+xlrd+request搭建API测试框架
    接口自动化,断言方法,深度定位错误
    python+requests+unittest API接口测试
    python+unittest框架整理(一点点学习前辈们的封装思路,一点点成长。。。)
    学习python的第一个小目标:通过requests+xlrd实现简单接口测试,将测试用例维护在表格中,与脚本分开。
    队列 —— 先入先出的数据结构
    卷积神经网络的简单可视化
    HOG 特征提取算法(实践篇)
  • 原文地址:https://www.cnblogs.com/Mrchengs/p/10699519.html
Copyright © 2020-2023  润新知