ข้อมูลเบื้องต้นเกี่ยวกับผู้ให้บริการเนื้อหา

ผู้ให้บริการเนื้อหาจะจัดการการเข้าถึงที่เก็บข้อมูลส่วนกลาง ผู้ให้บริการเป็นส่วนหนึ่งของแอปพลิเคชัน Android ซึ่งมักจะมี UI ของตนเองสำหรับทำงานกับข้อมูล อย่างไรก็ตาม แอปพลิเคชันอื่นๆ จะใช้ผู้ให้บริการเป็นหลัก ซึ่งเข้าถึงผู้ให้บริการโดยใช้ออบเจ็กต์ไคลเอ็นต์ของผู้ให้บริการ ผู้ให้บริการและไคลเอ็นต์ผู้ให้บริการจะให้บริการอินเทอร์เฟซมาตรฐานที่สอดคล้องกันซึ่งยังจัดการการสื่อสารระหว่างโปรเซสและการเข้าถึงข้อมูลอย่างปลอดภัยด้วย

โดยทั่วไปแล้ว คุณอาจต้องทำงานร่วมกับผู้ให้บริการเนื้อหาใน 1 ใน 2 สถานการณ์ ได้แก่ การใช้โค้ดเพื่อเข้าถึงผู้ให้บริการเนื้อหาที่มีอยู่ในแอปพลิเคชันอื่น หรือสร้างผู้ให้บริการเนื้อหาใหม่ในแอปพลิเคชันเพื่อแชร์ข้อมูลกับแอปพลิเคชันอื่นๆ

หน้านี้ครอบคลุมข้อมูลเบื้องต้นเกี่ยวกับการทำงานร่วมกับผู้ให้บริการเนื้อหาปัจจุบัน ดูข้อมูลเกี่ยวกับการใช้ผู้ให้บริการเนื้อหาในแอปพลิเคชันของคุณเองได้ที่ สร้างผู้ให้บริการเนื้อหา

หัวข้อนี้จะอธิบายข้อมูลต่อไปนี้

  • วิธีการทํางานของผู้ให้บริการเนื้อหา
  • API ที่คุณใช้ดึงข้อมูลจากผู้ให้บริการเนื้อหา
  • API ที่คุณใช้แทรก อัปเดต หรือลบข้อมูลในผู้ให้บริการเนื้อหา
  • ฟีเจอร์อื่นๆ ของ API ที่ช่วยให้การทํางานร่วมกับผู้ให้บริการสะดวกขึ้น

ภาพรวม

ผู้ให้บริการเนื้อหาจะแสดงข้อมูลไปยังแอปพลิเคชันภายนอกเป็นตารางอย่างน้อย 1 ตารางที่คล้ายกับตารางที่อยู่ในฐานข้อมูลเชิงสัมพันธ์ แถวหนึ่งๆ แสดงอินสแตนซ์ของข้อมูลประเภทหนึ่งๆ ที่ผู้ให้บริการรวบรวม และแต่ละคอลัมน์ในแถวแสดงข้อมูลแต่ละรายการที่รวบรวมสําหรับอินสแตนซ์

ผู้ให้บริการเนื้อหาจะประสานการเข้าถึงเลเยอร์พื้นที่เก็บข้อมูลในแอปพลิเคชันของคุณสําหรับ API และคอมโพเนนต์ต่างๆ ดังภาพประกอบในรูปที่ 1 ข้อมูลเหล่านั้นได้แก่

  • การแชร์การเข้าถึงข้อมูลแอปพลิเคชันของคุณกับแอปพลิเคชันอื่น
  • การส่งข้อมูลไปยังวิดเจ็ต
  • แสดงคําแนะนําการค้นหาที่กําหนดเองสําหรับแอปพลิเคชันผ่านเฟรมเวิร์กการค้นหาโดยใช้ SearchRecentSuggestionsProvider
  • การซิงค์ข้อมูลแอปพลิเคชันกับเซิร์ฟเวอร์โดยใช้การติดตั้งใช้งาน AbstractThreadedSyncAdapter
  • โหลดข้อมูลใน UI โดยใช้ CursorLoader
ความสัมพันธ์ระหว่างผู้ให้บริการเนื้อหากับคอมโพเนนต์อื่นๆ

รูปที่ 1 ความสัมพันธ์ระหว่างผู้ให้บริการเนื้อหากับคอมโพเนนต์อื่นๆ

เข้าถึงผู้ให้บริการ

เมื่อต้องการเข้าถึงข้อมูลในผู้ให้บริการเนื้อหา คุณจะใช้ออบเจ็กต์ ContentResolver ใน Context ของแอปพลิเคชันเพื่อสื่อสารกับผู้ให้บริการในฐานะไคลเอ็นต์ ออบเจ็กต์ ContentResolver จะสื่อสารกับออบเจ็กต์ผู้ให้บริการ ซึ่งเป็นอินสแตนซ์ของคลาสที่ใช้ ContentProvider

ออบเจ็กต์ผู้ให้บริการจะรับคําขอข้อมูลจากไคลเอ็นต์ ดําเนินการตามที่ขอ และแสดงผลลัพธ์ ออบเจ็กต์นี้มีเมธอดที่เรียกเมธอดที่มีชื่อเหมือนกันในออบเจ็กต์ผู้ให้บริการ ซึ่งเป็นอินสแตนซ์ของคลาสย่อยที่เฉพาะเจาะจงของ ContentProvider เมธอด ContentResolver มีฟังก์ชัน "CRUD" พื้นฐาน (สร้าง เรียกข้อมูล อัปเดต และลบ) ของพื้นที่เก็บข้อมูลถาวร

รูปแบบทั่วไปในการเข้าถึง ContentProvider จาก UI ของคุณจะใช้ CursorLoader เพื่อเรียกใช้การค้นหาแบบอะซิงโครนัสในเบื้องหลัง Activity หรือ Fragment ใน UI จะเรียก CursorLoader ไปยังการค้นหา ซึ่งส่งผลให้ได้ ContentProvider ที่ใช้ ContentResolver

ซึ่งจะช่วยให้ผู้ใช้ใช้งาน UI ได้ต่อไปขณะที่การค้นหาทํางานอยู่ รูปแบบนี้เกี่ยวข้องกับการโต้ตอบของวัตถุต่างๆ จำนวนมาก รวมถึงกลไกการจัดเก็บข้อมูลที่เกี่ยวข้อง ดังที่แสดงในรูปที่ 2

การโต้ตอบระหว่าง ContentProvider, คลาสอื่นๆ และพื้นที่เก็บข้อมูล

รูปที่ 2 การโต้ตอบระหว่าง ContentProvider, คลาสอื่นๆ และพื้นที่เก็บข้อมูล

หมายเหตุ: หากต้องการเข้าถึงผู้ให้บริการ แอปพลิเคชันของคุณมักจะต้องขอสิทธิ์ที่เฉพาะเจาะจงในไฟล์ Manifest รูปแบบการพัฒนานี้จะอธิบายรายละเอียดเพิ่มเติมในส่วนสิทธิ์ของผู้ให้บริการเนื้อหา

หนึ่งในผู้ให้บริการในตัวของแพลตฟอร์ม Android คือผู้ให้บริการพจนานุกรมของผู้ใช้ ซึ่งจะจัดเก็บคำที่ไม่ใช่มาตรฐานที่ผู้ใช้ต้องการเก็บไว้ ตารางที่ 1 จะแสดงลักษณะของข้อมูลในตารางของผู้ให้บริการรายนี้

ตารางที่ 1: ตารางพจนานุกรมผู้ใช้ตัวอย่าง

คำศัพท์ รหัสแอป ในการโพสต์ ภาษา _ID
mapreduce user1 100 th-TH 1
precompiler ผู้ใช้14 200 fr_FR 2
applet user2 225 fr_CA 3
const user1 255 pt_BR 4
int user5 100 th_TH 5

