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

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

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

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

หัวข้อนี้จะอธิบายสิ่งต่อไปนี้

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

ภาพรวม

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

ผู้ให้บริการเนื้อหาประสานการเข้าถึงเลเยอร์พื้นที่เก็บข้อมูลในแอปพลิเคชันของคุณเพื่อ จำนวน 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 ผู้ใช้1 100 th-TH 1
precompiler ผู้ใช้14 200 fr_FR 2
applet ผู้ใช้2 225 fr_CA 3
const ผู้ใช้1 255 แต้ม_BR 4
int ผู้ใช้5 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) ตรงกับคำสั่ง SQL SELECT ดังนี้

ตาราง 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 แบบ Raw อาจนำไปสู่การแทรก 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; วางตาราง *;" สำหรับ mUserInput ซึ่ง ผลลัพธ์ในวรรคการเลือก 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 จะลิงก์กับ 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 มี "get" หลายรายการ วิธีการสำหรับ เป็นการเรียกข้อมูลประเภทต่างๆ จากออบเจ็กต์ เช่น ข้อมูลโค้ดก่อนหน้า ใช้ getString() นอกจากนี้ยังมี getType() เมธอดที่แสดงค่าที่ระบุ ประเภทข้อมูลของคอลัมน์

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

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

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

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

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

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

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

ตัวอย่างต่อไปนี้ <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() หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับ โปรดอ่านส่วนป้องกันอินพุตที่เป็นอันตราย

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

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

  • จำนวนเต็ม
  • จำนวนเต็มแบบยาว (ยาว)
  • จุดทศนิยม
  • จุดลอยตัวยาว (คู่)

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

ประเภทข้อมูลสำหรับแต่ละคอลัมน์ในผู้ให้บริการมักจะแสดงอยู่ในเอกสารประกอบ ประเภทข้อมูลของผู้ให้บริการพจนานุกรมผู้ใช้ระบุไว้ในเอกสารอ้างอิง สำหรับคลาสสัญญา UserDictionary.Words ระดับสัญญาคือ ตามที่อธิบายไว้ในส่วนระดับสัญญา นอกจากนี้ คุณยังระบุประเภทข้อมูลได้โดยโทรไปที่ 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 เนื้อหาไปยังแอปอื่น ให้ใส่ค่าต่อไปนี้อย่างน้อย 1 รายการ แฟล็กจะมีความสามารถต่อไปนี้สำหรับแอปที่มี Intent และกำหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ขึ้นไป

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

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

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

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

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

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

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

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

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

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

ระดับสัญญา

คลาสสัญญากำหนดค่าคงที่ที่ช่วยให้แอปพลิเคชันทำงานกับ URI เนื้อหา คอลัมน์ ชื่อ การดำเนินการผ่านความตั้งใจ และฟีเจอร์อื่นๆ ของผู้ให้บริการเนื้อหา ระดับชั้นสัญญาไม่เท่ากับ ที่รวมอยู่ในผู้ให้บริการโดยอัตโนมัติ โดยนักพัฒนาซอฟต์แวร์ของผู้ให้บริการจะต้องกำหนด ให้นักพัฒนาซอฟต์แวร์รายอื่นๆ เข้าถึง ผู้ให้บริการหลายราย มาพร้อมกับ 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 สำหรับ Contacts Provider เอกสารอ้างอิงสำหรับคลาสนี้มีตัวอย่างข้อมูลโค้ด หนึ่งใน คลาสย่อย ContactsContract.Intents.Insert เป็นสัญญา ที่มีค่าคงที่สำหรับข้อมูล Intent และ Intent

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

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

ประเภท 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 เนื้อหาสำหรับแถวเดี่ยวมีการอธิบายไว้ใน URL เนื้อหา