移動開發筆記(十) 跨程序共享數據 運行時權限 Kotlin

1.運行時權限

Android運行時的危險權限
在這裏插入圖片描述
在運行時申請權限代碼:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        makeCall.setOnClickListener {

              //獲得運行時權限
                if(ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)!=PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.CALL_PHONE),1)
                }else{
                    call()
                }

        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when(requestCode){
            1 ->{
                if(grantResults.isNotEmpty()&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    call()
                }else{
                    Toast.makeText(this,"沒有打電話的權限",Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    private fun call(){
        try {
            val intent = Intent(Intent.ACTION_CALL)
            intent.data = Uri.parse("tel:10086")
            startActivity(intent)
        }catch (e : Exception){
            e.printStackTrace()
        }

    }
}

在按鈕點擊事件中,我們構建了一個隱式 Intent,Intent 的 action 指定爲Intent.ACTION_CALL,這是系統內置的打電話的動作,然後在data部分制定了協議是tel,號碼是10086。
第一步先判斷用戶是不是已經給過我們授權了,藉助的是ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接收兩個參數:第一個是Context,第二個參數是具體的權限名。然後我們使用返回值和PackageManager.PERMISSION_GRANTED做比較。相等就說明用戶已經授權
我們將打電話的邏輯封裝到了call()方法當中。如果沒有授權的話,則需調用ActivityCompat.request.Permissions()方法向用戶申請授權,requestPermissions()方法接收3個參數:第一個要求是Activity的實例,第二個參數是一個String數組,把申請的權限名放到數組裏即可。第三個參數是請求碼,只要是唯一值就可以,這裏傳入了1.
調用完requestPermissions()方法後,會彈出申請權限對話框。不論選擇哪種結果,都會回調到onRequestPermissionsResult()方法中,授權結果會封裝在grantResults參數中。

2.訪問其他程序中的數據

ContentResolver 中提供了一系列方法對數據進行增刪改查操作,但不同於SQLiteDatabase,ContentProvider中不接受表名,而是使用一個Uri參數代替。
內容URI最標準的格式如下:
content://com.example.app.provider/table1
content://com.example.app.provider/table2
它主要由兩部分組成:authority 和 path。authority用於對不同的應用程序做區分,會採用應用包名的方式進行命名。path則是對同一應用程序不同表做區分
1.將內容URI解析成Uri對象纔可以作爲參數傳入。
val uri = Uri.parse(“content://com.example.app.provider/table1”)
2.使用這個Uri對象查詢table1表中的數據

val cursor = contentResolver.query{
uri,
projection,
selection,
selectionArgs,
sortOrder
}
query()方法參數     |        對應SQL部分              |                 描述
uri				   |     from table_name             |     指定查詢某個應用程序下的某一張表
projection         |    selet column1 , colum2       |   指定查詢的列名
selection          |  where column =value            |   指定where的約束條件
selectionArgs      |                                 |    爲where中的佔位符提供具體的值
sortOrder          |   order by column1,column2      |  指定查詢結果的排序方式

查詢完成後返回仍然是一個Cursor對象

while(cursor.moveToNext()){
	val column1 = cursor.getString(Cursoe.getColumnIndex("colum1"))
	val column2 = cursor.getString(Cursoe.getColumnIndex("colum2"))
}
curosr.close()

向table1表添加一條數據

val values = contentValuesOf( "column1" to "text", "column2" to 1 )
contentResolver.insert(uri , values)

更新數據,將colum1的值清空

val values = contentValuesOf( "column1" to "" )
contentResolver.update(uri , values,"column2 = ?", arrayOf("1"))

調用ContentResolver的delete()方法將這條數據刪除掉

contentResolver.deleter(uri , "colummn2 = ?", arrayOf("1"))

2.2實戰讀取系統聯繫人
修改activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <ListView
        android:id="@+id/contactsView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        ></ListView>

</androidx.appcompat.widget.LinearLayoutCompat>

修改MainActivity

class MainActivity : AppCompatActivity() {

    private val contactsList = ArrayList<String>()
    private lateinit var adapter: ArrayAdapter<String>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        adapter= ArrayAdapter(this,android.R.layout.simple_list_item_1,contactsList)
        contactsView.adapter=adapter
        //動態獲取權限
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!=PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, arrayOf( Manifest.permission.READ_CONTACTS),1)
        }else{
            readContacts()
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when(requestCode){
            1 -> {
                if (grantResults.isNotEmpty()&&grantResults[0] == PackageManager.PERMISSION_GRANTED){
                readContacts()
                }else{
                    Toast.makeText(this,"沒有權限的話,就不能這樣做啦!",Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    //讀取聯繫人操作
    private fun readContacts(){
    //查詢聯繫人數據
        contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null)?.apply {
            while (moveToNext()){
                //獲取聯繫人姓名
                val displayName = getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                //獲取聯繫人手機號
                val number = getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))

                contactsList.add("$displayName\n電話號碼:$number")
            }
            adapter.notifyDataSetChanged()
            close()
        }
    }
}

3.創建自己的ContentProvider

3.1創建 ContentProvider的步驟
ContentProvider類種有6個抽象方法
1)onCreate()初始化ContentProvider時調用。通常會在這裏完成對數據庫的創建和升級等操作,返回true表示ContentProvider初始化成功,返回false則表示失敗
2)query()。從ContentProvider中查詢數據。uri確定查詢哪張表,projection確定查詢那些列,selection和selectionArgs約束那些行,sortOrder對結果排序,查詢結果存在Cursor對象中返回。
3)insert()。uri確定查詢哪張表,待添加的數據保存在values參數中。完成添加後,返回一個表示這條新記錄的URI
4)update()。uri確定查詢哪張表,待添加的數據保存在values參數中,selection和selectionArgs約束那些行,受影響的行數作爲返回值返回
5)delete()。uri確定查詢哪張表,selection和selectionArgs約束那些行,被刪除的行數作爲返回值返回
6)getType()。根據傳入的內容URI返回相應的MIME類型

