일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- list
- RxKotlin
- Koin
- SWIFTUI
- SwiftUI Tutorial
- liveData
- mvvm
- 테스트 자동화
- Android
- mysql
- android13
- Android 13
- junit
- node
- MediaSession
- Animation
- MediaPlayer
- Kotlin
- Observable
- rx
- 동영상
- node.js
- databinding
- PagingLib
- MotionLayout
- Reactive
- paging
- 인앱결제
- GCP
- google play
- Today
- Total
봄날은 갔다. 이제 그 정신으로 공부하자
android R Scoped Storage 2편 (미디어 저장소에서 이미지를 읽어오자) 본문
이번 글은 아래 글에 이은 2편 입니다.
https://als2019.tistory.com/52
지난번 글에서 Scoped Storage에 대해 너무 뜬구름 잡듯이 설명하고 지나가서 저 스스로도 그래서 어떻게 저장소에서 이미지를 읽어오라는건데? 라는 질문일 들더라구요.
그래서 저장소에서 이미지를 읽어오느 샘플 소스를 직접 만들어 보았습니다. ^______^v
샘플 소스는 아래 git lepo에 있습니다.
https://github.com/lee-kil-jae/MyGallery
샘플 소스 위주로 간략히 설명하도록 하겠습니다.
설명 전에…
해당 소스는 MVVM 패턴으로 제작되었으며 DI로는 KOIN을 이미지 라이브러리로는 coil을 사용하였습니다.
그외에 tedpermission 등 다양한 라이브러리를 사용했습니다.
샘플 소스에 추가된 라이브러리는 app 폴더의 build.gradle 파일을 참조하시면 됩니다.
제일 우선 해야 할 것. (저장소 접근 권한 받기)
샘플 소스에서는 저장소에 있는 이미지만 읽어올 예정이므로 manifest.xml 파일에 아래 permission만 추가해줍니다.
Android R부터는 이제 아래 permission에 대해 사용자로부터 권한을 부여받아야 합니다.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
권한은 TedPermission을 사용해 아래와 같이 요청해서 받아 줍니다.
TedPermission.with(this)
.setRationaleMessage(R.string.need_read_storage_permission)
.setDeniedMessage(R.string.denied_read_storage_permission)
.setPermissions(Manifest.permission.READ_EXTERNAL_STORAGE)
.setPermissionListener(object : PermissionListener {
override fun onPermissionGranted() {
Toast.makeText(this@MainActivity, R.string.granted_read_storage_permission, Toast.LENGTH_SHORT).show()
}
override fun onPermissionDenied(deniedPermissions: ArrayList<String>?) {
Toast.makeText(this@MainActivity, R.string.denied_read_storage_permission, Toast.LENGTH_SHORT).show()
}
}).check()
저장소 이미지 읽어오기 (폴더 별 이미지 읽어오기)
저장소의 이미지를 한꺼번에 읽어오면 시간이 오래걸리므로 일반적인 갤러리 앱들처럼 우선 폴더별 이미지를 읽어오는 방식을 사용하도록 하겠습니다. 폴더별 이미지를 읽어오는 부분은 GalleryFolderViewModel class의 loadFolderInfo()함수 입니다.
프로젝션 및 정렬 순서를 정한 후 폴더 정보를 요청하는 query 함수를 호출합니다.
val PROJECTION_FOLDER = arrayOf(
MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATA
)
val orderBy = MediaStore.Images.Media.DATE_TAKEN
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
PROJECTION_FOLDER,
null,
null,
"$orderBy DESC"
)
가져온 폴더 정보를 LiveData 객체에 추가해줍니다.
cursor?.let { folderCursor ->
var ids = mutableListOf<Int>()
while (folderCursor.moveToNext()) {
val folder = DataGalleryFolder(
folderCursor.getInt(INDEX_BUCKET_ID),
folderCursor.getString(INDEX_BUCKET_NAME),
folderCursor.getString(INDEX_BUCKET_URL)
)
if (!ids.contains(folder.id)) {
ids.add(folder.id)
items.value.add(folder)
}
}
ids.clear()
folderCursor.close()
}
폴더에 포함되어 있는 이미지 갯수 정보를 따로 query를 해 읽어와야 하므로 아래와 같이 query를 요청해 읽어옵니다.
for (folder in items.value) {
val searchPrarams = MediaStore.MediaColumns.BUCKET_ID + " = " + folder.id
val columns = arrayOf(MediaStore.Images.Media._ID)
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
columns,
searchPrarams,
null,
null
)
folder.count = cursor?.count?:0
cursor?.close()
}
이미지 보여주기
이렇게 읽어온 것을 아래 이미지와 같이 보여주기 위해 RecyclerView와 BindingAdapter를 만들어줍니다.
// layout.xml
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rcvFolders"
android:layout_width="0dp"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="6"
folderViewModel="@{vmFolder}"
folderItems="@{vmFolder.items}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintBottom_toBottomOf="parent"/>
// bindingAdapter
@BindingAdapter(value = ["folderItems", "folderViewModel"], requireAll = true)
@JvmStatic
fun initFolderListView(view : RecyclerView, items : List<DataGalleryFolder>, vm : GalleryFolderViewModel){
view.adapter?.run {
if (this is AdapterGalleryFolder) {
this.items = items
this.vm = vm
}
}?: run{
AdapterGalleryFolder(items, vm)
.apply {
view.adapter = this
}
}
(view.layoutManager as GridLayoutManager).setSpanSizeLookup(object: GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when(position){
0 -> 6
1,2 -> 3
else -> 2
}
}
})
}
정리
android R(11)부터 강제로 적용되는 Scoped Storage 정책에 대응하려면 아래 내용을 반드시 유념해야 합니다.
- 저장소 접근을 위해서는 READ_EXTERNAL_STORAGE 권한도 받아야 합니다.
- 저장소 접근 시 이상하게 접근하지 말고 위 소스와 같이 MediaStore 를 통해서만 접근해야 합니다.
간혹 MediaStore를 통해 접근해도 안되는 경우가 있는데 이런 경우 소스를 보면 대부분 query 호출 시
ID를 통한 접근이 아닌 Name을 통한 접근 등과 같이 잘 못된 접근인 경우가 많습니다.
R 이전에는 이런것도 허용했는데 R부터는 ID를 통해서만 접근해야 합니다.
샘플 소스에는 폴더별 이미지 상세 불러오기까지 구현되어 있습니다. ^^
'android Tip' 카테고리의 다른 글
android Bitmap과 함께하는 즐거운 시간 (0) | 2021.03.15 |
---|---|
네트워크 비동기 통신에서 중복 요청 방지 (0) | 2021.03.05 |
Android Q의 새로운 저장소 정책 (0) | 2021.02.05 |
간단히 만들어보는 시간차를 통한 중복 클릭 방지 class (0) | 2021.02.03 |
앱 재실행 (App Restart) (0) | 2021.01.22 |