[Android] ContentProvider ์ •๋ฆฌ

Minjun Kimยท2023๋…„ 9์›” 10์ผ
1

Android

๋ชฉ๋ก ๋ณด๊ธฐ
37/47
post-thumbnail

๐Ÿ“ SeSAC์˜ 'JetPack๊ณผ Kotlin์„ ํ™œ์šฉํ•œ Android App ๊ฐœ๋ฐœ' ๊ฐ•์ขŒ๋ฅผ ์ •๋ฆฌํ•œ ๊ธ€ ์ž…๋‹ˆ๋‹ค.


๐Ÿ“‡ ContentProvider ๊ฐœ์š”

๊ตฌ๊ธ€์˜ ๊ธฐ๋ณธ ์•ฑ ์—ฐ๋™์„ ์œ„ํ•ด์„œ๋Š” ContentProvider ๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

  • ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋Š” ์•ฑ๊ณผ ์•ฑ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ์—ฐ๋™์„ ๋ชฉ์ ์œผ๋กœ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

๋‚ด๊ฐ€ ๋งŒ๋“  ์•ฑ์—์„œ ์—ฐ๋ฝ์ฒ˜, ๊ฐค๋Ÿฌ๋ฆฌ ๋“ฑ์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”๋กœ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. ๋‹น์—ฐํ•˜๊ฒŒ๋„ ์™ธ๋ถ€ ์•ฑ์—์„œ ๋ฐ์ดํ„ฐ์— ์ง์ ‘ ์ ‘๊ทผํ•˜๋Š” ๊ฒƒ์€ ๊ธˆ์ง€๋˜์–ด ์žˆ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์•ฑ์— 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() ํ•จ์ˆ˜๋กœ ๋‚ด๋ถ€ ์•ฑ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๊ฒƒ์„ ํ—ˆ์šฉํ•˜๊ณ  ์‹ถ์ง€ ์•Š๋‹ค๋ฉด ๊ทธ๋ƒฅ ํ•จ์ˆ˜ ๋‚ด๋ถ€๋ฅผ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์œผ๋ฉด ๋œ๋‹ค. ์ „์ ์œผ๋กœ ๊ฐœ๋ฐœ์ž ๋งˆ์Œ์ด๋‹ค.

๐Ÿ“Œ Manifest ๋“ฑ๋ก

<provider
	android:name=".MyContentProvider"
    android:authorities="com.example.provider"
    android:enabled="true"
    android:exported="true></provider>
  • AndroidManifest.xml ํŒŒ์ผ์— ๋“ฑ๋ก

์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋Š” ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ ์ด๋‹ค. ๊ทธ๋ž˜์„œ ๋‹น์—ฐํžˆ ๋งค๋‹ˆํŽ˜์ŠคํŠธ ํŒŒ์ผ์— ๋“ฑ๋กํ•ด์•ผ ํ•œ๋‹ค.
<provider> ํƒœ๊ทธ๋กœ ๋“ฑ๋กํ•œ๋‹ค.

โ— ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋Š” <name> ์†์„ฑ๋งŒ ํ•„์ˆ˜์ง€๋งŒ, ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋Š” <authorities> ์†์„ฑ๋„ ํ•„์ˆ˜์ด๋‹ค. ์ด ์†์„ฑ๊ฐ’์œผ๋กœ ์‹๋ณ„๋œ๋‹ค.

๐Ÿ“‘ ContentProvider ์™ธ๋ถ€ ์•ฑ

  • ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ธํ…ํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๋Š”๋‹ค.

  • ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋Š” ํ•„์š”ํ•œ ์ˆœ๊ฐ„ ์‹œ์Šคํ…œ์—์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ.

  • ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ด์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ์•ฑ์€ query(), insert(), delete(), update() ํ•จ์ˆ˜๋งŒ ํ˜ธ์ถœ.

