DBILITY

안드로이드 정리 2 본문

android/kotlin

안드로이드 정리 2

DBILITY 2024. 1. 19. 10:45
반응형

사진을 찍으면 내부 저장소에 저장되고 미디어데이터베이스에도 정보가 저장된다. Contents Provider(앱의 데이터 접근을 다른 앱에 허용하는 콤포넌트)를 사용해 다른 앱에 공개될 수 있다.

안드로이드 주요 콤포넌트는 다음과 같다고

  • Activity : 화면을 구성
  • Contents Provider : 데이터베이스,파일,네트워크의 데이터를 다른 앱에 공유
  • Broadcast Receiver : 앱이나 기기가 발송하는 방송(?)을 수신
  • Service : 화면이 없는 백그라운드 작업용

안드로이드 저장소

  • 내부 저장수 : OS설치영역으로 시스템영역,앱이 사용하는 정보와 데이터베이스가 저장
  • 외부 저장소 : 컴퓨터에 디바이스를 연결하면 저장소로 인식, 유저영역. 사진,동영상은 여기에 저장

저장소 접근을 위해서 AndroidManifest 에 권한을 부여해야 한다. 

    <uses-permission
        android:name="android.permission.READ_MEDIA_IMAGES"
        android:minSdkVersion="33" />
    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

접근권한도 버전에 따라 다르게 정의 된다.android.Manifest 패키지의 permission을 확인하자.

Manifest.permission.READ_MEDIA_IMAGES( 33이상), Manifest.permission.READ_EXTERNAL_STORAGE 등등

실행시점에 권한이 있는지 체크를 해야하는데 이럴땐 ContextCompat(ActivityCompat).checkSelfPermisstion()을 사용한다.

https://developer.android.com/training/permissions/requesting?hl=ko

 

런타임 권한 요청  |  Android 개발자  |  Android Developers

런타임 권한 요청 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 모든 Android 앱은 액세스가 제한된 샌드박스에서 실행됩니다. 앱이 자체 샌드박스 밖에 있

developer.android.com

스토리지를 읽는 방법도 버전마다 다르다. MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL), MediaStore.Images.Media.EXTERNAL_CONTENT_URI(29미만) 등등

다음을 참고하자.

https://developer.android.com/training/data-storage/shared/media?hl=ko

 

공유 저장소의 미디어 파일에 액세스  |  Android 개발자  |  Android Developers

DataStore는 로컬 데이터를 저장하는 최신 방법을 제공합니다. SharedPreferences 대신 DataStore를 사용해야 합니다. 자세한 내용은 DataStore 가이드를 참고하세요. 공유 저장소의 미디어 파일에 액세스 컬

developer.android.com

 

class MainActivity : AppCompatActivity() {

    private val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    //읽기 권한
    private val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        Manifest.permission.READ_MEDIA_IMAGES
    } else {
        Manifest.permission.READ_EXTERNAL_STORAGE
    }

    //스토리지 URI
    private val contentUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
    } else {
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    }

    private val requestPermissionLauncher =
        registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
            Log.d("RequestPermission", isGranted.toString())
            if (isGranted) {
                getAllPhotos(contentUri)
            } else {
                AlertDialog.Builder(this).apply {
                    setTitle("경고")
                    setMessage("권한 설정을 거부하여 접근할 수 없습니다")
                    setPositiveButton("종료") { _, _ ->
                        Handler(mainLooper).postDelayed({
                            finishAndRemoveTask()
                            exitProcess(0)
                        }, 100)
                    }
                }.show()
            }
        }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        //requestCode를 직접관리하는 경우 구현
        when (requestCode) {
            1 -> {
                if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                    getAllPhotos(contentUri)
                } else {
                    AlertDialog.Builder(this).apply {
                        setTitle("경고")
                        setMessage("권한 설정을 거부하여 접근할 수 없습니다")
                        setPositiveButton("종료") { _, _ ->
                            Handler(Looper.getMainLooper()).postDelayed({
                                finishAndRemoveTask()
                                exitProcess(0)
                            }, 100)
                        }
                    }.show()
                }

            }
            else -> {

            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        //권한이 있는지 확인
        if (ContextCompat.checkSelfPermission(this, permission)
            != PackageManager.PERMISSION_GRANTED
        ) {
            //권한 요청을 거부한 적이 있는 경우
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
                AlertDialog.Builder(this).apply {
                    setTitle("권한요청")
                    setMessage("사진 정보를 얻으면 외부 저장소 권한이 필요합니다.")
                    setPositiveButton("수락") { _, _ ->
                        //requestPermissionLauncher.launch(permission) // 시스템이 requestCode 코드를 관리
                        ActivityCompat.requestPermissions(this@MainActivity, arrayOf(permission), 1) // 직접 requestCode 관리
                    }
                    setNegativeButton("거부", null)
                }.show()
            } else {
                //requestPermissionLauncher.launch(permission)
                ActivityCompat.requestPermissions(this@MainActivity, arrayOf(permission), 1)
            }
            return
        }
        getAllPhotos(contentUri)

    }

    private fun getAllPhotos(collection: Uri) {
        Log.d("MainActivity", "getAllPhotos $collection")
        val uris = mutableListOf<Uri>()
        contentResolver.query(
            collection,
            null,
            null,
            null,
            "${MediaStore.Images.ImageColumns.DATE_TAKEN} DESC"
        )?.use { cursor ->
            while (cursor.moveToNext()) {
                val id =
                    cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
                val contentUri =
                    ContentUris.withAppendedId(collection, id)
                uris.add(contentUri)
            }
        }

        Log.d("MainActivity", "getAllPhotos -----> ${uris}")

    }
}

