Android Studio学习笔记——跨进程共享数据、内容提供器Content Provider

7.1 内容提供器简介

内容提供器主要用于在不同的应用程序之间实现数据共享的功能。它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保是地方数据的安全性。
不同于文件存储和SharedPreferences存储中的两种全局可读写操作模式,内容提供器可以选择只对哪一部分数据进行共享,保证程序中的隐私数据不会有泄露的风险。

7.2 运行时权限

为了更好的保护用户的安全和隐私,安卓系统在6.0中引入了运行时权限这个功能。

7.2.1 Android权限机制详解

第五章BroadcastTest中,为了访问系统的网络状态和监听开机广播,于是在AndroidManifest.xml添加了这样两句权限声明:

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

如果不声明权限,系统运行就会崩溃。
用户不需要再安装软件的时候一次性授权所有申请的权限,而是可以在软件的使用过程中在对某一项权限申请进行授权。比如说相机应用在运行时申请了地理位置,因为就算我拒绝了这个权限,但是我仍然可以使用相机的其他功能。而不是像之前那样直接无法安装这个软件。
当然不是所有的权限都需要在运行时申请。对于用户来说不停的授权也很繁琐。安卓将所有的权限归成了两类,一类是普通权限,一类是危险权限。普通权限是,对于这部分权限的申请,系统会自动进行授权。
Android中的危险权限有九种。以下是九种权限组名。

  1. CALENDAR
  2. CAMERA
  3. CONTACTS
  4. LOCATION
  5. MICROPHONE
  6. PHONE
  7. SENSORS
  8. SMS
  9. STORAGE

7.2.2 在程序运行时申请权限

CALL_PHONE这个 权限是在编写拨打电话功能时需要声明的。因为拨打电话涉及到用户付费的问题,因此被列为了危险权限。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        Button makeCall = (Button) findViewById(R.id.buttonmy);
        makeCall.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
                    }, 1);
                } else {
                    call();
                }
            }
        });
    }
    private void call(){
        try{
            Intent intent=new Intent(Intent.ACTION_CALL);
            intent.setData(Uri.parse("tel:10086"));
        }catch(SecurityException e) {
            e.printStackTrace();
        }
    }
    public void onRequestPermissionResult(int requestCode,String [] permissions,int[] grantResults){
        switch (requestCode){
            case 1: if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
                call();
            }else{
                Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show();
            }
            break;
            default:
        }
    }
}

7.3 访问其他程序中的数据

内容提供器的用法一般有两种,一种是从现有的内容提供器来读取和操作相应程序中的数据。另一种是创建自己的内容提供器,给程序的数据提供外部访问接口。如果一个应用程序通过内容提供器对其数据提供了外部访问接口,那么任何其他的应用程序都可以对这部分数据进行访问,安卓系统中自带的电话簿,短信,媒体库等程序都提供了类似的访问接口。使得三第三方应用程序可以充分的利用这部分数据。来实现更好的功能。

7.3.1 ContentResovler的用法

对于每一个应用程序来说,如果想要访问内容提供器中的共享数数据,一定要借助ContentResolver类,可以通过getContentResolver()方法获取到该类的实例。ContentResolver类中提供了一系列的方法对数据进行CRUD操作,其中insert的方法用于添加数据,Update用于更新数据,delete用于删除数据,query方法用于查询数据,有没有似曾相识的感觉?没错。SQLiteDatabase中也是用这几个方法来进行crud操作的。只不过他们在方法参数上稍微有一些区别。
不同于SQLiteDatabase,ContentResolver类中的增删改查方法都是不接受表参数名的,而是接收一个Uri参数代替,这个参数被称为内容URI。内容URI给内容提供器中的数据建立了唯一标识符,它主要由两部分组成:authority和path。
authority是用于对不同的应用程序做区分的,一般为了避免冲突,都会采用程序包名的方式来进行命名。比如某个程序的包名是com.example.app,那么该程序对应的authority就可以命名为com.example.app.provider。
path则是用于对同一应用程序中不同表做区分的,通常都会添加到authority后面。比如某个程序的数据库里存在两个表:table1和table2,这时就可以将path分别命名为/table1和/table2,内容URI为两个
content://com.example.app.provider/table1
content://com.example.app.provider/table2
这样就可以清晰的访问哪个程序里的哪一张表
上述内容URI字符串需要解析成uri对象才可以作为参数传入。

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

