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

已经到底啦!!

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼4 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone4 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen4 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone4 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui