【Android】跨程序共享数据——内容提供器

【Android】跨程序共享数据------内容提供器

简介

内容提供器(Content Provider)是Android中的一个组件,用于在应用程序之间共享数据。它提供了一种标准机制,使得一个应用可以暴露其数据,并允许其他应用访问这些数据。内容提供器在访问和存储数据时提供了安全性和一致性,通常用于访问结构化的数据集。

程序运行时申请权限

首先创建一个RuntimePermissionTest项目

修改activity中的代码:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

</LinearLayout>

下来修改MainActivity中的代码:

java 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button makecall = (Button) findViewById(R.id.make_call);
        makecall.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Intent intent = new Intent(Intent.ACTION_CALL);
                    intent.setData(Uri.parse("tel:10086"));
                    startActivity(intent);
                } catch (SecurityException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

在按钮点击事件中构建了一个隐式Intent,Intent指定的Action为Intent.ACTION_CALL,是一个系统内置的打电话的动作

修改AndroidManifest文件:

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">

    <uses-permission android:name="android.permission.CALL_PHONE"
        tools:ignore="PermissionImpliesUnsupportedChromeOsHardware" />
    <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.RuntimePermissionTest"
        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>

</manifest>

此时在低于Android6.0系统行都是可以正常运行的,但是高于6.0却不行,因为6.0及以上系统在使用危险权限时都必须进行运行时权限处理

修改MainActivity中的代码:

java 复制代码
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button makecall = (Button) findViewById(R.id.make_call);
        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"));
            startActivity(intent);
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                call();
            } else {
                Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

第一步先判断用户是否授权,借助了ContextCompat.checkSelfPermission()方法,接收两个参数,第一个为Context,第二个为具体的权限名,比如拨打电话的权限名就是Manifest.permission.CALL_PHONE

我们把打电话的逻辑封装在requestPermission()方法中来向用户申请授权,接收三个参数,第一个是Activity实例,第二个是String数组,第三个是请求码

访问其他程序中的数据

ContentResolver的用法

标准格式的URI写法:

xml 复制代码
content://com.example.app.provider/table1
content://com.example.app.provider/table2

在得到了内容URI字符串之后,我们还需要将它解析成Uri对象才可以作为参数传入:

java 复制代码
Uri uri = Uri.parse("content://com.examole.app.provider/tabel")

只需要调用Uri.parse()方法,就可以将内容URI字符串解析成Uri对象了。

现在可以使用这个Uri对象来查询tabel表中的数据了:

java 复制代码
Cursor cursor = getContentResolver().query(
	uri,
    projection,
    selection,
    selectionArgs,
    sortOrder);

读取数据:

java 复制代码
if(cursor != null){
    do {
        String name = cursor.getString(cursor.getColumnIndex("name"));
    }while (cursor.moveToNext());
}

增加数据

java 复制代码
ContentValues values = new ContentValues();
values.put("name","This is a book");
getContentResolver().insert(uri,values);

删除数据

java 复制代码
ContentValues values1 = new ContentValues();
values.put("name","This is a book");
getContentResolver().delete(uri,"name = ?",new String[]{"This is a book"});

修改数据

java 复制代码
ContentValues values1 = new ContentValues();
values.put("name","This is a book1");
getContentResolver().update(uri,values,"name = ?",new String[]{"This is a book"});

读取系统联系人

新建ContactsTest项目

修改activity_main中的代码:

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <ListView
        android:id="@+id/contacts_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </ListView>
    

</LinearLayout>

下来修改MainActivity中的代码:

java 复制代码
public class MainActivity extends AppCompatActivity {

    // 声明一个 ArrayAdapter 对象
    ArrayAdapter<String> adapter;

    // 声明一个存储联系人列表的 List 对象
    List<String> contactsList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取 ListView 控件,并设置适配器
        ListView contactsView = (ListView) findViewById(R.id.contacts_view);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, contactsList);
        contactsView.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()) {
                    // 获取联系人姓名和电话号码,并添加到联系人列表
                    @SuppressLint("Range") String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                    @SuppressLint("Range") String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                    contactsList.add(displayName + '\n' + number);
                }
                // 通知适配器数据已更改
                adapter.notifyDataSetChanged();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 确保游标被关闭
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    // 权限请求结果的回调方法
    @SuppressLint("MissingSuperCall")
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == 1) {
            // 如果权限被授予,则读取联系人;否则,显示拒绝权限的提示
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                readContacts();
            } else {
                Toast.makeText(this, "You denied the permission", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

最后还需要申请权限:

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">

    <uses-permission android:name="android.permission.READ_CONTACTS"/>
    <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>

</manifest>

创建自己的内容提供器

新建MyProvider继承ContentProvider:

java 复制代码
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) {
        return "";
    }

    @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;
    }
}