ในตารางที่ 1 แต่ละแถวจะแสดงอินสแตนซ์ของคำที่ไม่พบในพจนานุกรมมาตรฐาน แต่ละคอลัมน์แสดงข้อมูลของคํานั้นๆ เช่น ภาษาที่พบคํานั้นเป็นครั้งแรก ส่วนหัวของคอลัมน์คือชื่อคอลัมน์ที่จัดเก็บอยู่ในผู้ให้บริการ ดังนั้น หากต้องการอ้างอิงภาษาของแถว เช่น คุณอ้างอิงถึงคอลัมน์ locale ของแถวดังกล่าว สําหรับผู้ให้บริการรายนี้ คอลัมน์ _ID จะทําหน้าที่เป็นคอลัมน์คีย์หลักที่ผู้ให้บริการจะดูแลรักษาโดยอัตโนมัติ

หากต้องการดูรายการคำและภาษาของคำจากผู้ให้บริการพจนานุกรมของผู้ใช้ ให้เรียกใช้ ContentResolver.query() เมธอด query() จะเรียกใช้เมธอด ContentProvider.query() ที่ผู้ให้บริการพจนานุกรมผู้ใช้กำหนด โค้ดบรรทัดต่อไปนี้แสดงการเรียกใช้ ContentResolver.query()

Kotlin

// Queries the UserDictionary and returns results
cursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
        projection,                        // The columns to return for each row
        selectionClause,                   // Selection criteria
        selectionArgs.toTypedArray(),      // Selection criteria
        sortOrder                          // The sort order for the returned rows
)

Java

// Queries the UserDictionary and returns results
cursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,  // The content URI of the words table
    projection,                        // The columns to return for each row
    selectionClause,                   // Selection criteria
    selectionArgs,                     // Selection criteria
    sortOrder);                        // The sort order for the returned rows

ตารางที่ 2 แสดงวิธีจับคู่อาร์กิวเมนต์ของ query(Uri,projection,selection,selectionArgs,sortOrder) กับคำสั่ง SELECT ของ SQL

ตารางที่ 2: query() เทียบกับการค้นหา SQL

อาร์กิวเมนต์ query() เลือกคีย์เวิร์ด/พารามิเตอร์ หมายเหตุ
Uri FROM table_name Uri แมปกับตารางในผู้ให้บริการชื่อ table_name
projection col,col,col,... projection คืออาร์เรย์ของคอลัมน์ที่รวมอยู่ในแถวที่ดึงข้อมูลแต่ละแถว
selection WHERE col = value selection ระบุเกณฑ์สําหรับการเลือกแถว
selectionArgs ไม่มีค่าเทียบเท่าที่ตรงกันทั้งหมด อาร์กิวเมนต์การเลือกจะแทนที่ตัวยึดตําแหน่ง ? ในประโยคการเลือก
sortOrder ORDER BY col,col,... sortOrder ระบุลําดับที่แถวจะปรากฏในCursorที่แสดงผล

URI ของเนื้อหา

URL เนื้อหาคือ URI ที่ระบุข้อมูลในผู้ให้บริการ URI ของเนื้อหาจะประกอบด้วยชื่อสัญลักษณ์ของผู้ให้บริการทั้งหมด ซึ่งก็คือหน่วยงาน และชื่อที่ชี้ไปยังตาราง ซึ่งเป็นเส้นทาง เมื่อคุณเรียกใช้เมธอดไคลเอ็นต์เพื่อเข้าถึงตารางในผู้ให้บริการ URI เนื้อหาของตารางจะเป็นหนึ่งในอาร์กิวเมนต์

ในบรรทัดโค้ดก่อนหน้า ค่าคงที่ CONTENT_URI มี URI เนื้อหาของตาราง Words ของผู้ให้บริการพจนานุกรมของผู้ใช้ ออบเจ็กต์ ContentResolver จะแยกสิทธิ์ของ URI ออกและใช้เพื่อแก้ไขผู้ให้บริการโดยเปรียบเทียบสิทธิ์กับตารางระบบของผู้ให้บริการที่รู้จัก จากนั้น ContentResolver จะส่งอาร์กิวเมนต์การค้นหาไปยังผู้ให้บริการที่ถูกต้องได้

ContentProvider ใช้ส่วนเส้นทางของ URI เนื้อหาเพื่อเลือกตารางที่จะเข้าถึง โดยปกติแล้ว ผู้ให้บริการจะมีเส้นทางสำหรับตารางแต่ละตารางที่แสดง

ในบรรทัดโค้ดก่อนหน้า URI แบบเต็มของตาราง Words คือ

content://user_dictionary/words
  • สตริง content:// คือ รูปแบบ ซึ่งจะมีอยู่เสมอ และระบุ URI ของเนื้อหา
  • สตริง user_dictionary เป็นสิทธิ์ของผู้ให้บริการ
  • สตริง words คือเส้นทางของตาราง

ผู้ให้บริการหลายรายให้คุณเข้าถึงแถวเดียวในตารางได้โดยใส่ค่ารหัสต่อท้าย URI เช่น หากต้องการดึงข้อมูลแถวที่มี _ID เป็น 4 จากผู้ให้บริการพจนานุกรมผู้ใช้ คุณใช้ URI เนื้อหานี้ได้

Kotlin

val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)

Java

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

คุณมักจะใช้ค่ารหัสเมื่อดึงชุดแถวมา แล้วต้องการอัปเดตหรือลบแถวใดแถวหนึ่ง

หมายเหตุ: คลาส Uri และ Uri.Builder มีเมธอดที่สะดวกสำหรับการสร้างออบเจ็กต์ URI ที่จัดรูปแบบอย่างถูกต้องจากสตริง คลาส ContentUris มีวิธีการอำนวยความสะดวกในการเพิ่มค่ารหัสต่อท้าย URI ข้อมูลโค้ดก่อนหน้าใช้ withAppendedId() เพื่อต่อท้ายรหัสไปยัง URI เนื้อหาของผู้ให้บริการพจนานุกรมของผู้ใช้

เรียกดูข้อมูลจากผู้ให้บริการ

ส่วนนี้จะอธิบายวิธีดึงข้อมูลจากผู้ให้บริการโดยใช้ผู้ให้บริการพจนานุกรมของผู้ใช้เป็นตัวอย่าง

เพื่อความชัดเจน ข้อมูลโค้ดในส่วนนี้จะเรียกใช้ ContentResolver.query() ในเธรด UI แต่โค้ดจริงจะทำการค้นหาแบบไม่พร้อมกันในเธรดแยกต่างหาก คุณใช้คลาส CursorLoader ได้ ซึ่งอธิบายไว้โดยละเอียดในคู่มือ โหลด นอกจากนี้ บรรทัดโค้ดเป็นเพียงตัวอย่างเท่านั้น แต่จะไม่แสดงแอปพลิเคชันที่สมบูรณ์

หากต้องการดึงข้อมูลจากผู้ให้บริการ ให้ทําตามขั้นตอนพื้นฐานต่อไปนี้

  1. ขอสิทธิ์การเข้าถึงระดับอ่านสำหรับผู้ให้บริการ
  2. กำหนดโค้ดที่จะส่งการค้นหาไปยังผู้ให้บริการ

ขอสิทธิ์เข้าถึงระดับอ่าน

หากต้องการดึงข้อมูลจากผู้ให้บริการ แอปพลิเคชันของคุณต้องมีสิทธิ์เข้าถึงระดับอ่านสําหรับผู้ให้บริการ คุณขอสิทธิ์นี้ระหว่างรันไทม์ไม่ได้ แต่คุณต้องระบุว่าต้องการสิทธิ์นี้ในไฟล์ Manifest โดยใช้องค์ประกอบ <uses-permission> และชื่อสิทธิ์ที่ผู้ให้บริการกำหนดไว้

