Android复习——ContentProvider

知识

1.contentprovider是什么:四大组件之一,它可以实现跨程序数据共享
在这里插入图片描述

2.写的很好

练习权限获取

读取联系人显示在listview上的过程:
1.初始化
2.加载适配器
3.判断权限
没权限 申请
有权限 直接读联系人

public class MainActivity extends AppCompatActivity {
    private static final int GO_READ_CONTACTS = 1;
    private ListView lv_main;
    private ArrayAdapter<String> adapter;
    private  List<String> list = new ArrayList<>();;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1.初始化
        lv_main = findViewById(R.id.lv_main);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1
        ,list);
        //2.加载适配器
        lv_main.setAdapter(adapter);
         //3.判断权限
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED){//没权限 申请
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},GO_READ_CONTACTS);//GO_READ_CONTACTS自定义后期写查询申请权限结果的时候,作为标识符使用
        }else{//有权限  直接读联系人
            readContact();
        }
 
 
    }
    //
    private void readContact() {//读取sqlite:联系人
        Cursor curso = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null
                , null, null, null);
        if(curso != null){
            while (curso.moveToNext()){
                String name = curso.getString(curso.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String number = curso.getString(curso.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                Log.d("main", "readContact: " + name  +"  "+ number);
//得到数据之后放在list中,后期放入lv
                list.add(name + "\n" + number);
 
            }
        }
//放入lv把数据
        adapter.notifyDataSetChanged();
//关流
        curso.close();
    }
 
//对于申请权限的结果 做出的动作
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case GO_READ_CONTACTS:
                if(grantResults.length >= 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){//同意了
                    readContact();
                }else{
                    Toast.makeText(this,"没权限",Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }
}

实现跨程序数据共享

1.databasetest中:创建表 extends SQLiteOperhelper:
目的:创建表,初始化
1.先继承SQLiteOpenHelper创建类MySQLiteOpenHelper
2.创建表的字符串 注意:public static final
3.有参构造,不用改动
4.oncreate中建表 execSQL 给表起名字
5…不用写onUpgrade

2.databasetest中:创建contentprovider SQLiteDatabase通过SQLiteOperhelper读取到表,然后对表进行操作
目的:本来是SQLiteDatabase来对表进行操作,但是这个不能跨程序,所以引入contentprovider,进行操作,
但是contentprovider是通过URI来进行操作的,所以改写CRUD,从而达到改写表
1.UriMatcher初始化,和建立,传入AUTHORITY(provider名字),path(book/#或者book)和标识符id(后期CURD知道是哪种path情况)
2.manifest文件中加入provider组件name是类名,authority是provider名字
3.数据库打开帮助者mySQLiteOpenHelper:建立 创建表文件名
4.重写query方法:
1.获得数据库数据sqLiteDatabase,通过数据库打开帮助者mySQLiteOpenHelper的get方法
2.urimatcher对传入的uri匹配
3.查整个数据时候:数据库数据sqLiteDatabase调用自己方法query查询(传入已有参数)
4.查询单条数据的时候:通过传入参数知道uri要查询哪条记录,通过getPathSegments将uri路径分配,得到结果列表1的位置放的就是id
5.数据库数据现在知道要对哪个id进行操作了,调用数据库数据的query方法,传入id查询
//…
3.databasetest中和providertest中:布局
4.providertest中:写主方法使用CRUD
初始化btn
insert方法:public Uri insert()返回的是最新uri
解析uri(uri字符串自己创建)
通过contentreslover得到自己写的contentprovider改动过的CRUD方法,调用insert,返回的是最新uri(id地址)
分析(getpathsegement的get1)并得到最新uri得到改动位置的最新id
query方法:
解析uri,查询时查全表uri就如下面写的,查询单条数据就加book/#
先判断cursor不为null:
然后移动cursor指针,遍历数据通过cursor。get方法(里面再是cursor。get列名字方法)
关流
update方法:
解析uri,如果要更新自己插入的那条数据的话,uri就要加newid
update这方法不用返回啥,通过uri升级就行
delete方法:
解析uri,如果要删除自己创建的那个数据的话,uri要加id
delete这方法不用返回啥,通过uri升级就行

public class MyContentProvider extends ContentProvider {

   private MySQLiteOpenHelper mySQLiteOpenHelper;//SQLite 数据库Open 打开Helper 帮助者,SQLiteOpenHelper的get流得到数据库数据
   private SQLiteDatabase sqLiteDatabase;//对表操作时候用的,CRUD中调用
   //private ContentProvider contentProvider;//
   public static UriMatcher uriMatcher;//CRUD时候分辨配对uri时使用的
   public static final String AUTHORITY = "com.example.newdatabasetest.provider";// manifest  public

   public static final int BOOK_ALL = 1;//public
   public static final int BOOK_ITEM = 2;

   static {//UriMatcher初始化,和建立
       uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
       uriMatcher.addURI(AUTHORITY,"book",BOOK_ALL);
       uriMatcher.addURI(AUTHORITY,"book/#",BOOK_ITEM);
   }
   //1.数据库打开帮助者mySQLiteOpenHelper:建立 创建表文件名
   @Override
   public boolean onCreate() {
       mySQLiteOpenHelper = new MySQLiteOpenHelper(getContext(),
               "BOOKSTORE.db",null,1);
       return true;
   }
   //2.重写query方法:
   @Nullable
   @Override
   public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
                       @Nullable String selection, @Nullable String[] selectionArgs,
                       @Nullable String sortOrder) {
       //2.1获得数据库数据sqLiteDatabase,通过数据库打开帮助者mySQLiteOpenHelper的get方法
       sqLiteDatabase = mySQLiteOpenHelper.getReadableDatabase();
       //2.2 urimatcher对传入的uri匹配。
       Cursor cursor = null;
       switch (uriMatcher.match(uri)){
           case BOOK_ALL:
                /*cursor = sqLiteDatabase.query("BOOK",null,null
                        ,null,null,null,null);
                */
               //查整个数据时候:数据库数据sqLiteDatabase调用自己方法query查询(传入已有参数)
               cursor = sqLiteDatabase.query("BOOK",projection,selection
                       ,selectionArgs,null,null,sortOrder);
               break;
           case BOOK_ITEM:
               /*cursor = sqLiteDatabase.query("BOOK",projection,selection,selectionArgs
               ,null,null,sortOrder);*/
               //查询单条数据的时候:通过传入参数知道uri要查询哪条记录,通过getPathSegments将uri路径分配,得到结果列表1的位置放的就是id
               String id = uri.getPathSegments().get(1);
               //数据库数据现在知道要对哪个id进行操作了,调用数据库数据的query方法,传入id查询
               cursor = sqLiteDatabase.query("BOOK",projection,"id = ?"
                       ,new String[]{id},null,null,sortOrder);
               break;
           default:
               break;
       }
       //cursor.close();
       return cursor;
   }
//3.重写getType方法:
   @Nullable
   @Override
   public String getType(@NonNull Uri uri) {
       switch (uriMatcher.match(uri)){
           case BOOK_ALL:
               //返回的字符串为:vnd.android.cursor.dir(填dir或者是item,item这是整个表的时候用dir,是单挑数据时候用item)/vnd.com.example.newdatabasetest.provider(provider名字).book(表名字,不是数据库名字)
               return "vnd.android.cursor.dir/vnd.com.example.newdatabasetest.provider.book";
           case BOOK_ITEM:
               return "vnd.android.cursor.item/vnd.com.example.newdatabasetest.provider.book";
       }
       return null;
   }
//4.重写insert方法
   @Nullable
   @Override
   public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
       //通过数据库打开帮助者的get方法得到数据
       sqLiteDatabase = mySQLiteOpenHelper.getWritableDatabase();
       Uri uri1 = null;//
       //uriMatcher对参数uri进行匹配,不同情况不同处理
       switch (uriMatcher.match(uri)){
           case BOOK_ALL:
               //插入全表,不可能,没操作
               //return null;
               //break;
           case BOOK_ITEM:
               //插入单条信息:插得肯定是最后一个位置,所以不用分析uri,直接数据库数据调用insert方法
               //因为contengprovider的insert方法返回的是一个新数据插入后的最新uri,所以sqLiteDatabase的insert返回的long id
               //放在uri最后,构建出新的uri
               long newbookid = sqLiteDatabase.insert("BOOK", null, values);
               uri1 = Uri.parse("content://"+ AUTHORITY +"/book/"+newbookid);
               //return uri1;
               break;
           default:
               //return null;
               break;
       }
       return  uri1;
   }
//5.delete重写:
   @Override
   public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
       //数据库打开帮助者的get方法获取数据库数据
       sqLiteDatabase =  mySQLiteOpenHelper.getWritableDatabase();
       int del = 0;
       //匹配uri,不同情况不同对待
       switch (uriMatcher.match(uri)){
           case BOOK_ALL:
               //删除全表时:数据库数据直接调取delete方法,传入已有参数,不用uri
               del = sqLiteDatabase.delete("BOOK", selection, selectionArgs);
               //return del;
               break;
           case BOOK_ITEM:
               //删除单条数据:通过uri(getPathSegments得到删除的id),知道要删啥,然后调用数据库数据的delete方法删除
               //del = sqLiteDatabase.delete("BOOK",selection,selectionArgs);
               //return del;

               String id = uri.getPathSegments().get(1);
               del = sqLiteDatabase.delete("BOOK","id = ?", new String[]{id});
               break;
           default:
               break;
       }
       return del;
   }
//6.update方法:


   @Override
   public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
       //数据库打开帮助者的get方法得到数据
       sqLiteDatabase = mySQLiteOpenHelper.getWritableDatabase();
       int upid =  0;
       //uriMatcher匹配uri
       switch (uriMatcher.match(uri)){
           case BOOK_ALL://这之前啥也没写
               //升级全表:数据库数据的update方法传入已有参数
               upid = sqLiteDatabase.update("BOOK",values,selection,selectionArgs);
               //return upid;
               break;
           case BOOK_ITEM:
               //单条数据:得到升级的id是啥,数据库数据的update方法进行升级
               String id = uri.getPathSegments().get(1);
               upid = sqLiteDatabase.update("BOOK",values,"id = ?", new String[]{id});
               break;
               //return upid;
       }
       return upid;
   }
}

调用CRUD方法
初始化btn
insert方法:public Uri insert()返回的是最新uri
解析uri(uri字符串自己创建)
通过contentreslover得到自己写的contentprovider改动过的CRUD方法,调用insert,返回的是最新uri(id地址)
分析(getpathsegement的get1)并得到最新uri得到改动位置的最新id
query方法:
解析uri,查询时查全表uri就如下面写的,查询单条数据就加book/#
先判断cursor不为null:
然后移动cursor指针,遍历数据通过cursor。get方法(里面再是cursor。get列名字方法)
关流
update方法:
解析uri,如果要更新自己插入的那条数据的话,uri就要加newid
update这方法不用返回啥,通过uri升级就行
delete方法:
解析uri,如果要删除自己创建的那个数据的话,uri要加id
delete这方法不用返回啥,通过uri升级就行

