๐ SeSAC์ 'JetPack๊ณผ Kotlin์ ํ์ฉํ Android App ๊ฐ๋ฐ' ๊ฐ์ข๋ฅผ ์ ๋ฆฌํ ๊ธ ์ ๋๋ค.
๊ตฌ๊ธ์ ๊ธฐ๋ณธ ์ฑ ์ฐ๋์ ์ํด์๋ ContentProvider
๋ผ๋ ์ปดํฌ๋ํธ๊ฐ ํ์ํ๋ค.
๋ด๊ฐ ๋ง๋ ์ฑ์์ ์ฐ๋ฝ์ฒ, ๊ฐค๋ฌ๋ฆฌ ๋ฑ์ ์๋ ๋ฐ์ดํฐ๊ฐ ํ์๋ก ํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. ๋น์ฐํ๊ฒ๋ ์ธ๋ถ ์ฑ์์ ๋ฐ์ดํฐ์ ์ง์ ์ ๊ทผํ๋ ๊ฒ์ ๊ธ์ง๋์ด ์๋ค. ๊ทธ๋์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ฑ์ ContentProvider
๋ฅผ ๋ง๋ค๊ณ , ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ๋ ์ธ๋ถ ์ฑ์์ ContentProvider
์ ์ด์ฉํ๋ค.
class MyContentProvider : ContentProvider() {
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { }
override fun getType(uri: Uri): String? { }
override fun insert(uri: Uri, values: ContentValues?): Uri? { }
override fun onCreate(): Boolean { }
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? { }
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int { }
์ปจํ
ํธ ํ๋ก๋ฐ์ด๋๋ ContentProvider
๋ฅผ ์์ ๋ฐ์ ์์ฑ
API ํํ๋ง Sql ๋ฌธ๊ณผ ๋น์ทํ ๊ฒ ๋ฟ์ด๋ค. ํ์ผ์ read ํ๋ DB๋ฅผ ์ฐธ์กฐํ๋ ํ๊ณ ์ถ์ ๋๋ก ๊ตฌํํ๋ฉด ๋๋ค.
์ธ๋ถ ์ฑ์์ query()
ํจ์๋ฅผ ํธ์ถํ๋ฉด ๋ด๋ถ ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ค. ์ธ๋ถ ์ฑ์์ delete()
ํจ์๋ก ๋ด๋ถ ์ฑ์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ ์๋ ์๋ค. ์ด๊ฒ์ ํ์ฉํ๊ณ ์ถ์ง ์๋ค๋ฉด ๊ทธ๋ฅ ํจ์ ๋ด๋ถ๋ฅผ ๊ตฌํํ์ง ์์ผ๋ฉด ๋๋ค. ์ ์ ์ผ๋ก ๊ฐ๋ฐ์ ๋ง์์ด๋ค.
<provider
android:name=".MyContentProvider"
android:authorities="com.example.provider"
android:enabled="true"
android:exported="true></provider>
์ปจํ
ํธ ํ๋ก๋ฐ์ด๋๋ ํ๋์ ์ปดํฌ๋ํธ
์ด๋ค. ๊ทธ๋์ ๋น์ฐํ ๋งค๋ํ์คํธ ํ์ผ์ ๋ฑ๋กํด์ผ ํ๋ค.
<provider>
ํ๊ทธ๋ก ๋ฑ๋กํ๋ค.
โ ๋ค๋ฅธ ์ปดํฌ๋ํธ๋ <name>
์์ฑ๋ง ํ์์ง๋ง, ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋๋ <authorities>
์์ฑ๋ ํ์์ด๋ค. ์ด ์์ฑ๊ฐ์ผ๋ก ์๋ณ๋๋ค.
์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ด์ฉํ๊ธฐ ์ํด์ ์ธํ ํธ๋ฅผ ๋ฐ์์ํค์ง ์๋๋ค.
์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ ํ์ํ ์๊ฐ ์์คํ ์์ ์๋์ผ๋ก ์์ฑ.
์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ด์ฉํ๊ณ ์ ํ๋ ์ฑ์ query(), insert(), delete(), update() ํจ์๋ง ํธ์ถ.
4๊ฐ์ ์ปดํฌ๋ํธ ์ค์์ ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ง์ ์ธํ ํธ ๋งค์ปค๋์ฆ๊ณผ ์ ํ ๊ด๋ จ์ด ์๋ค. ์๋ํ๋ฉด ์์คํ ์์ ์ปจํ ํธ ํ๋ก๋ฐ์ด๋์ ์ธํ ํธ๋ฅผ ๋ณ๊ฐ๋ก ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ด๋ค.
A์ฑ์์ B์ฑ์ ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ํธ์ถํ๋ฉด, B์ฑ์ ์์คํ ์์ ์๋์ผ๋ก ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์์ฑํ๋ค. ๊ทธ๋ฆฌ๊ณ A์ฑ์์ delete(), insert(), query(), update() ํจ์๋ฅผ ํธ์ถํ๋ฉด ๋๋ค.
ํ์ง๋ง ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ด์ฉํ๊ธฐ ์ํด์๋ ์ด์ฉํ๋ ์ชฝ์ ์ฑ์ ๋ฉ์ธ ํ๊ฒฝํ์ผ ์ค์ ์ด ํ์ํ๋ค.
<queries>
<!-- ๋ ์ค ํ๋๋ง ์ ์ธ๋์ด ์์ผ๋ฉด ๋๋ค.-->
<!-- <provider android:authorities="com.example.test.provider"/>-->
<package android:name="com.example.test.outter"/>
</queries>
์ธ๋ถ ์ฑ์ ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ด์ฉํ๊ณ ์ ํ๋ค๋ฉด ํด๋น ์ฑ์ ์ด์ฉํ๊ธฐ ์ํ Query Visibility ๊ด๋ จ ์ค์
์ปจํ
ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ๊ฐ์ง๊ณ ์๋ ์ฑ์ ํจํค์ง๋ช
์ <package>
ํ๊ทธ๋ก ํน์ ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋์ authorities ๋ฌธ์์ด์ <provider>
ํ๊ทธ๋ก ์ ์ธ
contentResolver.query(
Uri.parse("content://com.example.test.provider"),
null, null, null, null)
์์คํ
์ ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ด์ฉํ๊ธฐ ์ํ ๊ฐ์ฒด๊ฐ ContentResolver ๊ฐ์ฒด
ContentResolver ๊ฐ์ฒด๋ contentResolver ํ๋กํผํฐ๋ก ํ๋ํ์ฌ query(), insert(), update(), delete() ํจ์๋ฅผ ํธ์ถ
์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก Uri ๊ฐ, ์๋ณ์๋ฅผ ์ค์ ์ด๋ ์ฑ์ ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ํธ์ถ ํ ์ง ์ง์ ํ๋ค.
์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์๋ณํ๊ธฐ ์ํ Uri ๊ฐ์ฒด
ํ๋กํ ์ฝ์ content
๊ฐ ๋์ด์ผ ํ๊ณ , ๋๋ฉ์ธ ๋ถ๋ถ์ ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋์ ์๋ณ์
๊ฐ ๋์ด์ผ ํ๋ค.
์ฃผ์๋ก์ ์ด์ฉํ๊ธฐ ์ํด์๋ ํผ๋ฏธ์ ์ ์ธ์ด ํ์ํ๋ค.
<uses-permission android:name="android.permission.READ_CONTACTS"/>
์ฃผ์๋ก ์ฑ์ ์ฐ๋ํ์ฌ ์ฃผ์๋ก ๋ชฉ๋ก ํ๋ฉด์ ๋์ฐ๊ธฐ
์ ์ ๊ฐ ์ ํํ ์ฌ๋์ ์ ํ๋ฒํธ ํน์ ์ด๋ฉ์ผ ์ ๋ณด๋ฅผ ํ๋
์ฃผ์๋ก ๋ชฉ๋ก ํ๋ฉด์ ์ฃผ์๋ก ์ฑ์ ์กํฐ๋นํฐ
๊ฐ ๋๋ค. ๊ทธ๋์ ์ฃผ์๋ก ํ๋ฉด์ ๋์ฐ๋ ์์
์ ์ธํ
ํธ๋ฅผ ๋ฐ์์์ผ์ผ ํ๋ค.
์ ํํ ๋ฐ์ดํฐ๋ฅผ ํ๋ํ๋ ์์
์ ์ฃผ์๋ก ์ฑ์ ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋
๊ฐ ๋๋ค.
val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
requestActivity.launch(intent)
์ฃผ์๋ก์ ๋ชฉ๋ก ํ๋ฉด์ ๋์ฐ๊ธฐ
์ธํ ํธ๋ฅผ ๋ฐ์์์ผ์ผ ํ๋ค.
์ด ๋ ๋์ ธ์ฃผ๋ URI
์ ๋ฐ๋ผ ์ถ๋ ฅ๋๋ ๋ฐ์ดํฐ๊ฐ ๋ฌ๋ผ์ง๋ค.
ContactsContract.Contacts.CONTENT_URI : ๋ชจ๋ ์ฌ๋ ์ถ๋ ฅ
ContactsContract.CommonDataKinds.Phone.CONTENT_URI : ์ ํ๋ฒํธ๊ฐ ์๋ ์ฌ๋๋ง ์ถ๋ ฅ
ContactsContract.CommonDataKinds.Email.CONTENT_URI : ์ด๋ฉ์ผ ์ ๋ณด๊ฐ ์๋ ์ฌ๋๋ง ์ถ๋ ฅ
val cursor = contentResolver.query(
it.data!!.data!!, // ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ก ์๋ณ์๋ฅผ ์ ๋ฌ
arrayOf<String>(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME, // ์ด๋ฆ
ContactsContract.CommonKinds.Phone.NUMBER // ์ ํ๋ฒํธ
),
null,
null,
null,
)
์ฃผ์๋ก์์ ์ ๋ฌํ ๊ฒฐ๊ณผ๋ URL ๋ฌธ์์ด ํํ์ด๋ฉฐ URL ์ ๋งจ ๋ง์ง๋ง ๋จ์ด(์์์๋ 1144) ๊ฐ ์ ์ ๊ฐ ์ ํํ ์ฌ๋์ ์๋ณ์ ๊ฐ
์๋ณ์๋ฅผ ์กฐ๊ฑด์ผ๋ก ์ฃผ์๋ก ์ฑ์ ์ปจํ ์ธ ํ๋ก๋ฐ์ด๋ ์ด์ฉ
์ ํํ ์ ์ ๋ฐ์ดํฐ๋ URL ํํ๋ก ๋์ด์จ๋ค. ์๋ํ๋ฉด ์ ์ ๊ฐ ๊ฐ์ง๋ ๋ฐ์ดํฐ๊ฐ ๋๋ฌด ๋ง๊ธฐ ๋๋ฌธ์ด๋ค. ๋จ์ํ ์ ํ๋ฒํธ๋ง ์๋ ๊ฒ์ด ์๋๋ผ ์ด๋ฉ์ผ, ์ฃผ์, ์ง ์ ํ ๋ฑ์ ์ ๋ณด๋ ์๊ธฐ ๋๋ฌธ์ด๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ ์ฑ์ผ๋ก ๋์์ค๋ ๊ฒ์ ์ ํํ ์ ์ ์ ์๋ณ์
๋ง ๋์ด์ค๊ณ , ์ด๊ฒ์ด URL ํํ์ด๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ์๋ณ์
๋ฅผ ์ด์ฉํด์ ๋ด๊ฐ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ค์ ์ฃผ์๋ก์ ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋์ ์์ฒญํ๋ค.
<uses-permission android:name="android.permission.READ_CONTACTS" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/resultView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30dp"
android:textStyle="bold" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Contacts App" />
</LinearLayout>
package com.kotdev99.android.c63
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
val resultView = findViewById<TextView>(R.id.resultView)
val requestActivity: ActivityResultLauncher<Intent> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
// ์ฃผ์๋ก์์ ๋๋์ ์ค๋ฉด ์คํ ๋๋ ๋ถ๋ถ
val cursor = contentResolver.query(
it.data!!.data!!, // ContentProvider ๋ฐ ์ ์ ์ ์๋ณ์
arrayOf(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
),
null,
null,
null
)
var name = "none"
var phone = "none"
if (cursor!!.moveToFirst()) {
name = cursor?.getString(0).toString()
phone = cursor?.getString(1).toString()
}
resultView.text = "name - $name, phone - $phone"
}
val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
// ํผ๋ฏธ์
๋ค์ด์ผ๋ก๊ทธ ์ข
๋ฃ ํ ์คํ ๋๋ ๋ถ๋ถ
if (isGranted) {
val intent =
Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
requestActivity.launch(intent)
}
}
button.setOnClickListener {
val status =
ContextCompat.checkSelfPermission(this, "android.permission.READ_CONTACTS")
if (status == PackageManager.PERMISSION_GRANTED) {
// ํผ๋ฏธ์
ํ์ฉ ์ํ ์ ๋ฐ๋ก ์์์ ์ธํ
ํธ ๋ฐ์์์ผ ์๋ณ์ ํ๋
val intent =
Intent(
Intent.ACTION_PICK,
ContactsContract.CommonDataKinds.Phone.CONTENT_URI
)
requestActivity.launch(intent)
} else {
// ํผ๋ฏธ์
๊ฑฐ๋ถ ์ํ ์ ํผ๋ฏธ์
์์ฒญ
permissionLauncher.launch("android.permission.READ_CONTACTS")
}
}
}
}
๊ฐค๋ฌ๋ฆฌ๋ ์ฃผ์๋ก๊ณผ ๋น์ทํ๋ค.
์ธํ ํธ๋ก ๊ฐค๋ฌ๋ฆฌ ์ฑ์ ์ฌ์ง ๋ชฉ๋ก ์กํฐ๋นํฐ๋ฅผ ๋์ฐ๊ณ -> ์ ์ ๊ฐ ์ ํํ ์ฌ์ง์ ์๋ณ์ '๋ง' ๊ฐ์ ธ์ค๊ณ -> ์๋ณ์ ๊ฐ์ ์ด์ฉํด์ ์ปจํ ํธ ํ๋ก๋ฐ์ด๋๋ฅผ ์ฐ๋ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ํ๋
val intent = Intent(Intent.ACTION_PICK, MedialStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.type = "image/*"
requestActivity.launch(intent)
์์ฆ ์นด๋ฉ๋ผ ์ฑ๋ฅ์ด ๋๋ฌด ๋์์ ธ ์นด๋ฉ๋ผ๋ก ์ฐ์ ์ด๋ฏธ์ง ์ฌ์ด์ฆ๊ฐ ๋๋ฌด ํฌ๋ค.
์ด๋ฌ๋ฉด OOM ๋ฌธ์
๊ฐ ๋ฐ์ ํ ์ ์๋ค. ํ๋ฉด์ ์ถ๋ ฅ๋๋ ์ฌ์ด์ฆ๊ฐ ์๋๋ผ ์์ ๋ฐ์ดํฐ ์ฌ์ด์ฆ๋ฅผ ์ค์ฌ์ ๋ก๋ฉํด์ผ ํ๋ค.
val option = BitmapFactory.Options()
option.inSampleSize = 5 // 1/5 ๋ก ์ฌ์ด์ฆ ๋ค์ด
OOM (OutOfMemoryException) ๋ฌธ์
๊ฐ ๋ฐ์ ํ ์ ์๋ ์ํฉ
BitmapFactory.Option ๊ฐ์ฒด์ inSampleSize ๊ฐ์ ์ง์ ํด ๋ฐ์ดํฐ ์ฌ์ด์ฆ๋ฅผ ์ค์ฌ์ ๋ก๋ฉ
InputStream ์ ์ด์ฉํด์ ๊ฐค๋ฌ๋ฆฌ ์ฑ์์ ๋ฐ์ดํฐ๋ฅผ ํ๋ํ๊ณ , ํ๋ํ ๋ฐ์ดํฐ๋ฅผ BitmapFactory ์ ๋๊ฒจ์ ์ด๋ฏธ์ง ๊ฐ์ฒด๋ฅผ ๋ง๋ ๋ค.
var inputStream = contentResolver.openInputStream(it.data!!.data)
val bitmap = BitmapFactory.decodeStream(inputStream, null, option)
๊ฐค๋ฌ๋ฆฌ ์ฑ์ ContentProvider ๊ฐ ์ ๊ณตํ๋ InputStream ๊ฐ์ฒด๋ฅผ ํ๋
InputStream ๊ฐ์ฒด์ ์ํด ๋์ด์ค๋ ๋ฐ์ดํฐ ์ด์ฉ
openInputStream ์ ๋งค๊ฐ๋ณ์๋ก ์๋ณ์๋ฅผ ํ๋ก๋ฐ์ด๋์๊ฒ ์ ๋ฌํ๋ฉด์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๋ InputStream ์์ฒญ
BitmapFactory ์๊ฒ InputStream ์ ๋๊ฒจ InputStream ์ผ๋ก ๋์ด์ค๋ ๋ฐ์ดํฐ๋ก ์ด๋ฏธ์ง ๊ฐ์ฒด ์์ฑ
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</LinearLayout>
package com.kotdev99.android.c64
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageView = findViewById<ImageView>(R.id.imageView)
val button = findViewById<Button>(R.id.button)
val launcher: ActivityResultLauncher<Intent> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
// ๊ฐค๋ฌ๋ฆฌ ์ฑ์์ ๋๋์ ์์ ๋ ์คํ ๋๋ ์์ญ
try {
val option = BitmapFactory.Options()
option.inSampleSize = 5
// ์ปจํ
ํธ ํ๋ก๋ฐ์ด๋์๊ฒ ์๋ณ์์ ํด๋น ๋๋ ๋ฐ์ดํฐ InputStream ์์ฒญ
val inputStream = contentResolver.openInputStream(it.data!!.data!!)
val bitmap = BitmapFactory.decodeStream(inputStream, null, option)
inputStream!!.close()
bitmap?.let {
imageView.setImageBitmap(bitmap)
} ?: let {
}
} catch (e: Exception) {
e.printStackTrace()
}
}
button.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.type = "image/*"
launcher.launch(intent)
}
}
}
์ ํ๋ฅผ ๊ฑธ๊ฑฐ๋ ๋ฐ๋ ์ฑ์ ์ง์นญํ๋ค. ๋๋ฌด ์ ๊ตฌํ๋ ์ฑ์ด๋ผ ์ ํ ๊ธฐ๋ฅ์ ๊ทธ๋ฅ ์ด๊ฑฐ ์ฐ๋ฉด ๋๋ค.
1. ํผ๋ฏธ์
ํ์
2. ์ธํ
ํธ์ ์ก์
๋ฌธ์์ด์ Intent.ACTION_CALL ๋ก ์ง์
3. data ์ ๋ณด์ URL ์ tel: ์ผ๋ก ์ ์ธ (Call App ์ ์กํฐ๋นํฐ๊ฐ tel: ํ๋กํ ์ฝ ๋ช
์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ธฐ ๋๋ฌธ)
4. data ์ ๋ณด๋ก ์ ํ ๋ฒํธ ๋ช
์
<uses-permission android:name="android.permission.CALL_PHONE" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/editView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="phone" />
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Call" />
</LinearLayout>
package com.kotdev99.android.c65
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editView = findViewById<EditText>(R.id.editView)
val button = findViewById<Button>(R.id.button)
val permissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted ->
if (isGranted) {
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:${editView.text}"))
startActivity(intent)
} else {
Toast.makeText(this, "denied", Toast.LENGTH_SHORT).show()
}
}
button.setOnClickListener {
val status = ContextCompat.checkSelfPermission(this, "android.permission.CALL_PHONE")
if (status == PackageManager.PERMISSION_GRANTED) {
val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:${editView.text}"))
startActivity(intent)
} else {
permissionLauncher.launch("android.permission.CALL_PHONE")
}
}
}
}
์นด๋ฉ๋ผ ์ฑ์ ์ฐ๋ํ์ฌ ์ฌ์ง์ ์ดฌ์ํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋๋๋ ค ๋ฐ๋ ๋ฐฉ๋ฒ์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
์ฌ์ง ๋ฐ์ดํฐ ํ๋ ๋ฐฉ๋ฒ
ํ์ผ ๊ณต์ ๋ฐฉ๋ฒ
// ์ฌ์ง ๋ฐ์ดํฐ ํ๋ ๋ฐฉ๋ฒ
1. ์นด๋ฉ๋ผ ์ฑ์ ์ฌ์ง์ file ๋ก ์ ์ฅํ์ง ์๊ณ ๋ฐ์ดํฐ๋ง ๋๊ธด๋ค.
2. ๊ทธ๋์ ๊ฐค๋ฌ๋ฆฌ ๋ฑ์ ๋จ์ง ์๋๋ค.
3. ๊ทธ๋ฐ๋ฐ ์ฌ์ด์ฆ๊ฐ ์๊ฒ ๋์ด์จ๋ค. (OOM ๋ฌธ์ ๋๋ฌธ์ธ ๋ฏ?)
// ํ์ผ ๊ณต์ ๋ฐฉ๋ฒ
1. ์ธํ
ํธ๋ฅผ ๋ฐ์์ํฌ ๋ ์ฑ์ ํ์ผ์ ๋ณด๋ฅผ ๊ฐ์ด ๋๊ฒจ์ค๋ค.
2. ์นด๋ฉ๋ผ ์ฑ์ด ์ฌ์ง์ ์ดฌ์ํ๊ณ file ๋ก ์ ์ฅํ ํ ์ฑ๊ณต/์คํจ ์ฌ๋ถ๋ง ๋๊ฒจ์ค๋ค. (์ฌ์ง์ ํ ๋ฐ์ดํฐ๋ก ์ ์ฅ๋๋ค.)
3. ์ฐ๋ฆฌ ์ฑ์ ์ ์ฅ๋ ํ์ผ์ ์ฝ์ด ๋ค์ธ๋ค.
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
requestActivity.launch(intent)
val bitmap = it.data?.getExtras()?.get("data") as Bitmap
๋์ด์ค๋ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋ฅ Bitmap ๊ฐ์ฒด๋ก ์ด์ฉ ํด์ฃผ๋ฉด ๋๋ค.
// ํ์ผ ๊ณต์ ๋ฐฉ๋ฒ
1. ์ฑ์์ ์ฌ์ง์ ์ ์ฅํ ํ์ผ์ ๋ง๋ ๋ค.
2. ํ์ผ ์ ๋ณด๋ฅผ ํฌํจํด์ ์ธํ
ํธ๋ฅผ ๋ฐ์์์ผ ์นด๋ฉ๋ผ ์ฑ์ ์คํ ์ํจ๋ค.
3. ์นด๋ฉ๋ผ ์ฑ์์ ์ฌ์ง ์ดฌ์ ํ ์ดฌ์๋ ์ฌ์ง์ ๊ณต์ ๋ ํ์ผ์ ์ ์ฅ์ ํ๋ค.
4. ์นด๋ฉ๋ผ ์ฑ์ด ์ข
๋ฃ ๋๋ฉด์ ์ฑ๊ณต ์คํจ๋ฅผ ๋ฐํํ๋ค.
5. ์ฑ์์ ํ์ผ์ ์ฝ์ด ์นด๋ฉ๋ผ ์ฑ์ด ์ ์ฅํ ์ฌ์ง ๋ฐ์ดํฐ๋ฅผ ์ด์ฉํ๋ค.
๊ทธ๋ฐ๋ฐ ํ์ผ๋ก ์ด์ฉ์ ํ ๊ฒฝ์ฐ์๋ ์กฐ๊ธ ์์ ์ ํด์ผ ํ๋ค. ํ์ผ์ ๊ณต์ ํ๊ธฐ ๋๋ฌธ์ ๋ฐ์ํ๋ ์์ ๋ค ์ด๋ค.
XML ํ์ผ์ ๋ง๋ค์ด ์ค์ผ ํ๋ค.
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="myfiles" path="Android/data/com.kotdev99.android.c66/files/Pictures"/>
</paths>
FileProvider ๋ฅผ ์ด์ฉํ๋ ค๋ฉด ๊ณต์ ํ๊ณ ์ ํ๋ ํ์ผ์ Uri ๊ฐ์ ์ค๋น
path ๋ ๊ทธ๋ฅ ํจํค์ง ๋ช ์ผ๋ก ์์ฑํ๋ค.
๊ทธ๋ฆฌ๊ณ ๊ทธ XML ํ์ผ์ Manifest ์ ๋ฑ๋กํด ์ค์ผ ํ๋ค.
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.kotdev99.android.c66.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
AndroidManifest.xml ํ์ผ์ ๋ฑ๋ก
<provider>
ํ๊ทธ๋ก ๋ฑ๋กํ๋ค.
ํ๋ก๋ฐ์ด๋๋ฅผ ๋ง๋๋ ๊ฒ์ด ์๋๋ผ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์๋ ๊ฑธ ๊ฐ์ ธ๋ค ์ธ ๋ฟ์ด๋ค.
์ฐ๋ฆฌ๋ resource
์ XML ํ์ผ ์ ๋ณด๋ง ์ง์ ํด์ฃผ๋ฉด ๋๋ค.
val file = File.createTempFile(
"JPEG_${timeStamp}_",
".jpg",
storageDir
)
filePath = file.absolutePath
์์ฑ๋ ํ์ผ์ ์๋ณ์๋ผ๊ณ ๋ณด๋ฉด ๋๋ค.
val photoURI: Uri = FileProvider.getUriForFile(
this,
"com.kotdev99.android.c66.fileprovider", file
)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
fileRequestActivity.launch(intent)
ํ์ผ ๊ฒฝ๋ก์์ ๋ฐ์ดํฐ๋ฅผ ๋ฝ์ ์ด๋ฏธ์ง ๊ฐ์ฒด ํ๋ํ๋ค.
val bitmap = BitmapFactory.decodeFile(filePath, option)
DATA ๋ฒํผ์ ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ ํ๋ํ๊ณ , FILE ๋ฒํผ์ ์ด๋ฏธ์ง ํ์ผ์ ํ๋ํ๊ฒ๋ ๊ตฌํ ํด๋ณด์!
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<androidx.cardview.widget.CardView
android:layout_width="150dp"
android:layout_height="150dp"
app:cardCornerRadius="75dp"
app:cardElevation="0dp">
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop" />
</androidx.cardview.widget.CardView>
<Button
android:id="@+id/dataButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="data" />
<Button
android:id="@+id/fileButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="file" />
</LinearLayout>
res/xml ํ์์ File XML ์์ฑ
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="myfiles"
path="Android/data/com.kotdev99.android.c66/files/Pictures" />
</paths>
<provider>
ํ๊ทธ์ <meta-data>
ํ๊ทธ ๋ฑ๋ก
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidLab">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.kotdev99.android.c66.file-provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
package com.kotdev99.android.c66
class MainActivity : AppCompatActivity() {
@SuppressLint("SimpleDateFormat")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageView = findViewById<ImageView>(R.id.imageView)
val dataButton = findViewById<Button>(R.id.dataButton)
val fileButton = findViewById<Button>(R.id.fileButton)
val launcher: ActivityResultLauncher<Intent> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
val bitmap = it.data?.extras?.get("data") as Bitmap
bitmap?.let {
imageView.setImageBitmap(bitmap)
}
}
dataButton.setOnClickListener {
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
launcher.launch(intent)
}
var filePath = ""
val fileLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
val option = BitmapFactory.Options()
option.inSampleSize = 3
val bitmap = BitmapFactory.decodeFile(filePath, option)
bitmap?.let {
imageView.setImageBitmap(bitmap)
}
}
fileButton.setOnClickListener {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmm ss").format(Date())
val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File.createTempFile(
"JPEG_${timeStamp}_", // ํ์ผ๋ช
".jpg",
storageDir
)
filePath = file.absolutePath
val uri = FileProvider.getUriForFile(
this,
"com.kotdev99.android.c66.file-provider",
file
)
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
fileLauncher.launch(intent)
}
}
}