เมื่อคุณระบุองค์ประกอบนี้ในไฟล์ Manifest แสดงว่าคุณกำลังขอสิทธิ์นี้สําหรับแอปพลิเคชัน เมื่อผู้ใช้ติดตั้งแอปพลิเคชันของคุณ ผู้ใช้จะอนุมัติคําขอนี้โดยปริยาย

หากต้องการดูชื่อที่แน่นอนของสิทธิ์เข้าถึงการอ่านสำหรับผู้ให้บริการที่คุณใช้อยู่ รวมถึงชื่อของสิทธิ์การเข้าถึงอื่นๆ ที่ผู้ให้บริการใช้ โปรดดูในเอกสารประกอบของผู้ให้บริการดังกล่าว

บทบาทของสิทธิ์ในการเข้าถึงผู้ให้บริการมีคำอธิบายอย่างละเอียดในส่วนสิทธิ์ของผู้ให้บริการเนื้อหา

ผู้ให้บริการพจนานุกรมของผู้ใช้จะกำหนดสิทธิ์ android.permission.READ_USER_DICTIONARY ในไฟล์ Manifest ดังนั้นแอปพลิเคชันที่อ่านข้อมูลจากผู้ให้บริการจะต้องขอสิทธิ์นี้

สร้างคําค้นหา

ขั้นตอนถัดไปในการดึงข้อมูลจากผู้ให้บริการคือการสร้างการค้นหา ข้อมูลโค้ดต่อไปนี้จะกำหนดตัวแปรบางอย่างสำหรับการเข้าถึงผู้ให้บริการพจนานุกรมของผู้ใช้

Kotlin

// A "projection" defines the columns that are returned for each row
private val mProjection: Array<String> = arrayOf(
        UserDictionary.Words._ID,    // Contract class constant for the _ID column name
        UserDictionary.Words.WORD,   // Contract class constant for the word column name
        UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
)

// Defines a string to contain the selection clause
private var selectionClause: String? = null

// Declares an array to contain selection arguments
private lateinit var selectionArgs: Array<String>

Java

// A "projection" defines the columns that are returned for each row
String[] mProjection =
{
    UserDictionary.Words._ID,    // Contract class constant for the _ID column name
    UserDictionary.Words.WORD,   // Contract class constant for the word column name
    UserDictionary.Words.LOCALE  // Contract class constant for the locale column name
};

// Defines a string to contain the selection clause
String selectionClause = null;

// Initializes an array to contain selection arguments
String[] selectionArgs = {""};

ข้อมูลโค้ดถัดไปแสดงวิธีใช้ ContentResolver.query() โดยยกตัวอย่างจากพจนานุกรมของผู้ใช้ การค้นหาไคลเอ็นต์ของผู้ให้บริการคล้ายกับคําค้นหา SQL และมีชุดคอลัมน์ที่จะแสดง ชุดเกณฑ์การเลือก และลําดับการจัดเรียง

ชุดคอลัมน์ที่การค้นหาแสดงผลเรียกว่าโปรเจ็กชัน และตัวแปรคือ mProjection

นิพจน์ที่ระบุแถวที่จะดึงข้อมูลจะแยกออกเป็นวลีการเลือกและอาร์กิวเมนต์การเลือก คำสั่งการเลือกคือชุดค่าผสมของนิพจน์เชิงตรรกะและบูลีน ชื่อคอลัมน์ และค่า ตัวแปรคือ mSelectionClause หากคุณระบุพารามิเตอร์ที่แทนที่ได้ ? แทนค่า วิธีการค้นหาจะดึงค่าจากอาร์เรย์อาร์กิวเมนต์การเลือก ซึ่งเป็นตัวแปร mSelectionArgs

ในข้อมูลโค้ดต่อไปนี้ หากผู้ใช้ไม่ได้ป้อนคำ ระบบจะตั้งค่าคำสั่งการเลือกเป็น null และคำค้นหาจะแสดงผลคำทั้งหมดในผู้ให้บริการ หากผู้ใช้ป้อนคำ ระบบจะตั้งค่าวลีการเลือกเป็น UserDictionary.Words.WORD + " = ?" และจะตั้งค่าองค์ประกอบแรกของอาร์เรย์อาร์กิวเมนต์การเลือกเป็นคำที่ผู้ใช้ป้อน

Kotlin

/*
 * This declares a String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let {
    selectionClause = "${UserDictionary.Words.WORD} = ?"
    arrayOf(it)
} ?: run {
    selectionClause = null
    emptyArray<String>()
}

// Does a query against the table and returns a Cursor object
mCursor = contentResolver.query(
        UserDictionary.Words.CONTENT_URI, // The content URI of the words table
        projection,                       // The columns to return for each row
        selectionClause,                  // Either null or the word the user entered
        selectionArgs,                    // Either empty or the string the user entered
        sortOrder                         // The sort order for the returned rows
)

// Some providers return null if an error occurs, others throw an exception
when (mCursor?.count) {
    null -> {
        /*
         * Insert code here to handle the error. Be sure not to use the cursor!
         * You might want to call android.util.Log.e() to log this error.
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search is unsuccessful. This isn't
         * necessarily an error. You might want to offer the user the option to insert a new
         * row, or re-type the search term.
         */
    }
    else -> {
        // Insert code here to do something with the results
    }
}

Java

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] selectionArgs = {""};

// Gets a word from the UI
searchString = searchWord.getText().toString();

// Remember to insert code here to check for invalid or malicious input

// If the word is the empty string, gets everything
if (TextUtils.isEmpty(searchString)) {
    // Setting the selection clause to null returns all words
    selectionClause = null;
    selectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered
    selectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments
    selectionArgs[0] = searchString;

}

// Does a query against the table and returns a Cursor object
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI, // The content URI of the words table
    projection,                       // The columns to return for each row
    selectionClause,                  // Either null or the word the user entered
    selectionArgs,                    // Either empty or the string the user entered
    sortOrder);                       // The sort order for the returned rows

// Some providers return null if an error occurs, others throw an exception
if (null == mCursor) {
    /*
     * Insert code here to handle the error. Be sure not to use the cursor! You can
     * call android.util.Log.e() to log this error.
     *
     */
// If the Cursor is empty, the provider found no matches
} else if (mCursor.getCount() < 1) {

    /*
     * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily
     * an error. You can offer the user the option to insert a new row, or re-type the
     * search term.
     */

} else {
    // Insert code here to do something with the results

}

คำค้นหานี้คล้ายกับคำสั่ง SQL ต่อไปนี้

SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;

ในคำสั่ง SQL นี้ ระบบจะใช้ชื่อคอลัมน์จริงแทนค่าคงที่ของคลาสสัญญา

ป้องกันอินพุตที่เป็นอันตราย

หากข้อมูลที่มีการจัดการโดยผู้ให้บริการเนื้อหาอยู่ในฐานข้อมูล SQL การรวมข้อมูลภายนอกที่ไม่น่าเชื่อถือไว้ในคำสั่ง SQL ดิบอาจทําให้เกิดการแทรก SQL

ลองพิจารณาคำสั่งการเลือกต่อไปนี้

Kotlin

// Constructs a selection clause by concatenating the user's input to the column name
var selectionClause = "var = $mUserInput"

Java

// Constructs a selection clause by concatenating the user's input to the column name
String selectionClause = "var = " + userInput;

ซึ่งหมายความว่าคุณอนุญาตให้ผู้ใช้ต่อท้าย SQL ที่เป็นอันตรายลงในคำสั่ง SQL เช่น ผู้ใช้สามารถป้อน "nothing; DROP TABLE *;" สำหรับ mUserInput ซึ่งจะส่งผลให้เกิดคำสั่ง SELECT var = nothing; DROP TABLE *;

เนื่องจากระบบจะถือว่าวลีการเลือกเป็นคำสั่ง SQL จึงอาจทำให้ผู้ให้บริการลบตารางทั้งหมดในฐานข้อมูล SQLite ที่เกี่ยวข้อง เว้นแต่จะมีการตั้งค่าผู้ให้บริการให้ตรวจจับการพยายามแทรก SQL