各个方法的作用:

  1. onCreate() :
    • 用于初始化 ContentProvider。这个方法会在 ContentProvider 第一次创建时调用。
    • 返回 true 表示 ContentProvider 初始化成功,返回 false 表示初始化失败。
  2. query() :
    • 用于查询 ContentProvider 中的数据。
    • 参数解释:
      • uri: 查询的 URI。
      • projection: 查询的列。
      • selection: 查询条件。
      • selectionArgs: 查询条件的参数。
      • sortOrder: 排序方式。
    • 返回一个 Cursor 对象,指向查询结果。如果没有查询结果,返回 null
  3. getType() :
    • 获取指定 URI 对应的数据的 MIME 类型。
    • 参数解释:
      • uri: 查询的 URI。
    • 返回一个表示 MIME 类型的字符串。如果没有匹配的类型,返回空字符串。
  4. insert() :
    • ContentProvider 中插入数据。
    • 参数解释:
      • uri: 插入数据的 URI。
      • values: 要插入的数据。
    • 返回新插入数据的 URI。如果插入失败,返回 null
  5. delete() :
    • ContentProvider 中删除数据。
    • 参数解释:
      • uri: 删除数据的 URI。
      • selection: 删除条件。
      • selectionArgs: 删除条件的参数。
    • 返回删除的行数。如果删除失败,返回 0
  6. update() :
    • 更新 ContentProvider 中的数据。
    • 参数解释:
      • uri: 更新数据的 URI。
      • values: 要更新的数据。
      • selection: 更新条件。
      • selectionArgs: 更新条件的参数。
    • 返回更新的行数。如果更新失败,返回 0
  • *:表示匹配任意长度的任意字符
  • #:表示匹配任意长度的数字

修改MyProvider中的代码:

java 复制代码
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;

    private 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);
        uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_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) {
        if (uriMatcher.match(uri) == TABLE1_DIR) {
            ;//查询table1表中的所有数据
        } else if (uriMatcher.match(uri) == TABLE1_ITEM){
            ;//查询table1表中的单条数据
        } else if (uriMatcher.match(uri) == TABLE2_DIR) {
            ;//查询table2表中的所有数据
        } else if (uriMatcher.match(uri) == TABLE2_ITEM) {
            ;//查询table2表中的单条数据
        }
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return "";
    }

    @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;
    }
}

有一个方法getType(),它是所有内容提供器都必须提供的第一个方法,用于获取Uri对象所对应的MIME类型。一个内容URI所对应的MIME字符串主要有3部分构成:

  • 必须以vnd开头
  • 如果内容URI以路径结尾,则后接android.cursor.dir/,如果内容URI以id结尾,则后接android.cursor.item/
  • 最后接上vnd.<authority>.<path>

继续完善MyProvider中的内容:

java 复制代码
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;

    private 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);
        uriMatcher.addURI("com.example.app.provider", "table2", TABLE2_DIR);
        uriMatcher.addURI("com.example.app.provider", "table2/#", TABLE2_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) {
        if (uriMatcher.match(uri) == TABLE1_DIR) {
            ;//查询table1表中的所有数据
        } else if (uriMatcher.match(uri) == TABLE1_ITEM) {
            ;//查询table1表中的单条数据
        } else if (uriMatcher.match(uri) == TABLE2_DIR) {
            ;//查询table2表中的所有数据
        } else if (uriMatcher.match(uri) == TABLE2_ITEM) {
            ;//查询table2表中的单条数据
        }
        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.databasetest.provider.book";
            case TABLE1_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";
            case TABLE2_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";
            case TABLE2_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";
        }
        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;
    }
}

已经到底啦!!

相关推荐
拭心10 小时前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王13 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡13 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道13 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库14 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道15 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe15 小时前
Android Hook - 动态加载so库
android
居居飒16 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He18 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗19 小时前
Android笔试面试题AI答之Android基础(1)
android