4๊ฐœ์˜ ์ปดํฌ๋„ŒํŠธ ์ค‘์—์„œ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋งŒ์€ ์ธํ…ํŠธ ๋งค์ปค๋‹ˆ์ฆ˜๊ณผ ์ „ํ˜€ ๊ด€๋ จ์ด ์—†๋‹ค. ์™œ๋ƒํ•˜๋ฉด ์‹œ์Šคํ…œ์—์„œ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”์™€ ์ธํ…ํŠธ๋ฅผ ๋ณ„๊ฐœ๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

A์•ฑ์—์„œ B์•ฑ์˜ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, B์•ฑ์˜ ์‹œ์Šคํ…œ์—์„œ ์ž๋™์œผ๋กœ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  A์•ฑ์—์„œ delete(), insert(), query(), update() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ“Œ Query Visibility

ํ•˜์ง€๋งŒ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ด์šฉํ•˜๋Š” ์ชฝ์˜ ์•ฑ์˜ ๋ฉ”์ธ ํ™˜๊ฒฝํŒŒ์ผ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค.

<queries>
	<!--	๋‘˜ ์ค‘ ํ•˜๋‚˜๋งŒ ์„ ์–ธ๋˜์–ด ์žˆ์œผ๋ฉด ๋œ๋‹ค.-->
    <!--	<provider android:authorities="com.example.test.provider"/>-->
    <package android:name="com.example.test.outter"/>
</queries>
  • ์™ธ๋ถ€ ์•ฑ์˜ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ด์šฉํ•˜๊ณ ์ž ํ•œ๋‹ค๋ฉด ํ•ด๋‹น ์•ฑ์„ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•œ Query Visibility ๊ด€๋ จ ์„ค์ •

  • ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์•ฑ์˜ ํŒจํ‚ค์ง€๋ช…์„ <package> ํƒœ๊ทธ๋กœ ํ˜น์€ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”์˜ authorities ๋ฌธ์ž์—ด์„ <provider> ํƒœ๊ทธ๋กœ ์„ ์–ธ

๐Ÿ“Œ ContentResolver

contentResolver.query(
	Uri.parse("content://com.example.test.provider"),
    null, null, null, null)
  • ์‹œ์Šคํ…œ์˜ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ์ฒด๊ฐ€ ContentResolver ๊ฐ์ฒด

  • ContentResolver ๊ฐ์ฒด๋Š” contentResolver ํ”„๋กœํผํ‹ฐ๋กœ ํš๋“ํ•˜์—ฌ query(), insert(), update(), delete() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ

์ฒซ ๋ฒˆ์งธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ Uri ๊ฐ’, ์‹๋ณ„์ž๋ฅผ ์ค˜์„œ ์–ด๋Š ์•ฑ์˜ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ํ˜ธ์ถœ ํ• ์ง€ ์ง€์ •ํ•œ๋‹ค.

โ— ์ด URL ์— ์กฐ๊ฑด์ด ์žˆ๋‹ค.

  • ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•œ Uri ๊ฐ์ฒด

  • ํ”„๋กœํ† ์ฝœ์€ content ๊ฐ€ ๋˜์–ด์•ผ ํ•˜๊ณ , ๋„๋ฉ”์ธ ๋ถ€๋ถ„์€ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”์˜ ์‹๋ณ„์ž ๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.


๐Ÿ“š Contacts App

์ฃผ์†Œ๋ก์„ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํผ๋ฏธ์…˜ ์„ ์–ธ์ด ํ•„์š”ํ•˜๋‹ค.

<uses-permission android:name="android.permission.READ_CONTACTS"/>
  • ์ฃผ์†Œ๋ก ์•ฑ์„ ์—ฐ๋™ํ•˜์—ฌ ์ฃผ์†Œ๋ก ๋ชฉ๋ก ํ™”๋ฉด์„ ๋„์šฐ๊ธฐ

  • ์œ ์ €๊ฐ€ ์„ ํƒํ•œ ์‚ฌ๋žŒ์˜ ์ „ํ™”๋ฒˆํ˜ธ ํ˜น์€ ์ด๋ฉ”์ผ ์ •๋ณด๋ฅผ ํš๋“