AlertDialog도 평이하다. builder에 themeResId를 적용할 수 있다. 예쁘게?

this.onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        Log.d("handleOnBackPressed", dateFormat.format(Date()))
        AlertDialog.Builder(this@MainActivity,android.R.style.Theme_DeviceDefault_Light_Dialog_NoActionBar_MinWidth).apply {
            setTitle("Information")
            setMessage("terminate?")
            setPositiveButton("Yes") { dialog, which ->
                Log.d("handleOnBackPressed Yes", "${dialog.toString()} $which")
            }
            setNegativeButton("No") { dialog, which ->
                Log.d("handleOnBackPressed No", "$dialog $which")
            }
        }.show()
    }
})


사용법을 참고하자.

https://developer.android.com/guide/topics/ui/dialogs?hl=ko

 

대화상자  |  Android 개발자  |  Android Developers

대화상자 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 대화상자는 사용자에게 결정을 내리거나 추가 정보를 입력하라는 메시지를 표시하는 작은 창입니

developer.android.com

프래드먼트(Fragment)는 말 그대로 조각, 액티비티를 구성하는 인터페이스의 모음이다.음..그냥 액티비티에 여러 요소를 배치할때 분할하기 위해 필요하다고 생각하자. 그런데 이건 생명주기도 따로 있고, 재사용이 가능하다. 콤포넌트군

https://developer.android.com/guide/components/fragments?hl=ko#Lifecycle

 

프래그먼트  |  Android 개발자  |  Android Developers

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section

developer.android.com

Activity처럼 보통 하나의 xml과 소스파일로 구성된다. parcel을 해석하면 쉽다. 뭔가 생성시점에 보내는 객체(소포)겠군..호출자,피호출자 사이에 데이터 전달(?)에 필요하겠다. 그러나 Activity처럼 화면에 바로 나타나는게 아니다.

아래는 두개의 Fragment를 작성하고 MainActivity에는 각각을 호출하는 버튼과 Fragment가 보여질 FrameLayout(FragmentContainerView가 Fragemnt 전용이라고)을 위치 시켜 테스트한 코드와 화면이다. gradle dependency에 androidx.fragment:fragment-ktx:1.6.2를 추가했다.

fragment끼리 값을 주고 받을 때는 setFragmentResult, setFragmentResultListener를 사용한다고 함

fragmentManager는 다음을 참고하자.

https://developer.android.com/guide/fragments/fragmentmanager?hl=ko

 

프래그먼트 관리자  |  Android 개발자  |  Android Developers

프래그먼트 관리자 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 참고: Navigation 라이브러리를 사용하여 앱의 탐색을 관리하는 것이 좋습니다. 프레임워크

developer.android.com

class MainActivity : AppCompatActivity() {

    private val binding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        binding.run {
            btnFirstFragment.setOnClickListener {
                setFragment(FirstFragment())
            }

            btnSecondFragment.setOnClickListener {
                setFragment(SecondFragment())
            }
        }
    }

    private fun setFragment(fragment: Fragment) {
        supportFragmentManager.commit {
            replace(binding.frameLayout.id, fragment)
            setReorderingAllowed(true)
            addToBackStack("")
        }
    }

}

 

Image를 로딩할때는 bitmap에 coil library(사진 로딩에 특화되어 메모리절약,부드러운 로딩)를 사용해서 load한다. 그게 메모리관리나 성능면에서 좋다고

ViewPager2는 프래그먼드를 슬라이드하는 Adapter를 구현하는 view다. FragmentStateAdapter를 상속받아 구현된 adapter를 연결해야한다.

반응형

'android > kotlin' 카테고리의 다른 글

firebase block request...  (0) 2024.01.26
안드로이드 정리 3  (0) 2024.01.23
android avd path change ( 경로 변경 )  (0) 2024.01.16
android bmi  (0) 2024.01.11
안드로이드 정리 1  (0) 2023.09.15
Comments