หากต้องการหลีกเลี่ยงปัญหานี้ ให้ใช้วลีการเลือกที่ใช้ ? เป็นพารามิเตอร์แบบแทนที่ได้และอาร์เรย์ที่แยกต่างหากของอาร์กิวเมนต์การเลือก วิธีนี้จะทำให้ระบบเชื่อมโยงอินพุตของผู้ใช้กับการค้นหาโดยตรงแทนที่จะตีความว่าเป็นคำสั่ง SQL เนื่องจากระบบไม่ถือว่าข้อความเป็น SQL อินพุตของผู้ใช้จึงแทรก SQL ที่เป็นอันตรายไม่ได้ โปรดใช้วลีการเลือกนี้แทนที่จะใช้การต่อกันเพื่อรวมอินพุตของผู้ใช้

Kotlin

// Constructs a selection clause with a replaceable parameter
var selectionClause = "var = ?"

Java

// Constructs a selection clause with a replaceable parameter
String selectionClause =  "var = ?";

ตั้งค่าอาร์เรย์ของอาร์กิวเมนต์การเลือกดังนี้

Kotlin

// Defines a mutable list to contain the selection arguments
var selectionArgs: MutableList<String> = mutableListOf()

Java

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

ใส่ค่าในอาร์เรย์อาร์กิวเมนต์การเลือกดังนี้

Kotlin

// Adds the user's input to the selection argument
selectionArgs += userInput

Java

// Sets the selection argument to the user's input
selectionArgs[0] = userInput;

วลีการเลือกที่ใช้ ? เป็นพารามิเตอร์แบบแทนที่ได้และอาร์เรย์ของอาร์กิวเมนต์การเลือกเป็นวิธีที่แนะนำในการระบุการเลือก แม้ว่าผู้ให้บริการจะไม่อิงตามฐานข้อมูล SQL ก็ตาม

แสดงผลการค้นหา

เมธอดไคลเอ็นต์ ContentResolver.query() จะแสดงผล Cursor ที่มีคอลัมน์ที่ระบุโดยโปรเจ็กชันของคําค้นหาสําหรับแถวที่ตรงกับเกณฑ์การเลือกของคําค้นหาเสมอ ออบเจ็กต์ Cursor ให้สิทธิ์การอ่านแบบสุ่มในแถวและคอลัมน์ที่มี

เมื่อใช้เมธอด Cursor คุณจะวนซ้ำแถวในผลการค้นหา ระบุประเภทข้อมูลของคอลัมน์แต่ละคอลัมน์ รับข้อมูลจากคอลัมน์ และตรวจสอบพร็อพเพอร์ตี้อื่นๆ ของผลลัพธ์ได้

การติดตั้งใช้งาน Cursor บางรายการจะอัปเดตออบเจ็กต์โดยอัตโนมัติเมื่อข้อมูลของผู้ให้บริการมีการเปลี่ยนแปลง ทริกเกอร์เมธอดในออบเจ็กต์สังเกตการณ์เมื่อ Cursor มีการเปลี่ยนแปลง หรือทั้ง 2 อย่าง

หมายเหตุ: ผู้ให้บริการสามารถจํากัดการเข้าถึงคอลัมน์ตามลักษณะของออบเจ็กต์ที่ทำการค้นหา ตัวอย่างเช่น ผู้ให้บริการรายชื่อติดต่อจะจํากัดการเข้าถึงคอลัมน์บางคอลัมน์ไว้สําหรับอะแดปเตอร์การซิงค์ จึงไม่ได้แสดงคอลัมน์เหล่านั้นในกิจกรรมหรือบริการ

หากไม่มีแถวที่ตรงกับเกณฑ์การเลือก ผู้ให้บริการจะแสดงผลออบเจ็กต์ Cursor ที่มี Cursor.getCount() เป็น 0 ซึ่งก็คือเคอร์เซอร์ว่าง

หากเกิดข้อผิดพลาดภายใน ผลการค้นหาจะขึ้นอยู่กับผู้ให้บริการรายนั้นๆ ฟังก์ชันนี้อาจแสดงผล null หรือแสดงผล Exception

เนื่องจาก Cursor คือรายการแถว วิธีที่ดีในการแสดงเนื้อหาของ Cursor คือลิงก์ Cursor กับ ListView โดยใช้ SimpleCursorAdapter

ข้อมูลโค้ดต่อไปนี้ต่อจากโค้ดของข้อมูลโค้ดก่อนหน้า โดยจะสร้างออบเจ็กต์ SimpleCursorAdapter ที่มี Cursor ที่ดึงมาจากคําค้นหา และตั้งค่าออบเจ็กต์นี้เป็นอะแดปเตอร์สําหรับ ListView

Kotlin

// Defines a list of columns to retrieve from the Cursor and load into an output row
val wordListColumns : Array<String> = arrayOf(
        UserDictionary.Words.WORD,      // Contract class constant containing the word column name
        UserDictionary.Words.LOCALE     // Contract class constant containing the locale column name
)

// Defines a list of View IDs that receive the Cursor columns for each row
val wordListItems = intArrayOf(R.id.dictWord, R.id.locale)

// Creates a new SimpleCursorAdapter
cursorAdapter = SimpleCursorAdapter(
        applicationContext,             // The application's Context object
        R.layout.wordlistrow,           // A layout in XML for one row in the ListView
        mCursor,                        // The result from the query
        wordListColumns,                // A string array of column names in the cursor
        wordListItems,                  // An integer array of view IDs in the row layout
        0                               // Flags (usually none are needed)
)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter)

Java

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] wordListColumns =
{
    UserDictionary.Words.WORD,   // Contract class constant containing the word column name
    UserDictionary.Words.LOCALE  // Contract class constant containing the locale column name
};