標準URI格式寫法content://com.example.app.provider/table1
還可以在URI後面加idcontent://com.example.app.provider/table1/1
一個能匹配任意表的內容URI可以寫成content://com.example.app.provider/*
一個能匹配table1表中任意一行數據內容URI格式content://com.example.app.provider/#
getType()方法中,用於獲取Uri對象所對應的MIMe類型。一個內容URI對應的MIME字符串主要有3部分組成,android對這3部分做了如下格式規定
1).必須以vnd開頭。
2).如果內容URI以路徑結尾,則後接android.cursor.dir/;如果內容URI以id結尾,則後接android.cursor.item/
3).最後接上vnd.<authority>.<path>
3.2實現跨程序數據共享
首先在DatabaseTest項目的基礎上繼續開發。右擊 com.example.databasetest包->New->Other->Content Provider.
將 ContentProvider 命名 DatabaseProvider , 將authority 指定爲com.example.databasetest.provider。


class DatabasesProvider : ContentProvider() {

    private val bookDir = 0
    private val bookItem = 1
    private val categoryDir = 2
    private val categoryItem =3
    private val authority = "com.example.databasetest.provider"
    private var dbHelper : MyDatabaseHelper? = null

    private val uriMatcher by lazy{
        val matcher = UriMatcher(UriMatcher.NO_MATCH)
        matcher.addURI(authority,"book",bookDir)
        matcher.addURI(authority,"book/#",bookItem)
        matcher.addURI(authority,"category",categoryDir)
        matcher.addURI(authority,"category/#",categoryItem)
        matcher
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = dbHelper?.let{
       //刪除數據
        val db = it.writableDatabase
        val deletedRows = when (uriMatcher.match(uri)){
            bookDir -> db.delete("Book",selection,selectionArgs)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.delete("Book","id = ?", arrayOf(bookId))
            }
            categoryDir -> db.delete("Category",selection,selectionArgs)
            categoryItem ->{
                val categoryId = uri.pathSegments[1]
                db.delete("Category","id = ?", arrayOf(categoryId))
            }
            else -> 0
        }
        deletedRows
    } ?:0

    override fun getType(uri: Uri) = when (uriMatcher.match(uri)) {
       bookDir -> "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book"
        bookItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book"
        categoryDir ->"vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category"
        categoryItem -> "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category"
        else -> null
    }

    override fun insert(uri: Uri, values: ContentValues?) = dbHelper?.let{
        //添加數據
        val db = it.writableDatabase
        val uriReturn = when(uriMatcher.match(uri)){
            bookDir,bookItem -> {
                val newBookId = db.insert("Book",null,values)
                Uri.parse("content://$authority/book/$newBookId")
            }
            categoryDir, categoryItem -> {
                val newCategoryId = db.insert("Category",null,values)
                Uri.parse("content://$authority/category/$newCategoryId")
            }
            else -> null
        }
        uriReturn
    }

    override fun onCreate() = context?.let{
        dbHelper =MyDatabaseHelper(it,"BookStore.db",2)
        true
    } ?:false

    override fun query(
        uri: Uri, projection: Array<String>?, selection: String?,
        selectionArgs: Array<String>?, sortOrder: String?
    ) = dbHelper?.let {
        //查數據
        val db = it.readableDatabase
        val cursor =when(uriMatcher.match(uri)) {
            bookDir -> db.query("Book",projection,selection,selectionArgs,null,null,sortOrder)
            bookItem -> {
                val bookId = uri.pathSegments[1]
                db.query("Book",projection,"id = ?", arrayOf(bookId),null,null,sortOrder)

            }
            categoryDir ->db.query("Caregory",projection,selection,selectionArgs,null,null,sortOrder)
            categoryItem ->{
                val categoryId = uri.pathSegments[1]
                db.query("Category",projection,"id = ?", arrayOf(categoryId),null,null,sortOrder)
            }
        else -> null
        }
        cursor
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String>?
    ) = dbHelper?.let {
        //更新數據
        val db = it.writableDatabase
        val updatedRows =when(uriMatcher.match(uri)){
            bookDir ->db.update("Book",values,selection,selectionArgs)
            bookItem ->{
                val  bookId = uri.pathSegments[1]
                db.update("Book",values,"id = ?", arrayOf(bookId))
            }
            categoryDir ->db.update("Category",values,selection,selectionArgs)
            categoryItem -> {
                val categoryId = uri.pathSegments[1]
                db.update("Category",values,"id = ?", arrayOf(categoryId))
            }
            else -> 0
        }
        updatedRows
    } ?:0
}

