首页 > 其他分享 >Android Content Provider

Android Content Provider

时间:2024-09-17 15:13:44浏览次数:7  
标签:CONTENT ContactsContract 联系人 URI Content Provider Android Data

Android Content Provider

Content Provider 是 Android 中的一种组件,用于管理应用间的数据共享。它允许一个应用将其数据暴露给其他应用,也可以从其他应用中读取数据。通过 Content Provider,应用程序可以更方便地管理数据存储和数据访问,并且支持标准的数据库操作。

Content Provider 介绍

主要功能

  1. 数据共享: Content Provider 允许不同的应用程序共享数据。应用程序可以使用 Content Provider 访问另一个应用的数据库,前提是目标应用的 Content Provider 允许这样做。
  2. 统一接口: Content Provider 提供了一种统一的接口,使得数据访问操作(如查询、插入、更新和删除)不依赖于具体的数据存储方式或数据的实际格式。
  3. 数据同步: Content Provider 支持对数据的增、删、改、查操作,这些操作会自动更新所有使用该 Content Provider 的应用,确保数据的一致性和同步性。

主要组件

  1. Content URI: 每个 Content Provider 都有一个唯一的 Content URI,它是一个标识符,用于定位和访问 Content Provider 的数据。Content URI 是一个标准的 URI,例如 content://com.example.provider/contacts
  2. Content Resolver: 这是 Android 系统提供的一个类,用于访问 Content Provider。应用程序通过 Content Resolver 与 Content Provider 进行交互,执行如查询、插入、更新和删除等操作。
  3. Content Provider: 实现 Content Provider 的类,负责管理数据的具体操作。通常,Content Provider 需要实现 ContentProvider 类,并重写其方法,如 query(), insert(), update(), delete()getType()

主要操作

  1. 查询数据 (query()):
    • 使用 Content URI 和其他参数(如查询条件)来从 Content Provider 中检索数据。
  2. 插入数据 (insert()):
    • 向 Content Provider 中插入新的数据行。
  3. 更新数据 (update()):
    • 更新 Content Provider 中现有的数据行。
  4. 删除数据 (delete()):
    • 从 Content Provider 中删除数据行。
  5. 获取数据类型 (getType()):
    • 返回 Content URI 对应的数据类型 MIME 类型。

示例

假设你要在一个联系人应用程序中使用 Content Provider 来管理联系人数据

在AndroidManifest添加需要的权限

<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />

绘制管理界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/contacts_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/add_contact_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add Contact" />

    <Button
        android:id="@+id/edit_contact_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Edit Contact" />

    <Button
        android:id="@+id/delete_contact_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete Contact" />

</LinearLayout>	

其中使用了RecyclerView需要添加依赖

implementation 'androidx.recyclerview:recyclerview:1.1.0'

为RecyclerView添加适配器

public class ContactsAdapter extends RecyclerView.Adapter<ContactsAdapter.ContactViewHolder> {

    private final Cursor cursor;

    public ContactsAdapter(Cursor cursor) {
        this.cursor = cursor;
    }

    @NonNull
    @Override
    public ContactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_2, parent, false);
        return new ContactViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ContactViewHolder holder, int position) {
        if (cursor.moveToPosition(position)) {
            String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            holder.nameTextView.setText(displayName);
        }
    }

    @Override
    public int getItemCount() {
        return cursor.getCount();
    }
    public static class ContactViewHolder extends RecyclerView.ViewHolder {
        TextView nameTextView;

        public ContactViewHolder(@NonNull View itemView) {
            super(itemView);
            nameTextView = itemView.findViewById(android.R.id.text1);
        }
    }
}

绑定适配器

  private void loadContacts() {
        Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
        contactsAdapter = new ContactsAdapter(cursor);
        contactsRecyclerView.setAdapter(contactsAdapter);
    }

activity

public class ContactsActivity extends AppCompatActivity {

    private static final int REQUEST_CONTACTS_PERMISSION = 1;
    private static final int REQUEST_PICK_CONTACT_OF_UPDATE = 2;

    private static final int REQUEST_PICK_CONTACT_OF_DELETE = 3;