์ฃผ์†Œ๋ก ๋ชฉ๋ก ํ™”๋ฉด์€ ์ฃผ์†Œ๋ก ์•ฑ์˜ ์•กํ‹ฐ๋น„ํ‹ฐ ๊ฐ€ ๋œ๋‹ค. ๊ทธ๋ž˜์„œ ์ฃผ์†Œ๋ก ํ™”๋ฉด์„ ๋„์šฐ๋Š” ์ž‘์—…์€ ์ธํ…ํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์•ผ ํ•œ๋‹ค.

์„ ํƒํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํš๋“ํ•˜๋Š” ์ž‘์—…์€ ์ฃผ์†Œ๋ก ์•ฑ์˜ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋” ๊ฐ€ ๋œ๋‹ค.

๐Ÿ“Œ Intent

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 : ์ด๋ฉ”์ผ ์ •๋ณด๊ฐ€ ์žˆ๋Š” ์‚ฌ๋žŒ๋งŒ ์ถœ๋ ฅ
  • ์ฃผ์†Œ๋ก ์•ฑ์„ ์—ฐ๋™ํ•˜๊ธฐ ์œ„ํ•œ URL

๐Ÿ“Œ ContentProvider

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 ํ˜•ํƒœ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์‹๋ณ„์ž ๋ฅผ ์ด์šฉํ•ด์„œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ์ฃผ์†Œ๋ก์˜ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”์— ์š”์ฒญํ•œ๋‹ค.

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

  • Manifest.xml
<uses-permission android:name="android.permission.READ_CONTACTS" />

  • activity_main.xml
<?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>


  • MainActivity.kt
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")
			}
		}
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ


๐Ÿ“š Gallery App

๊ฐค๋Ÿฌ๋ฆฌ๋„ ์ฃผ์†Œ๋ก๊ณผ ๋น„์Šทํ•˜๋‹ค.

์ธํ…ํŠธ๋กœ ๊ฐค๋Ÿฌ๋ฆฌ ์•ฑ์˜ ์‚ฌ์ง„ ๋ชฉ๋ก ์•กํ‹ฐ๋น„ํ‹ฐ๋ฅผ ๋„์šฐ๊ณ  -> ์œ ์ €๊ฐ€ ์„ ํƒํ•œ ์‚ฌ์ง„์˜ ์‹๋ณ„์ž '๋งŒ' ๊ฐ€์ ธ์˜ค๊ณ  -> ์‹๋ณ„์ž ๊ฐ’์„ ์ด์šฉํ•ด์„œ ์ปจํ…ํŠธ ํ”„๋กœ๋ฐ”์ด๋”๋ฅผ ์—ฐ๋™ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ํš๋“

