首页 > 其他分享 >51.《一篇浅浅的搞懂Android四大组件之一内容提供者和观察者》

51.《一篇浅浅的搞懂Android四大组件之一内容提供者和观察者》

时间:2024-06-08 09:23:01浏览次数:21  
标签:51 uri public new import 搞懂 null Android android

一 内容提供者

背景:
之前提到过内容提供者就是在访问数据的时候 那么它因何诞生
我们之前的数据访问像SQLite之类访问都是在当前应用程序访问
那则么能行 而其他应用程序之间的访问 就需要这一组件的帮助

image
画的有点粗糙
但大致就是这样工作的
B通过ContentResolver类访问A中ContentProvider暴露(共享)的数据
A通过ContentResolver类将结果返回给B
1.ContentResolver类 就是中介
2.ContentProvider实质上就是内容提供者
3.通过 uri 唯一标识 建立连接

那什么是uri呢?

image
这个就是Android中内容访问的 uri固定格式
content:// 包名 路径
当然当你访问Android中自有的东西例如通讯录人家的uri都封装好了直接用就行
image


那看看程序中的内容提供者是怎样的

image

image

是的就是一个java继承类
但是你会发现清单文件中出现了这个:

image


那光创建了 咋访问数据呢并且显示出来呢
简简单单三步走:
1.通过parse()方法解析uri
Uri uri=Uri.parse(你定义的或者自带的 uri标识);

2.query()方法查询数据(需要先创建ContentResolver对象)
ContentResolver resolver=context.getContentResolver();
Cursor cursor=resolver.query(5个参数)

3.while循环遍历游标
while(cursor.moveToNext()){
	cursor对应的getInt()或者getString()方法获取然后输出
}
cursor.close()关闭释放

image
query中的五个参数
1.uri
2.查询内容
3.查询条件
4.配合参数
5.升序或者降序排序

这里还有多学一招 就是多个数据时 要使用到UriMatcher类


再来看看内容提供者的一个例子 读取手机通讯录

这里就不写layout文件美化了 也不录入数据
就直接在手机中添加联系人 然后log打印出来 主要看功能
后面一个综合例子 会写的

package com.example.four_content;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

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

public class ContactActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getPermissions();
    }
    //关于危险权限 如申请(位置 日历 照相机 联系人 存储卡 传感器 麦克风 电话 短信)权限
    //不仅需要在清单文件中静态申请权限 还要在代码中动态申请权限
    //获取手机通讯录数据之前 需要申请读取手机通讯录的权限 所以需要在getPermissions()方法中申请获取权限
    // 并重写onRequestPermissionsResult()方法 读取权限是否申请成功信息
    String[] permissions;

    public void getPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {   //版本是否大于6.0 Android 6.0开始,应用需要在运行时请求一些敏感权限
            //初始化permissions数组,仅包含一个权限:读取联系人权限
            permissions = new String[]{"android.permission.READ_CONTACTS"};
            //创建一个ArrayList来存储尚未被授予的权限
            ArrayList<String> list = new ArrayList<>();
            //遍历permissions数组,并检查每个权限是否已经被授予
            for (int i = 0; i < permissions.length; i++) {
                if (ActivityCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
                    list.add(permissions[i]);

                    //如果list中有未被授予的权限,则请求这些权限。1是请求代码,用于在onRequestPermissionsResult方法中识别这个特定的权限请求
                    if (list.size() > 0) {
                        ActivityCompat.requestPermissions(this, list.toArray(new String[list.size()]), 1);
                    } else {
                        setData();
                    }
                } else {
                    setData();
                }
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //检查请求代码是否为1,即之前getPermissions()方法中请求的权限
        if (requestCode == 1) {
            for (int i = 0; i < permissions.length; i++) {
                //如果请求的是读取联系人权限,并且该权限已经被授予,则显示“读取成功”的Toast消息
                if (permissions[i].equals("android.permission.READ_CONTACTS") && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "读取成功", Toast.LENGTH_SHORT).show();
                    setData();
                } else {
                    Toast.makeText(this, "读取失败", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
    public void setData(){
        ContentResolver contentResolver = getContentResolver();
        Cursor cursor = contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
        while (cursor.moveToNext()){
            @SuppressLint("Range") String id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
            @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            @SuppressLint("Range") int anInt = Integer.parseInt(cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)));
            if (anInt>0){
                Cursor cursor1 = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + id, null, null);
                while (cursor1.moveToNext()){
                    @SuppressLint("Range") String num = cursor1.getString(cursor1.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    Log.i("高远", "setData: "+num);
                }
                cursor1.close();
            }
        }
        cursor.close();
    }



}

清单文件中加入:静态访问权限
    <uses-permission android:name="android.permission.READ_CONTACTS" />

image
log打印
image

注意注意 上述代码是有误的

参考:https://www.cnblogs.com/tiancaige/p/10051563.html

就是遍历那里 
contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + id, null, null);
selectiong "="  查询条件要加引号


二 内容观察者

背景:
那当访问其他应用程序数据时候 共享的数据是否发生变化了
我们就要使用到内容观察者ContentObserver来通过指定的uri来观察

image
1.当B操作A中数据发生变化时候 先ContentProvider调用ContentResolver的notifyChange()方法 发送消息中心数据变化的信息
2.C(内容观察者)得知消息中心数据变化时 触发ContentObserver的onChange()方法

关于内容观察者如何使用:
1.创建内容观察者 实质上是写一个继承ContentObserver的java类
2.注册内容观察者 通过ContentResolver的registerContentObserver()方法注册
3.取消注册 在onDestroy()方法中通过ContentResolver的unregisterContentObserver()方法


来看一个综合例子 监测数据的变化

三个玩意:
1.A内容提供者
2.B操作A
3.C内容观察者

跳过layout布局了 大致样式:
image

1.创建一个SQLite数据库
package com.example.contents;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class Myhelper extends SQLiteOpenHelper {
    //构造方法,调用该方法创建一个person.db数据库
    public Myhelper(Context context) {
        super(context, "gao.db", null, 1);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建该数据库的同时新建一个info表,表中有_id,name这两个字段
        db.execSQL("create table info(_id integer primary key autoincrement, name varchar(20))");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

2.编写A ContentProvider内容提供者
package com.example.contents;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

public class MyContentProvider extends ContentProvider {
    //定义一个uri路径的匹配器,如果路径匹配不成功返回-1
    private static UriMatcher mUriMatcher = new UriMatcher(-1);
    private static final int SUCCESS = 1; //匹配路径成功时的返回码
    private Myhelper helper;     //数据库操作类的对象
    //添加路径匹配器的规则
    static {
        mUriMatcher.addURI("com.example.contents", "info", SUCCESS);
    }
    @Override
    public boolean onCreate() { //当内容提供者被创建时调用
        helper = new Myhelper(getContext());
        return false;
    }
    /**
     * 查询数据操作
     */
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        //匹配查询的Uri路径
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            SQLiteDatabase db = helper.getReadableDatabase();
            return db.query("info", projection, selection, selectionArgs,
                    null, null, sortOrder);
        } else {
            throw new IllegalArgumentException("路径不正确,无法查询数据!");
        }
    }
    /**
     * 添加数据操作
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            SQLiteDatabase db = helper.getReadableDatabase();
            long rowId = db.insert("info", null, values);
            if (rowId > 0) {
                Uri insertedUri = ContentUris.withAppendedId(uri, rowId);
                //提示数据库的内容变化了
                getContext().getContentResolver().notifyChange(insertedUri, null);
                return insertedUri;
            }
            db.close();
            return uri;
        } else {
            throw new IllegalArgumentException("路径不正确,无法插入数据!");
        }
    }
    /**
     * 删除数据操作
     */
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            SQLiteDatabase db = helper.getWritableDatabase();
            int count = db.delete("info", selection, selectionArgs);
            //提示数据库的内容变化了
            if (count > 0) {
                getContext().getContentResolver().notifyChange(uri, null);
            }
            db.close();
            return count;
        } else {
            throw new IllegalArgumentException("路径不正确,无法随便删除数据!");
        }
    }
    /**
     * 更新数据操作
     */
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        int code = mUriMatcher.match(uri);
        if (code == SUCCESS) {
            SQLiteDatabase db = helper.getWritableDatabase();
            int count = db.update("info", values, selection, selectionArgs);
            //提示数据库的内容变化了
            if (count > 0) {
                getContext().getContentResolver().notifyChange(uri, null);
            }
            db.close();
            return count;
        } else {
            throw new IllegalArgumentException("路径不正确,无法更新数据!");
        }
    }
    //返回MIME类型的数据 文档文件字节流格式 (例如windows中的.txt .jpg文件)
    @Override
    public String getType(Uri uri) {
        return null;
    }
}