// Defines a list of View IDs that receive the Cursor columns for each row
int[] wordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
cursorAdapter = new SimpleCursorAdapter(
    getApplicationContext(),               // The application's Context object
    R.layout.wordlistrow,                  // A layout in XML for one row in the ListView
    mCursor,                               // The result from the query
    wordListColumns,                       // A string array of column names in the cursor
    wordListItems,                         // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
wordList.setAdapter(cursorAdapter);

หมายเหตุ: หากต้องการถอยหลัง ListView ด้วย Cursor เคอร์เซอร์ต้องมีคอลัมน์ชื่อ _ID ด้วยเหตุนี้ คําค้นหาที่แสดงก่อนหน้านี้จึงดึงข้อมูลคอลัมน์ _ID สําหรับตาราง Words แม้ว่า ListView จะไม่แสดงก็ตาม ข้อจำกัดนี้ยังอธิบายเหตุผลที่ผู้ให้บริการส่วนใหญ่มีคอลัมน์ _ID สำหรับแต่ละตารางด้วย

รับข้อมูลจากผลการค้นหา

นอกจากการแสดงผลการค้นหาแล้ว คุณยังนำผลการค้นหาดังกล่าวไปใช้กับงานอื่นๆ ได้ด้วย เช่น คุณสามารถดึงข้อมูลการสะกดจากผู้ให้บริการพจนานุกรมของผู้ใช้ แล้วค้นหาการสะกดดังกล่าวในผู้ให้บริการรายอื่น โดยทำซ้ำแถวใน Cursor ดังที่แสดงในตัวอย่างต่อไปนี้

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers might throw an Exception instead of returning null.
*/
mCursor?.apply {
    // Determine the column index of the column named "word"
    val index: Int = getColumnIndex(UserDictionary.Words.WORD)

    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (moveToNext()) {
        // Gets the value from the column
        newWord = getString(index)

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
}

Java

// Determine the column index of the column named "word"
int index = mCursor.getColumnIndex(UserDictionary.Words.WORD);

/*
 * Only executes if the cursor is valid. The User Dictionary Provider returns null if
 * an internal error occurs. Other providers might throw an Exception instead of returning null.
 */

if (mCursor != null) {
    /*
     * Moves to the next row in the cursor. Before the first movement in the cursor, the
     * "row pointer" is -1, and if you try to retrieve data at that position you get an
     * exception.
     */
    while (mCursor.moveToNext()) {

        // Gets the value from the column
        newWord = mCursor.getString(index);

        // Insert code here to process the retrieved word
        ...
        // End of while loop
    }
} else {

    // Insert code here to report an error if the cursor is null or the provider threw an exception
}

การใช้งาน Cursor มีวิธี "รับ" หลายวิธีในการดึงข้อมูลประเภทต่างๆ จากออบเจ็กต์ เช่น ข้อมูลโค้ดก่อนหน้าใช้ getString() คอลัมน์เหล่านี้ยังมีเมธอด getType() ที่แสดงผลค่าที่ระบุประเภทข้อมูลของคอลัมน์ด้วย

ปล่อยทรัพยากรผลการค้นหา

คุณต้องปิดออบเจ็กต์ Cursor หากไม่จําเป็นต้องใช้อีกต่อไป เพื่อให้ระบบปล่อยทรัพยากรที่เชื่อมโยงกับออบเจ็กต์เหล่านั้นได้เร็วขึ้น ซึ่งทำได้โดยการเรียกใช้ close() หรือใช้คำสั่ง try-with-resources ในภาษาโปรแกรม Java หรือฟังก์ชัน use() ในภาษาโปรแกรม Kotlin

สิทธิ์ของผู้ให้บริการเนื้อหา

แอปพลิเคชันของผู้ให้บริการสามารถระบุสิทธิ์ที่แอปพลิเคชันอื่นๆ ต้องมีเพื่อเข้าถึงข้อมูลของผู้ให้บริการ สิทธิ์เหล่านี้ช่วยให้ผู้ใช้ทราบว่าแอปพลิเคชันพยายามเข้าถึงข้อมูลใด แอปพลิเคชันอื่นๆ จะขอสิทธิ์ที่จําเป็นในการเข้าถึงผู้ให้บริการตามข้อกําหนดของผู้ให้บริการ ผู้ใช้ปลายทางจะเห็นสิทธิ์ที่ขอเมื่อติดตั้งแอปพลิเคชัน

หากแอปพลิเคชันของผู้ให้บริการไม่ได้ระบุสิทธิ์ใดๆ แอปพลิเคชันอื่นๆ จะไม่มีสิทธิ์เข้าถึงข้อมูลของผู้ให้บริการ เว้นแต่ว่าจะมีการส่งออกผู้ให้บริการ นอกจากนี้ คอมโพเนนต์ในแอปพลิเคชันของผู้ให้บริการจะมีสิทธิ์อ่านและเขียนอย่างเต็มรูปแบบเสมอ ไม่ว่าจะระบุสิทธิ์ใดไว้ก็ตาม

ผู้ให้บริการพจนานุกรมของผู้ใช้ต้องมีสิทธิ์ android.permission.READ_USER_DICTIONARY เพื่อดึงข้อมูลจากพจนานุกรม ผู้ให้บริการมีสิทธิ์android.permission.WRITE_USER_DICTIONARY แยกต่างหากในการแทรก อัปเดต หรือลบข้อมูล

หากต้องการสิทธิ์ที่จำเป็นในการเข้าถึงผู้ให้บริการ แอปพลิเคชันจะขอพร้อมเอลิเมนต์ <uses-permission> ในไฟล์ Manifest เมื่อ Android Package Manager ติดตั้งแอปพลิเคชัน ผู้ใช้ต้องอนุมัติสิทธิ์ทั้งหมดที่แอปพลิเคชันขอ หากผู้ใช้อนุมัติ เครื่องมือจัดการแพ็กเกจจะติดตั้งต่อ หากผู้ใช้ไม่อนุมัติ โปรแกรมจัดการแพ็กเกจจะหยุดการติดตั้ง

ตัวอย่างองค์ประกอบ <uses-permission> ต่อไปนี้ขอสิทธิ์อ่านจากผู้ให้บริการพจนานุกรมของผู้ใช้

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

ผลกระทบของสิทธิ์ในการเข้าถึงของผู้ให้บริการมีคำอธิบายอย่างละเอียดในเคล็ดลับด้านความปลอดภัย

แทรก อัปเดต และลบข้อมูล

คุณใช้การโต้ตอบระหว่างไคลเอ็นต์ของผู้ให้บริการกับ ContentProvider ของผู้ให้บริการเพื่อแก้ไขข้อมูลด้วย เช่นเดียวกับการดึงข้อมูลจากผู้ให้บริการ คุณเรียกเมธอดของ ContentResolver ด้วยอาร์กิวเมนต์ที่ส่งไปยังเมธอดที่เกี่ยวข้องของ ContentProvider ผู้ให้บริการและไคลเอ็นต์ของผู้ให้บริการจะจัดการความปลอดภัยและการสื่อสารระหว่างโปรเซสโดยอัตโนมัติ

แทรกข้อมูล

หากต้องการแทรกข้อมูลลงในผู้ให้บริการ ให้เรียกใช้วิธี ContentResolver.insert() วิธีนี้จะแทรกแถวใหม่ลงในผู้ให้บริการและแสดง URI เนื้อหาสำหรับแถวนั้น ข้อมูลโค้ดต่อไปนี้แสดงวิธีแทรกคำใหม่ลงในผู้ให้บริการพจนานุกรมของผู้ใช้

Kotlin

// Defines a new Uri object that receives the result of the insertion
lateinit var newUri: Uri
...
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
    /*
     * Sets the values of each column and inserts the word. The arguments to the "put"
     * method are "column name" and "value".
     */
    put(UserDictionary.Words.APP_ID, "example.user")
    put(UserDictionary.Words.LOCALE, "en_US")
    put(UserDictionary.Words.WORD, "insert")
    put(UserDictionary.Words.FREQUENCY, "100")

}

newUri = contentResolver.insert(
        UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
        newValues                           // The values to insert
)

Java

// Defines a new Uri object that receives the result of the insertion
Uri newUri;
...
// Defines an object to contain the new values to insert
ContentValues newValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value".
 */
newValues.put(UserDictionary.Words.APP_ID, "example.user");
newValues.put(UserDictionary.Words.LOCALE, "en_US");
newValues.put(UserDictionary.Words.WORD, "insert");
newValues.put(UserDictionary.Words.FREQUENCY, "100");

newUri = getContentResolver().insert(
    UserDictionary.Words.CONTENT_URI,   // The UserDictionary content URI
    newValues                           // The values to insert
);

ข้อมูลสำหรับแถวใหม่จะอยู่ในออบเจ็กต์ ContentValues รายการเดียว ซึ่งมีลักษณะคล้ายกับเคอร์เซอร์แถวเดียว คอลัมน์ในออบเจ็กต์นี้ไม่จำเป็นต้องมีประเภทข้อมูลเดียวกัน และหากไม่ต้องการกำหนดค่าเลย คุณสามารถตั้งค่าคอลัมน์เป็น null โดยใช้ ContentValues.putNull()

ข้อมูลโค้ดก่อนหน้าไม่ได้เพิ่มคอลัมน์ _ID เนื่องจากคอลัมน์นี้ได้รับการดูแลรักษาโดยอัตโนมัติ ผู้ให้บริการจะกำหนดค่า _ID ที่ไม่ซ้ำกันให้กับทุกแถวที่เพิ่ม โดยปกติแล้วผู้ให้บริการจะใช้ค่านี้เป็นคีย์หลักของตาราง

URI ของเนื้อหาที่แสดงผลใน newUri จะระบุแถวที่เพิ่มใหม่ในรูปแบบต่อไปนี้

content://user_dictionary/words/<id_value>

<id_value> คือเนื้อหาของ _ID สำหรับแถวใหม่ ผู้ให้บริการส่วนใหญ่สามารถตรวจหา URI เนื้อหารูปแบบนี้ได้โดยอัตโนมัติ จากนั้นจึงดำเนินการตามคำขอในแถวนั้นๆ

หากต้องการรับค่าของ _ID จาก Uri ที่แสดงผล ให้เรียกใช้ ContentUris.parseId()

อัปเดตข้อมูล

หากต้องการอัปเดตแถว คุณต้องใช้ออบเจ็กต์ ContentValues ที่มีค่าที่อัปเดตแล้ว เช่นเดียวกับการแทรกและเกณฑ์การเลือกที่ใช้กับคําค้นหา เมธอดไคลเอ็นต์ที่คุณใช้คือ ContentResolver.update() คุณเพียงต้องเพิ่มค่าลงในออบเจ็กต์ ContentValues สำหรับคอลัมน์ที่อัปเดตเท่านั้น หากต้องการล้างเนื้อหาของคอลัมน์ ให้ตั้งค่าเป็น null

ข้อมูลโค้ดต่อไปนี้จะเปลี่ยนแถวทั้งหมดที่มีภาษา "en" เป็นภาษา null ค่าผลลัพธ์คือจำนวนแถวที่อัปเดต

Kotlin

// Defines an object to contain the updated values
val updateValues = ContentValues().apply {
    /*
     * Sets the updated value and updates the selected words.
     */
    putNull(UserDictionary.Words.LOCALE)
}

// Defines selection criteria for the rows you want to update
val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?"
val selectionArgs: Array<String> = arrayOf("en_%")

// Defines a variable to contain the number of updated rows
var rowsUpdated: Int = 0
...
rowsUpdated = contentResolver.update(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        updateValues,                      // The columns to update
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines an object to contain the updated values
ContentValues updateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String selectionClause = UserDictionary.Words.LOCALE +  " LIKE ?";
String[] selectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int rowsUpdated = 0;
...
/*
 * Sets the updated value and updates the selected words.
 */
updateValues.putNull(UserDictionary.Words.LOCALE);

rowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    updateValues,                      // The columns to update
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

ปรับปรุงข้อมูลที่ผู้ใช้ป้อนเมื่อเรียกใช้ ContentResolver.update() ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในส่วนป้องกันอินพุตที่เป็นอันตราย

ลบข้อมูล

การลบแถวจะคล้ายกับการดึงข้อมูลแถว คุณจะระบุเกณฑ์การเลือกสำหรับแถวที่ต้องการลบได้ และเมธอดไคลเอ็นต์จะแสดงผลจำนวนแถวที่ลบ ข้อมูลโค้ดต่อไปนี้จะลบแถวที่มีรหัสแอปตรงกับ "user" เมธอดจะแสดงผลจำนวนแถวที่ลบ

Kotlin

// Defines selection criteria for the rows you want to delete
val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?"
val selectionArgs: Array<String> = arrayOf("user")

// Defines a variable to contain the number of rows deleted
var rowsDeleted: Int = 0
...
// Deletes the words that match the selection criteria
rowsDeleted = contentResolver.delete(
        UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
        selectionClause,                   // The column to select on
        selectionArgs                      // The value to compare to
)

Java

// Defines selection criteria for the rows you want to delete
String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] selectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int rowsDeleted = 0;
...
// Deletes the words that match the selection criteria
rowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,  // The UserDictionary content URI
    selectionClause,                   // The column to select on
    selectionArgs                      // The value to compare to
);