創建新項目ProviderTest

class MainActivity : AppCompatActivity() {

    var bookId : String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        addData.setOnClickListener {
            //添加數據
            val uri = Uri.parse("content://com.example.databasetest.provider/book")
            val values =contentValuesOf("name" to "A Clash of Kings","author" to "George Martin","pages" to 1040 ,"price" to 33.85)
            val newUri =contentResolver.insert(uri,values)
            bookId = newUri?.pathSegments?.get(1)
        }
        queryData.setOnClickListener {
            //查詢數據
            val uri =Uri.parse("content://com.example.databasetest.provider/book")
            contentResolver.query(uri,null,null,null,null)?.apply {
                while(moveToNext()){
                    val name = getString(getColumnIndex("name"))
                    val author = getString(getColumnIndex("author"))
                    val pages = getInt(getColumnIndex("pages"))
                    Log.d("MainActivity","書名是 $name")
                    Log.d("MainActivity","作者是 $author")
                    Log.d("MainActivity","有 $pages 頁")
                }
                close()
            }
        }
        updateData.setOnClickListener {
        //更新數據
            bookId?.let {
                val uri = Uri.parse("content://com.example.databasetest.provider/book/$it")
                val values = contentValuesOf("name" to "A Storm of Swords","pages" to 1216)
                contentResolver.update(uri,values,null,null)
            }
        }

        deleteData.setOnClickListener {
            //刪除數據
            bookId?.let {
                val uri = Uri.parse("content://com.example.databasetest.provider/book/$it")
                contentResolver.delete(uri,null,null)
            }
        }
    }
}

4.Kotlin

4.1泛型基本用法
4.1.1定義一個泛型類

class MyClass<T> {
	fun method(param : T) : T{
		return param
}
}

調用方式

val myClass = MyClass<Int>()
val result = myClass.method(123)

4.1.2定義一個泛型方法

class MyClass {
	fun <T> method (param : T) : T{
	return param
	}
}

調用方式

val myClass = MyClass()
val result = myClass.method<Int>(123)

由於kotlin有出色的類推導機制,這裏可以簡化成

val myClass = MyClass()
val result = myClass.method(123)

4.1.3Kotlin還允許對泛型的類型進行限制,可以將method方法的泛型指定成任意類型。

class MyClass {
	fun <T : Number>  method (param : T) : T{
	return param
	}
}

在默認情況下,所有泛型可指定可空類型。泛型上界默認是Any?。如果想讓泛型的類型不可爲空,只需要將泛型的上界手動指定爲Any就可以了
4.1.4泛型實例
新建一個build.kt文件,並編寫如下代碼:

fun <T> T.build(block : T.() -> Unit) : T{
	block()
	return this
}

完成後就可以像使用apply函數一樣去使用build
4.2類委託和委託屬性
委託使用的關鍵字:by
4.2.1類委託

class MySet<T>(val helperSet : HashSet<T>) : Set<T> by helperSet{
}

藉助委託的功能之後,只需要單獨重寫那一方法就可以了。如:

class MySet<T> (val helperSet : HashSet<T>) : Set<T> by helperSet {
	fun helloWorld() = println("Hello World")
	override fun isEmpty() = false
}																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																																	```

4.2.2屬性委託
語法結構

class MyClass{
	var p by Delegate() 
}

這裏使用by連接左邊的p屬性和右邊Delegate實例
當調用p屬性的時候會自動調用Delegate類的getValue()方法,當給p屬性賦值的時候會自動調用Delegate類的setValue()的方法
同時我們還得對Delegate類進行具體操作

class Delegate {
	var propValue : Any? = null
	operator fun getValue(myClass : MyClass.prop : KProperty<*>) : Any?{
	return propValue
}
operator fun settValue(myClass : MyClass.prop : KProperty<*>,vlalue : Any?) {
	propValue = value
}
}


getValue()方法需要接收兩個參數:第一個用於聲明該Delegate類的委託可能在什麼類中使用,這裏寫成MyClass表示僅可在MyClass類中使用;第二個參數KProperty<>是Kotlin中的一個屬性操作類,可用於獲取各種屬性相關的值。<>這種泛型的寫法表示你不知道或者不關心的泛型的具體類型。類似於java中<?>的寫法。

setValue()方法啊相似。
4.2.3實現一個自己的lazy函數
工作原理解密

val p by lazy{ ...}

新建一個Later.kt文件

class Later<T>(val block : () -> T){
	var value:Any? = null
	operator fun getValue(any : Any?,prop : KProperty<*>) : T{
	if ( value == null) {
		value = block()
	}
	return value as T
	}
}

爲了是它的用法更加類似於lazy函數,最好再定義一個頂層函數

fun <T> later(block : () -> T) =Later(T)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章