public class MainActivity extends AppCompatActivity  {
    private Button button_insert;
    private Button button_delete;
    private Button button_update;
    private Button button_query;
    //private MyContentProvider myContentProvider;
    //private MySQLiteOpenHelper mySQLiteOpenHelper;
    private SQLiteDatabase sqLiteDatabase;
    private String newid;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();//初始化btn
        button_insert.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //添加,public Uri insert()返回的是最新uri
                //第一步:解析uri(uri字符串自己创建)
                Uri uri = Uri.parse("content://com.example.newdatabasetest.provider/book");
                ContentValues values = new ContentValues();
                values.put("author","ha");
                values.put("price",1.1);
                values.put("pages",1);
                values.put("name","haha");
                //myContentProvider.insert(uri,values); 思路错了
                //通过contentreslover得到自己写的contentprovider改动过的CRUD方法,调用insert,返回的是最新uri(id地址)
                Uri newuri = getContentResolver().insert(uri, values);
                newid = newuri.getPathSegments().get(1);//分析最新uri得到改动位置的最新id
            }
        });
        button_query.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //解析uri,查询时查全表uri就如下面写的,查询单条数据就加book/#
                Uri uriqu = Uri.parse("content://com.example.newdatabasetest.provider/book");
                //。。
                Cursor cursor = getContentResolver().query(uriqu, null, null, null);
                //通过先判断cursor不为null:
                if(cursor != null){
                    //然后移动cursor指针,遍历数据通过cursor。get方法(里面再是cursor。get列名字方法)
                    while (cursor.moveToNext()){
                        String author = "author:"
                                + cursor.getString(cursor.getColumnIndex("author"))+"\n"
                                +"price:"
                                + cursor.getString(cursor.getColumnIndex("price"))+"\n"
                                +"pages:"
                                + cursor.getString(cursor.getColumnIndex("pages"))+"\n"
                                +"name:"
                                + cursor.getString(cursor.getColumnIndex("name"))+"\n";
                        Log.d("niu", author);
                    }
                }
                cursor.close();//记得关流
            }
        });
        button_update.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //要更新自己插入的那条数据的话,uri就要加newid
                Uri uriup = Uri.parse("content://com.example.newdatabasetest.provider/book/"+newid);
                ContentValues valuesup = new ContentValues();
                valuesup.put("author"," haha");
                valuesup.put("price",1.2);
                valuesup.put("pages",2);
                valuesup.put("name","hahahaha");
                //这方法不用返回啥,通过uri升级就行
                getContentResolver().update(uriup, valuesup, null, null);
 
            }
        });
        button_delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //要删除自己创建的那个数据的话,uri要加id
                Uri uridel = Uri.parse("content://com.example.newdatabasetest.provider/book/"+newid);
                getContentResolver().delete(uridel,null,null);
 
            }
        });
    }
 
    private void init() {
        button_insert = findViewById(R.id.button_insert);
        button_query = findViewById(R.id.button_query);
        button_update = findViewById(R.id.button_update);
        button_delete = findViewById(R.id.button_delete);
    }

}