    private RecyclerView contactsRecyclerView;
    private ContactsAdapter contactsAdapter;

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

        contactsRecyclerView = findViewById(R.id.contacts_recycler_view);
        contactsRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        Button addContactButton = findViewById(R.id.add_contact_button);
        Button editContactButton = findViewById(R.id.edit_contact_button);
        Button deleteContactButton = findViewById(R.id.delete_contact_button);

        addContactButton.setOnClickListener(v -> addContact());
        editContactButton.setOnClickListener(v -> pickContactForEditing());
        deleteContactButton.setOnClickListener(v -> pickContactForDeletion());

        //检查联系人权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_CONTACTS_PERMISSION);
        } else {
            //读取联系人信息
            loadContacts();
        }
    }

    private void openAddMyDialogFrom() {
        // 创建自定义对话框
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        View dialogView = getLayoutInflater().inflate(R.layout.my_dialog_layout, null);
        builder.setView(dialogView);

        EditText editName = dialogView.findViewById(R.id.edit_name);
        EditText editPhone = dialogView.findViewById(R.id.edit_phone);
        Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
        Button btnCancel = dialogView.findViewById(R.id.btn_cancel);

        AlertDialog dialog = builder.create();

        btnConfirm.setOnClickListener(v -> {
            String newName = editName.getText().toString();
            String newPhone = editPhone.getText().toString();
            addContact(newName, newPhone);
            dialog.dismiss();
        });

        btnCancel.setOnClickListener(v -> dialog.dismiss());

        dialog.show();
    }

    private void addContact(String name, String phone) {
        //用来存储原始联系人的数据。
        ContentValues values = new ContentValues();
        //联系人账户类型的字段,通常用于指定该联系人是哪个账户,设置为 null 表示联系人没有指定账户或是本地联系人。
        values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, (String) null);
        //这是联系人账户名称的字段,通常用于指定该联系人是哪个账户,设置为 null 同样表示联系人没有指定账户或是本地联系人。
        values.put(ContactsContract.RawContacts.ACCOUNT_NAME, (String) null);

        //getContentResolver().insert(): 通过 ContentResolver 的 insert() 方法将数据插入到指定的内容 URI 中。
        //ContactsContract.RawContacts.CONTENT_URI: 这是原始联系人数据的内容 URI,表示联系人记录的表。插入数据时使用此 URI。
        //insert() 方法会返回一个 Uri 对象,表示新插入记录的 URI。这个 URI 包含了新记录的唯一标识符(ID)。
        Uri rawContactUri = getContentResolver().insert(ContactsContract.RawContacts.CONTENT_URI, values);

        //从返回的 Uri 中提取路径的最后一部分,这部分是新插入记录的 ID。
        long rawContactId = Long.parseLong(rawContactUri.getLastPathSegment());


        // 添加联系人姓名
        //创建 ContentValues 对象: 用于存储将要插入的数据。
        ContentValues nameValues = new ContentValues();
        //设置联系人 ID: 将 rawContactId 作为原始联系人 ID。
        nameValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
        //设置 MIME 类型: 指定数据的类型为联系人姓名。
        nameValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        //设置姓名: 将用户输入的姓名作为联系人姓名存储。
        nameValues.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
        //插入数据: 将 ContentValues 插入到 ContactsContract.Data.CONTENT_URI 指定的内容 URI 中。
        getContentResolver().insert(ContactsContract.Data.CONTENT_URI, nameValues);

        // 添加联系人电话
        ContentValues phoneValues = new ContentValues();
        phoneValues.put(ContactsContract.Data.RAW_CONTACT_ID, rawContactId);
        phoneValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        phoneValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone);
        phoneValues.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
        getContentResolver().insert(ContactsContract.Data.CONTENT_URI, phoneValues);

        loadContacts();
    }

    private void openUpdateMyDialogFrom(String contactId) {
        //定义查询的列
        //ContactsContract.Data.MIMETYPE: 数据的 MIME 类型,用于确定数据的具体类型。
        //ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME: 联系人的显示名称。
        //ContactsContract.CommonDataKinds.Phone.NUMBER: 联系人的电话号码。
        String[] projection = new String[]{
                ContactsContract.Data.MIMETYPE,
                ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
                ContactsContract.CommonDataKinds.Phone.NUMBER
        };

        // 查询姓名

        //getContentResolver().query(): 用于查询内容提供者的 query() 方法。这会返回一个 Cursor 对象,表示查询结果。
        //ContactsContract.Data.CONTENT_URI: 查询的内容 URI,表示联系人数据表。
        //projection: 查询中需要返回的列。
        //ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?": 查询的 selection 条件。这里是查询条件,? 是占位符。
        //new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}: selection 条件中的参数替换占位符。contactId 是指定的联系人的 ID,ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE 指定查询姓名数据。
        //null: 排序条件,这里没有排序。
        Cursor nameCursor = getContentResolver().query(
                ContactsContract.Data.CONTENT_URI,
                projection,
                ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
                new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE},
                null
        );
        //从 Cursor 对象中提取联系人的姓名
        String name = null;
        if (nameCursor != null) {
            if (nameCursor.moveToFirst()) {
                //nameCursor: 这是一个 Cursor 对象,表示查询结果集。它包含了从内容提供者中检索到的数据。
                //getColumnIndex(String columnName): 这个方法用于获取指定列的索引(即列的位置)。columnName 是你要检索的列的名称。
                //ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME: 这是列名,表示联系人的显示名称。你通过这个列名来查询列的索引。
                name = nameCursor.getString(nameCursor.getColumnIndex(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME));
            }
            nameCursor.close();
        }

        // 查询电话号码
        Cursor phoneCursor = getContentResolver().query(
                ContactsContract.Data.CONTENT_URI,
                projection,
                ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
                new String[]{contactId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE},
                null
        );

        String phone = null;
        if (phoneCursor != null) {
            if (phoneCursor.moveToFirst()) {
                phone = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
            }
            phoneCursor.close();
        }

        // 显示或使用联系人信息
        if (name != null && phone != null) {
            Toast.makeText(this, "Name: " + name + "\nPhone: " + phone, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(this, "No details found", Toast.LENGTH_SHORT).show();
        }
        // 创建自定义对话框
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        View dialogView = getLayoutInflater().inflate(R.layout.my_dialog_layout, null);
        builder.setView(dialogView);

        EditText editName = dialogView.findViewById(R.id.edit_name);
        EditText editPhone = dialogView.findViewById(R.id.edit_phone);
        Button btnConfirm = dialogView.findViewById(R.id.btn_confirm);
        Button btnCancel = dialogView.findViewById(R.id.btn_cancel);

        if (name != null) {
            editName.setText(name);
        }
        if (phone != null) {
            editPhone.setText(phone);
        }

        AlertDialog dialog = builder.create();

        btnConfirm.setOnClickListener(v -> {
            String newName = editName.getText().toString();
            String newPhone = editPhone.getText().toString();
            //去修改联系人信息
            updateContact(contactId, newName, newPhone);
            dialog.dismiss();
        });

        btnCancel.setOnClickListener(v -> dialog.dismiss());

        dialog.show();
    }

    private void updateContact(String contactId, String newName, String newPhone) {

        // 更新姓名
        ContentValues nameValues = new ContentValues();
        nameValues.put(ContactsContract.Data.RAW_CONTACT_ID, contactId);
        nameValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
        nameValues.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, newName);

        // 更新电话号码
        ContentValues phoneValues = new ContentValues();
        phoneValues.put(ContactsContract.Data.RAW_CONTACT_ID, contactId);
        phoneValues.put(ContactsContract.Data.MIMETYPE, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
        phoneValues.put(ContactsContract.CommonDataKinds.Phone.NUMBER, newPhone);
        //ContactsContract.CommonDataKinds.Phone.TYPE: 这是一个列名,表示电话号码的类型。例如,家庭、工作、移动等。
        //ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE: 这是一个常量,表示电话号码的类型是“移动电话”。这使得更新后的电话号码被标记为移动号码。
        phoneValues.put(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);


        // 使用 ContentResolver 更新数据
        getContentResolver().update(
                ContactsContract.Data.CONTENT_URI,
                nameValues,
                ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
                new String[]{contactId, ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE}
        );
        //where条件改为 ContactsContract.Data.RAW_CONTACT_ID + " = ? ",new String[]{contactId} 同一个联系人下有多个联系方式时会出现混乱
        //唯一性: 如果你只用 RAW_CONTACT_ID,可能会更新与该联系人 ID 相关的所有数据类型(如姓名、电话号码、电子邮件等),而不仅仅是电话号码。这会导致数据混乱,尤其是在联系人有多个电话号码或不同类型的联系方式时。
        //准确性: 加上 MIMETYPE 条件可以确保你只更新特定类型的数据(如电话号码),而不会影响同一联系人的其他信息。

        getContentResolver().update(
                ContactsContract.Data.CONTENT_URI,
                phoneValues,
                ContactsContract.Data.RAW_CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?",
                new String[]{contactId, ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE}
        );

        // 重新加载联系人
        loadContacts();
    }