之后,可以用uri对象来查询table1表中的数据了

Cursor cursor=getContentResolver().query{
 uri,
 projection,
 selection,
 selectionArgs,
 sortOrder);
 

query() 方法参数 对应SQL部分 描述
uri from table_name 指定查询某个应用程序下的某一张表

查询之后返回的仍然是一个Cursor对象,这时我们就可以将数据从Cursor对象中逐个读取出来了。读取的思路仍然是通过移动游标的位置来遍历Cursor的所有行,然后再取出每一个行中相应列的数据

if(cursor!=null){
 while(cursor.moveToNext()){
	String column1=cursor.getSring(cursor.getColumnIndex("column1"));
	int column2=cursor.getInt(cursor.getColumnIndex("column2"));
 	
 	ContentValues Values=new ContentValues();
 	values.put("column1","text");
 	values.put("column2",1);
	getContentResolver().insert(uri,values);

	getContentResolver().updata(uri,values,"column1=? and column2=?",new String[] {"text","1"});
	getContentResolver().delete(uri,"column2=?" ,new String[] {"1"});
	
 }
	
cursor.close();
}

7.3.2 读取系统联系人

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

<

mainActivity.java

public class MainActivity extends AppCompatActivity {

    ArrayAdapter<String> adapter;
    List<String> contactsList=new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        ListView contactView=(ListView) findViewById(R.id.contacts_view);
        adapter =new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);
        contactView.setAdapter(adapter);
        if( ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED ){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},
                    1);
        }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 displayName=cursor.getString(cursor.getColumnIndex(
                            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    String number=cursor.getString( cursor.getColumnIndex(
                            ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(displayName+"\n"+nunmber);
                }
            }
            adapter.notifyDataSetChanged();
        }
        catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if(cursor!=null){
                cursor.close();
            }
        }
    }
    public void onRequestPermissionResult(){

    }

}

Manifest.xml文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ContactsTest"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    
<uses-permission android:name="android.permission.READ_CONTACTS"/>
    
</manifest>

7.4 创建自己的内容提供器

7.4.1 创建内容提供器的步骤

新建一个类继承ContentProvider,有6个抽象方法,在使用子类继承的时候,需要6个方法全部重写。

public class MyProvider extends ContentProvider {