val intent = Intent(Intent.ACTION_PICK, MedialStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.type = "image/*"
requestActivity.launch(intent)
  • ์ธํ…ํŠธ๋กœ ๊ฐค๋Ÿฌ๋ฆฌ ์•ฑ์˜ ์ด๋ฏธ์ง€ ๋ชฉ๋ก ํ™”๋ฉด์„ ์ถœ๋ ฅ

โ— OOM ๋ฌธ์ œ

์š”์ฆ˜ ์นด๋ฉ”๋ผ ์„ฑ๋Šฅ์ด ๋„ˆ๋ฌด ๋†’์•„์ ธ ์นด๋ฉ”๋ผ๋กœ ์ฐ์€ ์ด๋ฏธ์ง€ ์‚ฌ์ด์ฆˆ๊ฐ€ ๋„ˆ๋ฌด ํฌ๋‹ค.

์ด๋Ÿฌ๋ฉด OOM ๋ฌธ์ œ ๊ฐ€ ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ™”๋ฉด์— ์ถœ๋ ฅ๋˜๋Š” ์‚ฌ์ด์ฆˆ๊ฐ€ ์•„๋‹ˆ๋ผ ์•„์˜ˆ ๋ฐ์ดํ„ฐ ์‚ฌ์ด์ฆˆ๋ฅผ ์ค„์—ฌ์„œ ๋กœ๋”ฉํ•ด์•ผ ํ•œ๋‹ค.

val option = BitmapFactory.Options()
option.inSampleSize = 5		// 1/5 ๋กœ ์‚ฌ์ด์ฆˆ ๋‹ค์šด
  • OOM (OutOfMemoryException) ๋ฌธ์ œ ๊ฐ€ ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ

  • BitmapFactory.Option ๊ฐ์ฒด์˜ inSampleSize ๊ฐ’์„ ์ง€์ •ํ•ด ๋ฐ์ดํ„ฐ ์‚ฌ์ด์ฆˆ๋ฅผ ์ค„์—ฌ์„œ ๋กœ๋”ฉ

๐Ÿ“Œ ContentProvider

InputStream ์„ ์ด์šฉํ•ด์„œ ๊ฐค๋Ÿฌ๋ฆฌ ์•ฑ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํš๋“ํ•˜๊ณ , ํš๋“ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ BitmapFactory ์— ๋„˜๊ฒจ์„œ ์ด๋ฏธ์ง€ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ ๋‹ค.

var inputStream = contentResolver.openInputStream(it.data!!.data)
val bitmap = BitmapFactory.decodeStream(inputStream, null, option)
  • ๊ฐค๋Ÿฌ๋ฆฌ ์•ฑ์˜ ContentProvider ๊ฐ€ ์ œ๊ณตํ•˜๋Š” InputStream ๊ฐ์ฒด๋ฅผ ํš๋“

  • InputStream ๊ฐ์ฒด์— ์˜ํ•ด ๋„˜์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ ์ด์šฉ

openInputStream ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ์‹๋ณ„์ž๋ฅผ ํ”„๋กœ๋ฐ”์ด๋”์—๊ฒŒ ์ „๋‹ฌํ•˜๋ฉด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” InputStream ์š”์ฒญ

BitmapFactory ์—๊ฒŒ InputStream ์„ ๋„˜๊ฒจ InputStream ์œผ๋กœ ๋„˜์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋กœ ์ด๋ฏธ์ง€ ๊ฐ์ฒด ์ƒ์„ฑ

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

  • activity_main.xml
<?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>

  • MainActivity.kt
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)
		}
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ


๐Ÿ“š Call App

์ „ํ™”๋ฅผ ๊ฑธ๊ฑฐ๋‚˜ ๋ฐ›๋Š” ์•ฑ์„ ์ง€์นญํ•œ๋‹ค. ๋„ˆ๋ฌด ์ž˜ ๊ตฌํ˜„๋œ ์•ฑ์ด๋ผ ์ „ํ™” ๊ธฐ๋Šฅ์€ ๊ทธ๋ƒฅ ์ด๊ฑฐ ์“ฐ๋ฉด ๋œ๋‹ค.

1. ํผ๋ฏธ์…˜ ํ•„์š”

2. ์ธํ…ํŠธ์˜ ์•ก์…˜ ๋ฌธ์ž์—ด์„ Intent.ACTION_CALL ๋กœ ์ง€์ •

3. data ์ •๋ณด์˜ URL ์€ tel: ์œผ๋กœ ์„ ์–ธ (Call App ์˜ ์•กํ‹ฐ๋น„ํ‹ฐ๊ฐ€ tel: ํ”„๋กœํ† ์ฝœ ๋ช…์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ ๋•Œ๋ฌธ)

4. data ์ •๋ณด๋กœ ์ „ํ™” ๋ฒˆํ˜ธ ๋ช…์‹œ

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

  • Manifest.xml