//绑定recyclerview
    private void loadContacts() {
        Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
        contactsAdapter = new ContactsAdapter(cursor);
        contactsRecyclerView.setAdapter(contactsAdapter);
    }
//打开系统新建联系人界面
    private void openContactCreation() {
        Intent intent = new Intent(Intent.ACTION_INSERT);
        intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
        startActivity(intent);
    }

    private void addContact() {
        //二选一
        //使用系统自带方法添加联系人
//        openContactCreation();
        //使用自定义对话框添加联系人
        openAddMyDialogFrom();
    }

    private void pickContactForEditing() {
        //Intent.ACTION_PICK 是一个动作常量,表示用户要选择某项数据(在这里是联系人)。
        //ContactsContract.Contacts.CONTENT_URI 是联系人内容提供者的 URI(统一资源标识符),它指向所有联系人数据的集合。
        Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
        startActivityForResult(intent, REQUEST_PICK_CONTACT_OF_UPDATE);
    }

    private void pickContactForDeletion() {
        Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
        startActivityForResult(intent, REQUEST_PICK_CONTACT_OF_DELETE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_PICK_CONTACT_OF_UPDATE && resultCode == RESULT_OK && data != null) {
            //从 Intent 对象中获取 Uri。这个 Uri 代表了用户选择的联系人的数据。
            Uri contactUri = data.getData();
            //从 Uri 中提取最后一部分,通常是联系人的唯一 ID。
            String contactId = contactUri.getLastPathSegment();
            //使用自定义修改联系人
            openUpdateMyDialogFrom(contactId);
//            // 使用意图打开联系人编辑界面
//            Intent intent = new Intent(Intent.ACTION_EDIT);
//            intent.setData(ContactsContract.Contacts.CONTENT_URI.buildUpon().appendPath(contactId).build());
//            startActivity(intent);
        } else if (requestCode == REQUEST_PICK_CONTACT_OF_DELETE && resultCode == RESULT_OK && data != null) {
            Uri contactUri = data.getData();
            String contactId = contactUri.getLastPathSegment();

            // 删除联系人的具体操作
            //ContactsContract.RawContacts.CONTENT_URI: 这是联系人内容提供者的 URI,表示原始联系人数据的集合。它是用来访问和操作联系人的基础 URI。
            //buildUpon(): 创建一个 Uri.Builder 对象,用于修改或构建新的 URI。
            //appendPath(contactId): 在现有 URI 的末尾追加联系人 ID。这样可以指定要操作的特定联系人。例如,如果原始 URI 是 content://com.android.contacts/raw_contacts,追加 contactId 后的 URI 可能是 content://com.android.contacts/raw_contacts/123,其中 123 是联系人 ID。
            //build(): 生成修改后的 URI。在这个例子中,最终的 URI 表示要删除的特定联系人。
            Uri deleteUri = ContactsContract.RawContacts.CONTENT_URI.buildUpon().appendPath(contactId).build();
            int rowsDeleted = getContentResolver().delete(deleteUri, null, null);
            if (rowsDeleted > 0) {
                Toast.makeText(this, "成功删除该联系人", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "联系人删除失败", Toast.LENGTH_SHORT).show();
            }
            // 更新联系人列表
            loadContacts();
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        //判断请求码
        if (requestCode == REQUEST_CONTACTS_PERMISSION) {
            //返回权限请求结果
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //加载联系人数据
                loadContacts();
            } else {
                Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

总结

Content Provider 是 Android 平台提供的强大机制,允许应用程序进行跨进程的数据共享和管理。它通过提供一个统一的接口来简化数据的访问,同时确保数据的完整性和一致性。理解和有效使用 Content Provider 是开发 Android 应用程序的重要技能,特别是在涉及到多应用数据交互和共享的场景中。

标签:CONTENT,ContactsContract,联系人,URI,Content,Provider,Android,Data
From: https://www.cnblogs.com/20lxj666/p/18400131

相关文章

  • Android Service
    AndroidService参考:https://blog.csdn.net/javazejian/article/details/527098571、Service简单概述Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),服务一旦被启动将在后台一直运行,即使启动服务的组件(Act......
  • 基于Android的健身计划管理系统 基于Android的健身管理平台(程序+LW+PPT+部署)
    ......
  • Android使用LiquidFun物理引擎实现果冻碰撞效果
    一、效果展示Android使用LiquidFun物理引擎实现果冻碰撞效果二、LiquidFun物理引擎简介LiquidFun是一个由Google开发并开源的2D物理模拟库,它基于Box2D物理引擎,并扩展了流体模拟的功能。流体动力学模拟:LiquidFun提供了强大的流体动力学系统,可以模拟流体的行为,包括液体......
  • Android 环形
    关于“Android环形”,这个描述可能指的是在Android应用开发中使用的环形布局或者控件,比如环形进度条、环形菜单等。这里我将给出一个简单的例子来创建一个环形进度条(CircularProgressBar)和一个环形菜单(CircularMenu)的例子。环形进度条在Android中,可以通过自定义ProgressBar来实......
  • Android HandlerThread Post后延迟7秒才执行的原因及解决方案|如何提高Android后台线程
    在Android开发中,HandlerThread是用于处理后台线程任务的常见工具。然而,有时我们会遇到这样的问题:当任务通过HandlerThread的post方法发送后,任务的执行时间会出现明显的延迟,比如7秒的延迟才执行任务。本文将深入分析这种问题的成因,探讨可能的影响因素,并提供多种优化方案,帮助开发者解......
  • AndroidStudio - - - 点击头像更换头像_菜单选择_相机拍照与相册获取
    1.逻辑代码1.1MainActivity类packagecom.example.myapplication;importandroid.Manifest;importandroid.app.Activity;importandroid.app.AlertDialog;importandroid.content.DialogInterface;importandroid.content.Intent;importandroid.content.pm.PackageM......
  • android 双重吸顶
    双重吸顶效果通常是指在一个页面中有两层头部区域,在用户滚动列表时,这两层头部会根据不同的条件分别吸顶显示。这种效果常见于具有多层级导航的应用中,比如在顶部有一个主要的导航栏,在下方有一个次要的导航栏或者标题栏。实现双重吸顶效果,可以利用Android中的CoordinatorLayout配合A......
  • Android中的单例模式
    在Android开发中,单例模式(SingletonPattern)是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式在需要控制资源访问、管理共享资源或配置信息的场景下特别有用。在Android中,实现单例模式的方法有多种,但主要思想是一致的:私有化构造函数,......
  • Android SDK和NDK的区别
    AndroidSDK(SoftwareDevelopmentKit,软件开发工具包)和NDK(NativeDevelopmentKit,本地开发工具包)在Android应用开发中扮演着不同的角色,它们各自具有独特的功能和优势。一、定义与功能AndroidSDKAndroidSDK是由Google提供的一套开发工具,用于开发基于Android操作系统的应用......
  • Android中如何处理运行时权限?
    在Android中,处理运行时权限是开发过程中一个至关重要的环节,它自Android6.0(API级别23)引入,旨在提高用户隐私保护和应用的透明度。以下将详细阐述Android中处理运行时权限的方法、步骤、注意事项以及相关的最佳实践。一、运行时权限概述Android运行时权限(RuntimePermissions)允......