3.编写B 操作A中共享的数据
package com.example.contents;


import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class BmakeA_Activity extends AppCompatActivity implements
        View.OnClickListener {
    private ContentResolver resolver;
    private Uri uri;
    private ContentValues values;
    private Button btnInsert;
    private Button btnUpdate;
    private Button btnDelete;
    private Button btnSelect;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contents);
        initView(); //初始化界面
        createDB(); //创建数据库
    }

    private void initView() {
        btnInsert = findViewById(R.id.btn_insert);
        btnUpdate = findViewById(R.id.btn_update);
        btnDelete = findViewById(R.id.btn_delete);
        btnSelect = findViewById(R.id.btn_select);
        btnInsert.setOnClickListener(this);
        btnUpdate.setOnClickListener(this);
        btnDelete.setOnClickListener(this);
        btnSelect.setOnClickListener(this);
    }

    private void createDB() {
        //创建数据库并向info表中添加3条数据
        Myhelper helper = new Myhelper(this);
        SQLiteDatabase db = helper.getWritableDatabase();
        for (int i = 0; i < 3; i++) {
            ContentValues values = new ContentValues();
            values.put("name", "itcast" + i);
            db.insert("info", null, values);
        }
        db.close();
    }

    @Override
    public void onClick(View v) {
        //得到一个内容提供者的解析对象
        resolver = getContentResolver();
        //获取一个Uri路径
        uri = Uri.parse("content://com.example.contents/info");
        //新建一个ContentValues对象,该对象以key-values的形式来添加数据到数据库表中
        values = new ContentValues();
        if (v.getId() == R.id.btn_insert) {
            Random random = new Random();
            values.put("name", "add_itcast" + random.nextInt(10));
            Uri newuri = resolver.insert(uri, values);
            Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
            Log.i("数据库应用", "添加");
        }
        if (v.getId() == R.id.btn_delete) {
            //返回删除数据的条目数
            int deleteCount = resolver.delete(uri, "name=?",
                    new String[]{"itcast0"});
            Toast.makeText(this, "成功删除了" + deleteCount + "行",
                    Toast.LENGTH_SHORT).show();
            Log.i("数据库应用", "删除");
        }
        if (v.getId() == R.id.btn_select) {
            List<Map<String, String>> data = new ArrayList<Map<String, String>>();
            //返回查询结果,是一个指向结果集的游标
            Cursor cursor = resolver.query(uri, new String[]{"_id", "name"},
                    null, null, null);
            //遍历结果集中的数据,将每一条遍历的结果存储在一个List的集合中
            while (cursor.moveToNext()) {
                Map<String, String> map = new HashMap<String, String>();
                map.put("_id", cursor.getString(0));
                map.put("name", cursor.getString(1));
                data.add(map);
            }
            //关闭游标,释放资源
            cursor.close();
            Log.i("数据库应用", "查询结果:" + data.toString());
        }
        if (v.getId() == R.id.btn_update) {
            //将数据库info表中name为itcast1的这条记录更改为name是update_itcast
            values.put("name", "update_itcast");
            int updateCount = resolver.update(uri, values, "name=?",
                    new String[]{"itcast1"});
            Toast.makeText(this, "成功更新了" + updateCount + "行",
                    Toast.LENGTH_SHORT).show();
            Log.i("数据库应用", "更新");
        }
    }

}


4.创建C(内容观察者)程序
注意是一个新的项目:
package com.example.contentobserver;

import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 该uri路径指向数据库应用中的数据库info表
        Uri uri = Uri.parse("content://com.example.contents/info");
        //注册内容观察者,参数uri指向要监测的数据库info表,
        //参数true定义了监测的范围,最后一个参数是一个内容观察者对象
        getContentResolver().registerContentObserver(uri, true,
                new MyObserver(new Handler()));
    }
    private class MyObserver extends ContentObserver {
        public MyObserver(Handler handler) {//handler 是一个消息处理器。
            super(handler);
        }
        @Override
        //当info表中的数据发生变化时则执行该方法
        public void onChange(boolean selfChange) {
            Log.i("监测数据变化", "有人动了你的数据库!");
            super.onChange(selfChange);
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消注册内容观察者
        getContentResolver().unregisterContentObserver(new MyObserver(
                new Handler()));
    }
}