<uses-permission android:name="android.permission.CALL_PHONE" />

  • activity_main.xml
<?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>

  • MainActivity.kt
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")
			}
		}
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ


๐Ÿ“š Camera App

์นด๋ฉ”๋ผ ์•ฑ์„ ์—ฐ๋™ํ•˜์—ฌ ์‚ฌ์ง„์„ ์ดฌ์˜ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ๋˜๋Œ๋ ค ๋ฐ›๋Š” ๋ฐฉ๋ฒ•์€ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค.

  • ์‚ฌ์ง„ ๋ฐ์ดํ„ฐ ํš๋“ ๋ฐฉ๋ฒ•

  • ํŒŒ์ผ ๊ณต์œ  ๋ฐฉ๋ฒ•

// ์‚ฌ์ง„ ๋ฐ์ดํ„ฐ ํš๋“ ๋ฐฉ๋ฒ• 

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 ํŒŒ์ผ ์ƒ์„ฑ

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 ๋Š” ๊ทธ๋ƒฅ ํŒจํ‚ค์ง€ ๋ช…์œผ๋กœ ์ž‘์„ฑํ–ˆ๋‹ค.

๐Ÿ“ Manifest ๋“ฑ๋ก

๊ทธ๋ฆฌ๊ณ  ๊ทธ 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 ํŒŒ์ผ ์ •๋ณด๋งŒ ์ง€์ • ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

๐Ÿ’ฌ XML ํŒŒ์ผ ์ž‘์„ฑ

๐Ÿ“Œ ํŒŒ์ผ ์ƒ์„ฑ

val file = File.createTempFile(
	"JPEG_${timeStamp}_",
    ".jpg",
    storageDir
)
filePath = file.absolutePath
  • ์นด๋ฉ”๋ผ ์•ฑ์„ ์—ฐ๋™ํ•˜๊ธฐ ์œ„ํ•ด์„œ ํŒŒ์ผ ์ƒ์„ฑ

๐Ÿ“Œ Uri ๊ฐ์ฒด ์ƒ์„ฑ

์ƒ์„ฑ๋œ ํŒŒ์ผ์˜ ์‹๋ณ„์ž๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

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)
  • ํŒŒ์ผ์„ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ Uri ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ  ์ด ์ •๋ณด๋ฅผ ์ธํ…ํŠธ์˜ ์—‘์ŠคํŠธ๋ผ ๋ฐ์ดํ„ฐ๋กœ ์„ค์ •

๐Ÿ“Œ ํŒŒ์ผ ์ƒ์„ฑ

ํŒŒ์ผ ๊ฒฝ๋กœ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฝ‘์•„ ์ด๋ฏธ์ง€ ๊ฐ์ฒด ํš๋“ํ•œ๋‹ค.

val bitmap = BitmapFactory.decodeFile(filePath, option)
  • ํŒŒ์ผ ๊ฒฝ๋กœ๋ฅผ decodeFile() ํ•จ์ˆ˜์— ์ง€์ •ํ•˜์—ฌ Bitmap ๊ฐ์ฒด๋ฅผ ํš๋“

๐Ÿงฉ ์‹ค์Šต ์˜ˆ์ œ

DATA ๋ฒ„ํŠผ์€ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ํš๋“ํ•˜๊ณ , FILE ๋ฒ„ํŠผ์€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ํš๋“ํ•˜๊ฒŒ๋” ๊ตฌํ˜„ ํ•ด๋ณด์ž!

  • activity_main.xml
<?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>

  • file_path.xml

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>

  • Manifest.xml

<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>

  • MainActivity.kt
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)
		}
	}
}

๐Ÿ“ฒ ๊ฒฐ๊ณผ

profile
์‘์•  ๋‚˜ ์•„๊ธฐ ๋‰ด๋น„

0๊ฐœ์˜ ๋Œ“๊ธ€