面试

1.为什么设计这个组件?Android 设计 ContentProvider 的目的是什么呢?
1.封装:对数据采用统一接口,将一切得数据不用管是什么类型,都可以接收到。并且在数据来源改变时候也不需要对我们代码进行改变
2.他其中有数据更新机制的功能,如果改了数据,每个订阅的都能对此做出相应动作
3.可以跨程序数据共享

2.如何访问自定义 ContentProvider
定义 ContentProvider

在这里插入图片描述

ContentResolver 接口的 notifyChange 函数来通知那些注册了监控特定 URI的ContentObserver 对象,使得它们可以相应地执行一些处理。
ContentObserver 可以通过 registerContentObserver 进行注册。
通过 ContentProvider 的 Uri 访问开放的数据。
2.1ContenResolver 对象通过 Context 提供的方法 getContenResolver() 来获得。
2.2ContenResolver 提供了以下方法来操作:insert delete update query 这些方法分别会调用 ContenProvider 中与之对应的方法并得到返回的结果。

3.通过 ContentResolver 获取 ContentProvider 内容的基本步骤?(咋用query)
1.自定义uri字符串,解析uri
2.getcontentresolver()得到ContentResolver对象,在调用query方法。
3.query方法返回一个cusor对象,对他先进行判断是否为null,然后在移动指针,拿取数据

4.ContentProvider 是如何实现数据共享的?(咋写的这个继承类)
1.自定义一个类实现ContentProvider
2.重写CRUD和gettype五个方法
3.重写与有参构造
4.在功能清单文件上注册provider组件

5.为什么要用 ContentProvider ?它和 sql 的实现上有什么差别?
1.cp可以CRUD本地和其他应用数据,但是sql只能修改本程序下数据
2.cp对屏蔽数据存储的细节,内部实现完全透明

6.uri介绍
1.他的组成是“content:(标准前缀)//provider name(uri标识)/table name(/#)”
2.通过urimatcher来匹配uri,来对不同情况就行CRUD