ปรับปรุงข้อมูลที่ผู้ใช้ป้อนเมื่อเรียกใช้ ContentResolver.delete() ดูข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ในส่วนป้องกันอินพุตที่เป็นอันตราย

ประเภทข้อมูลของผู้ให้บริการ

ผู้ให้บริการเนื้อหาสามารถนำเสนอข้อมูลประเภทต่างๆ ได้มากมาย ผู้ให้บริการพจนานุกรมของผู้ใช้มีให้บริการเฉพาะข้อความ แต่ผู้ให้บริการอาจเสนอรูปแบบต่อไปนี้ด้วย

  • จำนวนเต็ม
  • จำนวนเต็มแบบยาว (long)
  • จุดลอยตัว
  • จุดลอยตัวแบบยาว (Double)

ข้อมูลประเภทอื่นที่ผู้ให้บริการมักใช้คือออบเจ็กต์ขนาดใหญ่แบบไบนารี (BLOB) ที่ติดตั้งใช้งานเป็นอาร์เรย์ไบต์ 64 KB คุณดูประเภทข้อมูลที่ใช้ได้ได้โดยดูที่เมธอด "get" ของคลาส Cursor

โดยทั่วไปแล้ว ประเภทข้อมูลของคอลัมน์แต่ละคอลัมน์ในผู้ให้บริการจะแสดงอยู่ในเอกสารประกอบของผู้ให้บริการ ประเภทข้อมูลของผู้ให้บริการพจนานุกรมผู้ใช้แสดงอยู่ในเอกสารประกอบอ้างอิงสำหรับคลาสสัญญา UserDictionary.Words คลาส Contract อธิบายไว้ในส่วนคลาส Contract นอกจากนี้ คุณยังระบุประเภทข้อมูลได้โดยเรียกใช้ Cursor.getType()

นอกจากนี้ ผู้ให้บริการยังต้องดูแลรักษาข้อมูลประเภทข้อมูล MIME สำหรับ URI เนื้อหาแต่ละรายการที่ระบุ คุณสามารถใช้ข้อมูลประเภท MIME เพื่อดูว่าแอปพลิเคชันจัดการข้อมูลที่ผู้ให้บริการเสนอได้หรือไม่ หรือเลือกประเภทการจัดการตามประเภท MIME โดยปกติแล้ว คุณต้องใช้ประเภท MIME เมื่อทำงานร่วมกับผู้ให้บริการที่มีโครงสร้างข้อมูลหรือไฟล์ที่ซับซ้อน

ตัวอย่างเช่น ตาราง ContactsContract.Data ในผู้ให้บริการรายชื่อติดต่อใช้ประเภท MIME เพื่อติดป้ายกำกับประเภทข้อมูลติดต่อที่จัดเก็บในแต่ละแถว หากต้องการดูประเภท MIME ที่สอดคล้องกับ URI ของเนื้อหา ให้เรียกใช้ ContentResolver.getType()

ส่วนการอ้างอิงประเภท MIME จะอธิบายไวยากรณ์ของประเภท MIME ทั้งแบบมาตรฐานและที่กำหนดเอง

การเข้าถึงของผู้ให้บริการในรูปแบบอื่น

รูปแบบการเข้าถึงผู้ให้บริการทางเลือก 3 รูปแบบที่สําคัญในการพัฒนาแอปพลิเคชัน ได้แก่

  • การเข้าถึงแบบเป็นกลุ่ม: คุณสามารถสร้างการเรียกใช้การเข้าถึงแบบเป็นกลุ่มด้วยเมธอดในคลาส ContentProviderOperation แล้วนำไปใช้กับ ContentResolver.applyBatch()
  • การค้นหาแบบไม่พร้อมกัน: ทําการค้นหาในชุดข้อความแยกต่างหาก คุณใช้ออบเจ็กต์ CursorLoader ได้ ตัวอย่างในคู่มือเครื่องมือโหลดจะสาธิตวิธีการดำเนินการ
  • การเข้าถึงข้อมูลโดยใช้ Intent: แม้ว่าคุณจะส่ง Intent ไปยังผู้ให้บริการโดยตรงไม่ได้ แต่ก็สามารถส่ง Intent ไปยังแอปพลิเคชันของผู้ให้บริการได้ ซึ่งมักจะมีเครื่องมือที่เหมาะที่สุดในการแก้ไขข้อมูลของผู้ให้บริการ