    @Override
    public boolean onCreate() {
    //数据库创建和升级
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
    //内容URI返回相应的类型
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

一个标准的内容URI写法是这样的:

  • content://com.example.app.provider/table1

在这个内容URI后面加一个id

  • content://com.example.app.provider/table1/1

表示com.example.app.provider这个应用的table1表中id为1的数据

内容URI就两种格式,已路径结束表示期望访问表中所有数据;以id结尾期望访问表中相应id的数据。
可以用通配符方式匹配这两种格式的URI,规则如下:

  • *:表示匹配任意长度的任意字符。
  • #:表示匹配任意长度的数字。
    所以,一个能够匹配任意表的内容URI格式可以写成:
    content://com.example.app.provider/*
    一个能够匹配table1表中任意一行数据的内容URI格式可以为
    content://com.example.app.provider/table1/#

借助UriMatcher这个类就可以匹配内容URI的功能。UriMatcher中提供了一个addURI()方法,这个方法接收了一个addURI方法,这个方法接收三个参数,可以分别把authority、path和一个自定义代码传进入。这样,当调用UriMatcher的match方法时,就可以将一个Uri对象传入,返回值是某个能否匹配这个Uri对象所对应的自定义代码,利用这个代码,就可以判断出调用方期望访问的是哪一个表中的数据了。

public class MyProvider extends ContentProvider {
    public static final int TABLE1_DIR=0;
    public static final int TABLE1_ITEM=1;
    public static final int TABLE2_DIR=2;
    public static final int TABLE2_ITEM=3;
    public static UriMatcher uriMatcher;

    static {
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI("com.example.app.provider","table1",TABLE1_DIR);
        uriMatcher.addURI("com.example.app.provider","table1/#",TABLE1_ITEM);
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {

        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (uriMatcher.match(uri)){
            case TABLE1_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
        }
        return null;
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
}

7.4.2 实现跨进程数据共享

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android Studio是Google推出的Android应用程序开发工具,它集成了开发、调试、打包等功能。要学习使用Android Studio,需要了解以下几个方面的知识: 1. Java基础: Android开发是基于Java语言的,所以要学习Android Studio首先要掌握Java基础知识。 2. Android基础: 了解Android操作系统的基本构架和常用组件,如Activity、Service、BroadcastReceiver等。 3. Android Studio使用: 了解Android Studio的基本界面和常用功能,如创建工程、编写代码、调试程序等。 4. Android SDK: 了解Android SDK的安装和配置,以及如何使用SDK Manager来下载SDK并配置项目。 5. 练习实践: 多练习实际项目,熟悉Android Studio的使用,并不断练习和完善自己的技能。 ### 回答2: Android Studio 是一个为 Android 来开发的整体开发环境。它基于 IntelliJ IDEA ,强调速度和智能操作,并致力于为应用程序开发提供高效率的开发工具。Android Studio 的推出,使得 Android 开发者更加容易地创建高质量的应用程序。 首先,要学 Android Studio ,你需要首先了解一些基本知识。学习 Android Studio 的工具包括 Gradle 、 Kotlin 和 Java 等。通过学习这些基础知识,你可以了解不同的语言、工具和框架。 在学习 Android Studio 时要使用实战性的方法。也就是说,每次学习新的东西时,你需要找到一个相关的例子。这样可以帮助你更好地理解它。如果你使用 Android Studio 模板,这将非常有用。这些模板可以帮助你创建常见的应用程序结构,并且你可以基于这些模板创建自己的应用程序。 另外,还有很多不同的工具和插件可以帮助你学习 Android Studio 。例如, Android Studio 网站上有很多有用的文章和教程,可以帮助你更好地理解 Android Studio 。还有很多在线工具可以帮助你实现特定的功能,在初学者阶段,这些工具可以为学习的难度减轻很多。 总之,学习 Android Studio 任重而道远。需要有耐心和时间,还需要不断地尝试和实践。在这个过程中,你可以尝试教学资料、交流学习合作、进一步增强你的知识水平。通过不断的应用和实践,一定可以成为一名优秀的 Android 开发者并圆满地完成应用开发。 ### 回答3: 作为一个应用开发者,学习使用安卓应用程序开发套件非常重要。Android Studio是Google出品的最新安卓开发工具,它具有多种强大的工具和功能,可以极大地提高应用程序的开发效率和质量。在学习过程中,个人对Android Studio的理解主要体现在以下几个方面。 首先,要开始使用Android Studio,需要了解它的基本结构和工具。Android Studio主要由三个主要组件组成:IDE、SDK、和Emulator。IDE是开发者使用的主要工具。它提供了编写、测试和调试代码所需的所有基本功能,包括视觉化设计,代码编辑和调试工具。SDK是开发应用程序所需的软件开发包。它包括实用程序和库,可用于在应用程序中实现预定功能。Emulator是一个虚拟的安卓设备,可以用来在开发应用程序时模拟真实设备上的应用程序运行情况。 其次,一些基本的编程知识也必不可少,例如Java编程、XML语言、UI界面设计等。Java是Android Studio中最常用的编程语言,用于编写应用程序的逻辑代码;XML语言则用于设计UI界面。在学习这些基本知识时,建议首先了解Java语言特性和面向对象编程理念,熟悉它们的基本语法和数据类型。同时,也可以通过在线教程和网课等方式加强对UI设计和布局的理解。 最后,学会使用网上的资源和社区的帮助非常重要。Android Studio的开发者社区很大,有很多在线资源可以帮助开发者的开发学习,例如stack overflow、CSDN社区等。同时,Google官方的开发者网站也提供了大量的文档资料、教程、API使用说明等。 总之,Android Studio学习需要进行持续不断的针对不同方面的学习和实践。要充分运用Android Studio的强大功能,建议在掌握基础知识的基础上,多做实际的项目练习,不断提高自己的技术水平和代码能力,才能成为一名优秀的安卓应用程序开发者。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值