7.如何访问 asserts 资源(main下面自己建立)目录下的数据库?
把数据复制到/data/data/packagename/databases(手机里的)下就可以操作了

8.多个进程同时调用一个 ContentProvider 的 query 获取数据,ContentPrvoider 是如何反应的呢?//不太懂
cp在调用CRUD的时候使用的是CP进程的线程池,不是主线程,这个线程池有binder维护,其实就是使用的每个进程的binder线程池

9.运行在主线程的 ContentProvider 为什么不会影响主线程的 UI 操作?ContentProvider 为什么不会影响主线程的UI操作?
ContentProvider 的 onCreate() 是运行在 UI 线程的,而 query() ,insert() ,delete() ,update() 是运行在线程池中的工作线程的,所以调用这些个方法并不会阻塞 ContentProvider 所在进程的主线程,但可能会阻塞调用者所在的进程的 UI 线程!所以,调用者的 ContentProvider 的操作仍然要放在子线程中去做。虽然直接的 CRUD 的操作是在工作线程的,但系统会让你的调用线程等待这个异步的操作完成,你才可以继续线程之前的工作。

10.对外提供数据共享,那么如何限制对方的使用呢?
android:exported这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。
如果设置为 true,则能够被调用或交互,否则不能。
设置为 false 时,只有同一个应用程序的组件或带有相同用户 ID 的应用程序才能启动或绑定该服务。
如果只需要对同一个签名的其它应用开放 ContentProvider ,则可以设置 signature 级别的权限。

11.如果我们只需要开放部份的 URI 给其他的应用访问呢?
可以参考 Provider 的 URI 权限设置,只允许访问部份 URI

<provider android:name="ContactsProvider2"
    android:authorities="contacts;com.android.contacts"
    android:label="@string/provider_label"
    android:multiprocess="false"
    android:exported="true"
    android:grantUriPermissions="true"
    android:readPermission="android.permission.READ_CONTACTS"
    android:writePermission="android.permission.WRITE_CONTACTS">
    <path-permission
//Content Provider 数据子集的 URI 路径的开头部分。 可以对 以此为路径前缀的数据 进行授权
            android:pathPrefix="/search_suggest_query"
            android:readPermission="android.permission.GLOBAL_SEARCH" />
    <path-permission
            android:pathPrefix="/search_suggest_shortcut"
            android:readPermission="android.permission.GLOBAL_SEARCH" />
    <path-permission
            android:pathPattern="/contacts/.*/photo"
            android:readPermission="android.permission.GLOBAL_SEARCH" />
    <grant-uri-permission android:pathPattern=".*" />
</provider>
 

12.ContentProvider 接口方法运行在哪个线程中呢?讨论得好好!
查看android:multiprocess 的属性,默认为false:表示只有一个单例CP,所有客户端访问的是一个对象,此时的话,他的接口方法运行在CP线程池中。ture的话:为每一个访问该CP的进程都创建一个实例,所以此时谁访问的他,他方法就在哪个线程

13.如果我要问每个 ContentProvider 的操作是在哪个线程中运行的呢?( 其实我们关心的是 UI 线程和工作线程 )比如我们在UI线程调用getContentResolver().query查询数据,而当数据量很大时(或者需要进行较长时间的计算)会不会阻塞UI线程呢?
ContentProvider 和调用者在同一个进程,ContentProvider 的方法( query/insert/update/delete 等 )和调用者在同一线程中;
ContentProvider 和调用者在不同的进程,ContentProvider 的方法会运行在它自身所在进程的一个 Binder 线程中。
但是,注意这两种方式在 ContentProvider 的方法没有执行完成前都会 blocked 调用者。所以你应该知道这个上面这个问题的答案了吧。

14.ContentProvider 是如何在不同应用程序之间传输数据的?
一个应用进程有 16 个 Binder 线程去和远程服务进行交互,而每个线程可占用的缓存空间是 128KB 这样,超过会报异常。ContentResolver 虽然是通过 Binder 进程间通信机制打通了应用程序之间共享数据的通道,但 ContentProvider 组件在不同应用程序之间传输数据是基于匿名共享内存机制来实现的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值