5.测试

先启动操作数据和内容提供者 数据情况如图

image

然后启动内容观察者

当点击事件后

image

内容提供者:

image

数据改变:

image

当数据变化时 都会调用 此方法 然后发送给数据中心数据变化的消息

   getContext().getContentResolver().notifyChange(uri, null);

ok了至此结束

标签:51,uri,public,new,import,搞懂,null,Android,android
From: https://www.cnblogs.com/gaodiyuanjin/p/18236491

相关文章

  • Android Media Framework(三)OpenMAX API阅读与分析
    这篇文章我们将聚焦ControlAPI的功能与用法,为实现OMXCore、Component打下坚实的基础。1、OMX_Core.hOMXCore在OpenMAXIL架构中的位置位于ILClient与实际的OMX组件之间,OMXCore提供了两组API给ILClient使用,一组API用于管理OMX组件,另一组API用于操作/使用创建的OMX组件。......
  • 创建Android studio项目出现connect time out
    创建Androidstudio项目出现connecttimeout解决方法:AndroidStudio导入项目后报错connecttimeout-知乎(zhihu.com) 在项目里面找到gradle-wrapper.properties文件,找到gradle版本 在下面网站找到对应版本下载Indexof/gradle/(tencent.com) 我的是8.0-bin,下......
  • 一篇文章带你搞懂C++引用(建议收藏)
    引用6.1引用概念引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"typedef是给类型取别名引用是给变量取别名注意:引用类型必须和引用实体是同种类......
  • Android设置app开机自启
    Android7.1.1开机自动启动配置在AndroidManifest.xml文件中添加权限<uses-permissionandroid:name="android.permission.RECEIVE_BOOT_COMPLETED"/>在AndroidManifest.xml文件中注册接收广播配置,添加到manifest>application节点下<receiverandroid:name=".MyReceive......
  • 代码随想录算法训练营第三十天 | 51.N 皇后
    51.N皇后题目链接文章讲解视频讲解递归三部曲递归函数参数需要传入当前chessBoard和棋盘大小n,以及当前要放置皇后的行数rowvoidbacktracking(vector<string>&chessBoard,intn,introw);递归终止条件当最后一个皇后放置好后结束if(row==n){result.push_b......
  • 基于51单片机煤气天然气CO检测报警器排气风扇断气
    **单片机设计介绍,基于51单片机煤气天然气CO检测报警器排气风扇断气文章目录一概要二、功能设计设计思路三、软件设计原理图五、程序六、文章目录一概要  基于51单片机煤气天然气CO检测报警器排气风扇断气系统概要如下:一、系统概述本系统旨在利用51单片......
  • CronetDynamite.apk 中的奇怪崩溃(偏移量 0x1000) Android
    我的应用程序在2021年2月1日出现崩溃报告。崩溃LGELGPremierPro安卓9(SDK28)Playstore控制台崩溃已在CronetDynamite.apk中报告了40次backtrace:......
  • Codeforces Round 951 (Div. 2)
    A.GuesstheMaximum题意:给定一个数组,求一个k值,k满足对于任意的这个数组的区间的最大值max,k<max。求满足条件的最大k。思路:只考虑长度为2的区间即可。参与到比较中的数值一定是两个数中的大数,从所有大数中选出最小的一个即可。总结:赛时很快就A掉了,但是思考的不够细节,思维太......
  • 一文搞懂DevOps、DataOps、MLOps、AIOps:所有“Ops”的比较
    引言近年来,“Ops”一词在IT运维领域的使用迅速增加。IT运维正在向自动化过程转变,以改善客户交付。传统的应用程序开发采用DevOps实施持续集成(CI)和持续部署(CD)。但对于数据密集型的机器学习和人工智能(AI)应用,精确的交付和部署过程可能并不适用。本文将定义不同的“Ops”并解释......
  • Android 13.0 hal层关于新增自定义hal模块功能实现
    1.前言在13.0的系统rom定制化开发中,在对hal模块进行开发时,需要通过添加自定义的hal模块来实现某些功能时,就需要添加hal模块的相关功能,接下来就来实现一个案例来供参考接下来就来具体实现这个功能2.hal层关于新增自定义hal模块功能实现的核心类hardware\interfaces\3.ha......