การเข้าถึงและการแก้ไขแบบเป็นกลุ่มโดยใช้ Intent จะอธิบายไว้ในส่วนต่อไปนี้

การเข้าถึงแบบเป็นกลุ่ม

การเข้าถึงผู้ให้บริการแบบเป็นกลุ่มมีประโยชน์สำหรับการแทรกแถวจํานวนมาก แทรกแถวในหลายตารางในการเรียกใช้เมธอดเดียวกัน และโดยทั่วไปสําหรับการดําเนินการชุดหนึ่งข้ามขอบเขตกระบวนการเป็นธุรกรรม ซึ่งเรียกว่าการดำเนินการแบบอะตอม

หากต้องการเข้าถึงผู้ให้บริการในโหมดแบบกลุ่ม ให้สร้างอาร์เรย์ของออบเจ็กต์ ContentProviderOperation แล้วส่งรายการเหล่านั้นไปยังผู้ให้บริการเนื้อหาด้วย ContentResolver.applyBatch() คุณต้องส่งอำนาจของผู้ให้บริการเนื้อหาไปยังเมธอดนี้ แทนที่จะเป็น URI ของเนื้อหาหนึ่งๆ

ซึ่งช่วยให้ออบเจ็กต์ ContentProviderOperation แต่ละรายการในอาร์เรย์ทำงานกับตารางอื่นได้ การเรียกใช้ ContentResolver.applyBatch() จะแสดงผลลัพธ์เป็นอาร์เรย์

คําอธิบายของContactsContract.RawContactsคลาสสัญญา มีข้อมูลโค้ดที่แสดงการแทรกข้อมูลหลายรายการพร้อมกัน

การเข้าถึงข้อมูลโดยใช้ Intent

Intent สามารถให้สิทธิ์เข้าถึงผู้ให้บริการเนื้อหาได้แบบอ้อม คุณอนุญาตให้ผู้ใช้เข้าถึงข้อมูลในผู้ให้บริการได้แม้ว่าแอปพลิเคชันจะไม่มีสิทธิ์เข้าถึงโดยการดึงผลลัพธ์ Intent กลับมาจากแอปพลิเคชันที่มีสิทธิ์หรือด้วยการเปิดใช้งานแอปพลิเคชันที่มีสิทธิ์และให้ผู้ใช้ทำงานในนั้น

รับสิทธิ์เข้าถึงด้วยสิทธิ์ชั่วคราว

คุณสามารถเข้าถึงข้อมูลในผู้ให้บริการเนื้อหาได้แม้ว่าจะไม่มีสิทธิ์เข้าถึงที่เหมาะสมก็ตาม โดยการส่ง Intent ไปยังแอปพลิเคชันที่มีสิทธิ์และรับ Intent ผลลัพธ์ที่มีสิทธิ์ URI สิทธิ์เหล่านี้เป็นสิทธิ์สำหรับ URI เนื้อหาที่เฉพาะเจาะจง ซึ่งจะใช้งานได้จนกว่ากิจกรรมที่ได้รับสิทธิ์จะสิ้นสุดลง แอปพลิเคชันที่มีสิทธิ์ถาวรจะให้สิทธิ์ชั่วคราวโดยตั้งค่าการติดธงใน Intent ของผลลัพธ์ ดังนี้

หมายเหตุ: แฟล็กเหล่านี้ไม่ได้ให้สิทธิ์เข้าถึงการอ่านหรือการเขียนทั่วไปแก่ผู้ให้บริการที่เป็นเจ้าของสิทธิ์ที่อยู่ใน URI เนื้อหา สิทธิ์เข้าถึงมีไว้สำหรับ URI เท่านั้น

เมื่อส่ง URI เนื้อหาไปยังแอปอื่น ให้ใส่ Flag เหล่านี้อย่างน้อย 1 รายการ Flag ดังกล่าวจะมอบความสามารถต่อไปนี้ให้แก่แอปใดก็ตามที่ได้รับ Intent และกำหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ขึ้นไป

  • อ่านจากหรือเขียนไปยังข้อมูลที่ URI เนื้อหาแสดงโดยขึ้นอยู่กับแฟล็กที่รวมอยู่ใน Intent
  • รับสิทธิ์เข้าถึงแพ็กเกจในแอปที่มีผู้ให้บริการเนื้อหาที่ตรงกับสิทธิ์ URI แอปที่ส่ง Intent และแอปที่มีผู้ให้บริการเนื้อหาอาจเป็นคนละแอปกัน

ผู้ให้บริการจะกำหนดสิทธิ์ URI สำหรับ URI ของเนื้อหาในไฟล์ Manifest โดยใช้แอตทริบิวต์ android:grantUriPermissions ขององค์ประกอบ <provider> รวมถึงองค์ประกอบย่อย <grant-uri-permission> ขององค์ประกอบ <provider> กลไกสิทธิ์ URI มีคำอธิบายอย่างละเอียดในคู่มือสิทธิ์ใน Android

เช่น คุณสามารถดึงข้อมูลของผู้ติดต่อในผู้ให้บริการรายชื่อติดต่อได้ แม้ว่าจะไม่มีสิทธิ์ READ_CONTACTS คุณอาจต้องดำเนินการนี้ในแอปพลิเคชันที่ส่งอีคำอวยพรวันเกิดให้รายชื่อติดต่อ แทนที่จะขอ READ_CONTACTS ซึ่งจะให้สิทธิ์เข้าถึงรายชื่อติดต่อทั้งหมดและข้อมูลทั้งหมดของผู้ใช้ ให้ผู้ใช้ควบคุมรายชื่อติดต่อที่แอปพลิเคชันจะใช้ โดยทำตามขั้นตอนต่อไปนี้

  1. ในแอปพลิเคชัน ให้ส่ง Intent ที่มีการดำเนินการ ACTION_PICK และ MIME ประเภท "contacts" CONTENT_ITEM_TYPE โดยใช้เมธอด startActivityForResult()
  2. เนื่องจาก Intent นี้ตรงกับตัวกรอง Intent สําหรับกิจกรรม "การเลือก" ของแอปรายชื่อติดต่อ กิจกรรมดังกล่าวจึงปรากฏขึ้นที่เบื้องหน้า
  3. ในกิจกรรมการเลือก ผู้ใช้จะเลือกรายชื่อติดต่อที่จะอัปเดต เมื่อเกิดเหตุการณ์เช่นนี้ขึ้น กิจกรรมการเลือกจะเรียกใช้ setResult(resultcode, intent) เพื่อตั้งค่า Intent ที่จะคืนให้กับแอปพลิเคชันของคุณ Intent มี URI ของเนื้อหาของผู้ติดต่อที่ผู้ใช้เลือกและ Flag "extras" FLAG_GRANT_READ_URI_PERMISSION แฟล็กเหล่านี้จะให้สิทธิ์ URI แก่แอปในการอ่านข้อมูลสำหรับรายชื่อติดต่อที่ URI เนื้อหาชี้ไป จากนั้นกิจกรรมการเลือกจะเรียก finish() เพื่อส่งคืนการควบคุมไปยังแอปพลิเคชัน
  4. กิจกรรมจะกลับมาอยู่เบื้องหน้า และระบบจะเรียกใช้onActivityResult()วิธีของกิจกรรม เมธอดนี้จะได้รับ Intent ของผลลัพธ์ที่สร้างโดยกิจกรรมการเลือกในแอป People
  5. URI เนื้อหาจาก Intent ของผลการค้นหาช่วยให้คุณอ่านข้อมูลจากผู้ให้บริการ Contacts ได้ แม้ว่าคุณจะไม่ได้ขอสิทธิ์เข้าถึงแบบอ่านอย่างเดียวแบบถาวรจากผู้ให้บริการในไฟล์ Manifest ก็ตาม จากนั้นคุณจะรับข้อมูลวันเกิดหรืออีเมลของรายชื่อติดต่อ แล้วส่งคำทักทายอิเล็กทรอนิกส์ได้

