ผู้ให้บริการเนื้อหาจะจัดการสิทธิ์เข้าถึงที่เก็บข้อมูลกลาง ผู้ให้บริการ เป็นส่วนหนึ่งของแอปพลิเคชัน Android ซึ่งมักจะมี UI ของตัวเองเพื่อทำงานกับ ข้อมูลดังกล่าว อย่างไรก็ตาม ผู้ให้บริการเนื้อหาส่วนใหญ่จะใช้โดยผู้ให้บริการเนื้อหารายอื่น ซึ่งเข้าถึงผู้ให้บริการโดยใช้ออบเจ็กต์ไคลเอ็นต์ของผู้ให้บริการ ผู้ให้บริการเมื่อรวมกัน และผู้ให้บริการจะมีอินเทอร์เฟซมาตรฐาน ที่คงเส้นคงวาสำหรับข้อมูลที่จัดการกับ การสื่อสารระหว่างโปรเซส และการเข้าถึงข้อมูลอย่างปลอดภัย
โดยปกติแล้ว คุณทำงานร่วมกับผู้ให้บริการเนื้อหาในสถานการณ์ใดสถานการณ์หนึ่ง ได้แก่ การใช้ เพื่อเข้าถึงผู้ให้บริการเนื้อหาที่มีอยู่ในแอปพลิเคชันอื่นหรือการสร้าง ผู้ให้บริการเนื้อหารายใหม่ในแอปพลิเคชันของคุณเพื่อแชร์ข้อมูลกับแอปพลิเคชันอื่นๆ
หน้านี้ ครอบคลุมข้อมูลพื้นฐานในการทำงานร่วมกับผู้ให้บริการเนื้อหาที่มีอยู่ หากต้องการทราบเกี่ยวกับการติดตั้งใช้งาน ผู้ให้บริการเนื้อหาในแอปพลิเคชันของคุณเองได้ โปรดดู สร้างผู้ให้บริการเนื้อหา
หัวข้อนี้จะอธิบายสิ่งต่อไปนี้
- วิธีการทำงานของผู้ให้บริการเนื้อหา
- API ที่คุณใช้เพื่อดึงข้อมูลจากผู้ให้บริการเนื้อหา
- API ที่คุณใช้เพื่อแทรก อัปเดต หรือลบข้อมูลในผู้ให้บริการเนื้อหา
- ฟีเจอร์อื่นๆ ของ API ที่อำนวยความสะดวกในการทำงานร่วมกับผู้ให้บริการ
ภาพรวม
ผู้ให้บริการเนื้อหานำเสนอข้อมูลไปยังแอปพลิเคชันภายนอก เป็นตารางหนึ่งหรือหลายตาราง คล้ายกับตารางที่พบในฐานข้อมูลเชิงสัมพันธ์ แถวแสดงถึงอินสแตนซ์บางประเภท ที่ผู้ให้บริการจะรวบรวม และแต่ละคอลัมน์ในแถวแสดงถึงข้อมูลแต่ละชิ้น ข้อมูลที่รวบรวมสำหรับอินสแตนซ์
ผู้ให้บริการเนื้อหาประสานการเข้าถึงเลเยอร์พื้นที่เก็บข้อมูลในแอปพลิเคชันของคุณเพื่อ จำนวน API และคอมโพเนนต์ต่างๆ ดังภาพประกอบในรูปที่ 1 ข้อมูลเหล่านั้นได้แก่
- การแชร์การเข้าถึงข้อมูลแอปพลิเคชันของคุณกับแอปพลิเคชันอื่น
- กำลังส่งข้อมูลไปยังวิดเจ็ต
- การแสดงคำแนะนำการค้นหาที่กำหนดเองสำหรับแอปพลิเคชันของคุณผ่านการค้นหา
เฟรมเวิร์กที่ใช้
SearchRecentSuggestionsProvider
- การซิงค์ข้อมูลแอปพลิเคชันกับเซิร์ฟเวอร์ของคุณโดยใช้การดำเนินการ
AbstractThreadedSyncAdapter
- กำลังโหลดข้อมูลใน UI ของคุณโดยใช้
CursorLoader
![ความสัมพันธ์ระหว่างผู้ให้บริการเนื้อหากับคอมโพเนนต์อื่นๆ](https://developer.android.google.cn/static/guide/topics/providers/images/content-provider-tech-stack.png?authuser=9&hl=th)
รูปที่ 1 ความสัมพันธ์ระหว่างผู้ให้บริการเนื้อหากับองค์ประกอบอื่นๆ
เข้าถึงผู้ให้บริการ
เมื่อต้องการเข้าถึงข้อมูลในผู้ให้บริการเนื้อหา ให้ใช้
ContentResolver
ออบเจ็กต์ในแอปพลิเคชันของคุณ
Context
เพื่อสื่อสารกับผู้ให้บริการในฐานะลูกค้า
ออบเจ็กต์ ContentResolver
สื่อสารกับออบเจ็กต์ผู้ให้บริการ
ของคลาสที่ใช้งาน ContentProvider
ผู้ให้บริการ
ได้รับคำขอข้อมูลจากไคลเอ็นต์ ดำเนินการตามที่ขอ และแสดงผล
ผลลัพธ์ ออบเจ็กต์นี้มีเมธอดที่เรียกเมธอดที่มีชื่อเหมือนกันในออบเจ็กต์ผู้ให้บริการ
ตัวอย่างของคลาสย่อยที่เป็นรูปธรรมของ ContentProvider
เมธอด ContentResolver
จะให้ข้อมูลเบื้องต้น
"CRUD" (สร้าง เรียก อัปเดต และลบ) ของพื้นที่เก็บข้อมูลถาวร
รูปแบบทั่วไปสำหรับการเข้าถึง ContentProvider
จาก UI จะใช้
CursorLoader
เพื่อเรียกใช้การค้นหาแบบไม่พร้อมกันในเบื้องหลัง
Activity
หรือ Fragment
ใน UI เรียก
CursorLoader
ในข้อความค้นหา ซึ่งได้ค่า
ContentProvider
โดยใช้ ContentResolver
ซึ่งจะช่วยให้ผู้ใช้ใช้งาน UI ได้ต่อไปขณะที่คำค้นหากำลังทำงาน ช่วงเวลานี้ รูปแบบเกี่ยวข้องกับการโต้ตอบของวัตถุต่างๆ รวมถึง กลไกการจัดเก็บข้อมูลตามที่แสดงในรูปที่ 2
![การโต้ตอบระหว่าง ContentProvider, คลาสอื่นๆ และพื้นที่เก็บข้อมูล](https://developer.android.google.cn/static/guide/topics/providers/images/content-provider-interaction.png?authuser=9&hl=th)
รูปที่ 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
ซึ่งอธิบายไว้
รายละเอียดเพิ่มเติมใน
คู่มือเครื่องมือโหลด นอกจากนี้ บรรทัดโค้ดเป็นเพียงตัวอย่างเท่านั้น แต่ไม่ได้แสดงสมบูรณ์
แอปพลิเคชัน
หากต้องการดึงข้อมูลจากผู้ให้บริการ ให้ทำตามขั้นตอนพื้นฐานต่อไปนี้
- ขอสิทธิ์เข้าถึงการอ่านสำหรับผู้ให้บริการ
- กำหนดโค้ดที่ส่งคำค้นหาไปยังผู้ให้บริการ
ขอสิทธิ์อ่าน
หากต้องการดึงข้อมูลจากผู้ให้บริการ แอปพลิเคชันของคุณต้องมีสิทธิ์ในการอ่านสำหรับ
คุณขอสิทธิ์นี้ระหว่างรันไทม์ไม่ได้ แต่คุณจะต้องระบุว่า
คุณต้องมีสิทธิ์นี้ในไฟล์ 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()
เมธอดที่แสดงค่าที่ระบุ
ประเภทข้อมูลของคอลัมน์
สิทธิ์ของผู้ให้บริการเนื้อหา
แอปพลิเคชันของผู้ให้บริการสามารถระบุสิทธิ์ที่แอปพลิเคชันอื่นต้องมี เข้าถึงข้อมูลของผู้ให้บริการ สิทธิ์เหล่านี้ช่วยให้ผู้ใช้ทราบว่าข้อมูลใด แอปพลิเคชันพยายามเข้าถึง ตามข้อกำหนดของผู้ให้บริการ แอปพลิเคชันอื่นๆ ขอสิทธิ์ที่จำเป็นเพื่อเข้าถึงผู้ให้บริการ ผู้ใช้ปลายทางจะเห็น เมื่อผู้ใช้ติดตั้งแอปพลิเคชัน
หากแอปพลิเคชันของผู้ให้บริการไม่ได้ระบุสิทธิ์ใดๆ หมายความว่าแอปพลิเคชันอื่นจะไม่มี เข้าถึงข้อมูลของผู้ให้บริการ เว้นแต่ว่าจะมีการส่งออกข้อมูลผู้ให้บริการรายนั้น นอกจากนี้ คอมโพเนนต์ ในแอปพลิเคชันของผู้ให้บริการ จะมีสิทธิ์การเข้าถึงเพื่ออ่านและเขียนโดยสมบูรณ์เสมอ โดยไม่คำนึงถึง สิทธิ์ที่ระบุ
ผู้ให้บริการพจนานุกรมผู้ใช้ต้องการ
สิทธิ์ในการดึงข้อมูลจาก 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 ของผลลัพธ์:
-
สิทธิ์อ่าน
FLAG_GRANT_READ_URI_PERMISSION
-
สิทธิ์ในการเขียน:
FLAG_GRANT_WRITE_URI_PERMISSION
หมายเหตุ: แฟล็กเหล่านี้ไม่ได้ให้สิทธิ์เข้าถึงการอ่านหรือการเขียนทั่วไปแก่ผู้ให้บริการ ที่มีสิทธิ์ใน 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
ซึ่งทำให้คุณเข้าถึง
รายชื่อติดต่อของผู้ใช้และข้อมูลทั้งหมดของผู้ใช้ ให้ผู้ใช้ควบคุมได้ว่าจะให้
รายชื่อติดต่อที่แอปพลิเคชันของคุณใช้ โดยใช้กระบวนการต่อไปนี้
-
ในแอปพลิเคชัน ให้ส่ง Intent ที่มีการดำเนินการ
ACTION_PICK
และ "รายชื่อติดต่อ" ประเภท MIMECONTENT_ITEM_TYPE
โดยใช้ เมธอดstartActivityForResult()
- เนื่องจาก Intent นี้ตรงกับตัวกรอง Intent สำหรับ "การเลือก" ของแอป People กิจกรรมจะมาที่เบื้องหน้า
-
ในกิจกรรมการเลือก ให้ผู้ใช้เลือก
รายชื่อติดต่อเพื่ออัปเดต ในกรณีนี้ ระบบจะเรียกใช้กิจกรรมการเลือก
setResult(resultcode, intent)
เพื่อตั้งค่าความตั้งใจที่จะตอบแทนแอปพลิเคชันของคุณ Intent มี URI เนื้อหา ของรายชื่อติดต่อที่ผู้ใช้เลือก และ "เพิ่มเติม" FlagFLAG_GRANT_READ_URI_PERMISSION
แฟล็กเหล่านี้ให้สิทธิ์ URI สิทธิ์แก่แอปของคุณในการอ่านข้อมูลผู้ติดต่อที่ URI เนื้อหา กิจกรรมการเลือกจะเรียกfinish()
ไปยัง กลับไปยังแอปพลิเคชันของคุณ -
กิจกรรมของคุณจะกลับไปที่เบื้องหน้า และระบบจะเรียก
onActivityResult()
เมธอดนี้ได้รับ Intent ของผลลัพธ์ที่สร้างโดยกิจกรรมการเลือกใน แอป People - คุณสามารถอ่านข้อมูลของรายชื่อติดต่อได้ด้วย 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 เนื้อหา