ใช้แอปพลิเคชันอื่น

อีกวิธีหนึ่งในการอนุญาตให้ผู้ใช้แก้ไขข้อมูลที่คุณไม่มีสิทธิ์เข้าถึงคือการเปิดใช้งานแอปพลิเคชันที่มีสิทธิ์และอนุญาตให้ผู้ใช้ดำเนินการในนั้น

ตัวอย่างเช่น แอปพลิเคชันปฏิทินยอมรับACTION_INSERT Intent ที่ช่วยให้คุณเปิดใช้งาน UI แทรกของแอปพลิเคชันได้ คุณส่งผ่านข้อมูล "เพิ่มเติม" ใน Intent นี้ ซึ่งแอปพลิเคชัน ใช้เพื่อป้อนข้อมูล UI ล่วงหน้าได้ เนื่องจากกิจกรรมที่เกิดขึ้นซ้ำมีไวยากรณ์ที่ซับซ้อน วิธีที่เหมาะสมในการแทรกกิจกรรมลงในผู้ให้บริการปฏิทินคือการเปิดใช้งานแอปปฏิทินด้วย ACTION_INSERT แล้วให้ผู้ใช้แทรกกิจกรรมในนั้น

แสดงข้อมูลโดยใช้แอปผู้ช่วย

หากแอปพลิเคชันมีสิทธิ์เข้าถึง คุณอาจยังใช้ Intent แสดงข้อมูลในแอปพลิเคชันอื่นอยู่ ตัวอย่างเช่น แอปพลิเคชันปฏิทินยอมรับ Intent ACTION_VIEW ที่แสดงวันที่หรือเหตุการณ์ที่เฉพาะเจาะจง ซึ่งจะช่วยให้คุณแสดงข้อมูลปฏิทินได้โดยไม่ต้องสร้าง UI ของคุณเอง ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่ภาพรวมของผู้ให้บริการปฏิทิน

แอปพลิเคชันที่คุณส่งความตั้งใจถึงไม่จำเป็นต้องเป็นแอปพลิเคชันที่เชื่อมโยงกับผู้ให้บริการ เช่น คุณสามารถดึงข้อมูลรายชื่อติดต่อจากผู้ให้บริการรายชื่อติดต่อ จากนั้นส่ง Intent ACTION_VIEW ที่มี URI ของเนื้อหาสำหรับรูปภาพของรายชื่อติดต่อไปยังโปรแกรมดูรูปภาพ

คลาส Contract

คลาสสัญญาจะกำหนดค่าคงที่ซึ่งช่วยให้แอปพลิเคชันทำงานร่วมกับ URI ของเนื้อหา ชื่อคอลัมน์ การดำเนินการ Intent และฟีเจอร์อื่นๆ ของผู้ให้บริการเนื้อหาได้ ระบบจะไม่รวมชั้นเรียนแบบสัญญาไว้กับผู้ให้บริการโดยอัตโนมัติ นักพัฒนาซอฟต์แวร์ของผู้ให้บริการต้องกำหนดค่าและทำให้นักพัฒนาซอฟต์แวร์รายอื่นๆ เข้าถึงได้ ผู้ให้บริการหลายรายที่รวมอยู่ในแพลตฟอร์ม Android จะมีคลาสสัญญาที่เกี่ยวข้องในแพ็กเกจ android.provider

ตัวอย่างเช่น ผู้ให้บริการพจนานุกรมผู้ใช้มีคลาสสัญญา UserDictionary ที่มี URI ของเนื้อหาและค่าคงที่ของชื่อคอลัมน์ URI เนื้อหาของตาราง Words ได้รับการกําหนดไว้ในค่าคงที่ UserDictionary.Words.CONTENT_URI คลาส UserDictionary.Words ยังมีค่าคงที่ของชื่อคอลัมน์ ซึ่งใช้ในตัวอย่างข้อมูลในคู่มือนี้ด้วย ตัวอย่างเช่น การฉายภาพการค้นหาจะกําหนดได้ดังนี้

Kotlin

val projection : Array<String> = arrayOf(
        UserDictionary.Words._ID,
        UserDictionary.Words.WORD,
        UserDictionary.Words.LOCALE
)

Java

String[] projection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

คลาสสัญญาอีกคลาสหนึ่งคือ ContactsContract สำหรับผู้ให้บริการรายชื่อติดต่อ เอกสารอ้างอิงสำหรับคลาสนี้มีตัวอย่างข้อมูลโค้ด ContactsContract.Intents.Insert ซึ่งเป็นคลาสย่อยคลาสหนึ่งคือคลาสสัญญาที่มีค่าคงที่สำหรับ Intent และข้อมูล Intent

การอ้างอิงประเภท MIME

ผู้ให้บริการเนื้อหาสามารถแสดงผลประเภทสื่อ MIME มาตรฐาน สตริงประเภท MIME ที่กําหนดเอง หรือทั้ง 2 อย่าง

ประเภท MIME มีรูปแบบดังนี้

type/subtype

เช่น ประเภท MIME ที่รู้จักกันดีอย่าง text/html มีประเภท text และประเภทย่อย html หากผู้ให้บริการแสดงผลประเภทนี้สําหรับ URI หนึ่งๆ หมายความว่าการค้นหาโดยใช้ URI นั้นแสดงผลข้อความที่มีแท็ก HTML

สตริงประเภท MIME ที่กําหนดเองหรือที่เรียกว่าประเภท MIME เฉพาะผู้ให้บริการมีค่า type และ subtype ที่ซับซ้อนกว่า สำหรับหลายแถว ค่าประเภทจะเป็นค่าต่อไปนี้เสมอ

vnd.android.cursor.dir

สำหรับแถวเดียว ค่าประเภทจะเป็นค่าต่อไปนี้เสมอ

vnd.android.cursor.item

subtype เจาะจงผู้ให้บริการ ผู้ให้บริการในตัวของ Android มักจะมีประเภทย่อยที่เรียบง่าย เช่น เมื่อแอปพลิเคชันรายชื่อติดต่อสร้างแถวสำหรับหมายเลขโทรศัพท์ ก็จะตั้งค่าประเภท MIME ต่อไปนี้ในแถว

vnd.android.cursor.item/phone_v2

ค่าประเภทย่อยคือ phone_v2

นักพัฒนาแอปผู้ให้บริการรายอื่นๆ จะสร้างรูปแบบของประเภทย่อยของตนเองได้โดยอิงตามชื่อตารางและสิทธิ์ของผู้ให้บริการ เช่น ลองพิจารณาผู้ให้บริการที่มีตารางเวลารถไฟ สิทธิ์ของผู้ให้บริการคือ com.example.trains และมีตาราง Line1, Line2 และ Line3 การตอบสนองต่อ URI เนื้อหาต่อไปนี้สําหรับตาราง Line1

content://com.example.trains/Line1

ผู้ให้บริการแสดงผลประเภท MIME ต่อไปนี้

vnd.android.cursor.dir/vnd.example.line1

เพื่อตอบสนอง URI เนื้อหาต่อไปนี้สำหรับแถว 5 ในตาราง Line2:

content://com.example.trains/Line2/5

ผู้ให้บริการแสดงผลประเภท MIME ต่อไปนี้

vnd.android.cursor.item/vnd.example.line2

ผู้ให้บริการเนื้อหาส่วนใหญ่จะกำหนดค่าคงที่ของคลาสสัญญาสำหรับประเภท MIME ที่ใช้ ตัวอย่างเช่น คลาสสัญญาของผู้ให้บริการรายชื่อติดต่อ ContactsContract.RawContacts กำหนดค่า CONTENT_ITEM_TYPE คงที่สำหรับประเภท MIME ของแถวรายชื่อติดต่อดิบ 1 แถว

URI ของเนื้อหาสำหรับแถวเดียวจะอธิบายไว้ในส่วนURI ของเนื้อหา