Search

'uri'에 해당되는 글 2건

  1. 2021.05.18 Android 10 스토리지 정책 대처하기
  2. 2018.07.02 URI (Uniform Resource Identifier)

Android 10 스토리지 정책 대처하기

개발/안드로이드 2021. 5. 18. 20:40 Posted by 아는 개발자

targetSdkVersion 을 30으로 올리면 파일 절대 경로를 사용해서 접근 할 수 없기 때문에 개발자들은 지금부터 슬슬 절대 경로를 사용해서 접근하는 코드를 변경해야한다. 이번 포스트에서는 안드로이드 새로운 스토리지 정책을 적용한 과정을 다뤄본다.

 

1. 절대 경로 대신 Uri 를 사용하도록 변경

 

기존에는 ContentResolver 클래스를 이용해 파일을 읽어올 때 DATA 칼럼을 이용해서 파일의 절대 경로를 읽어올 수 있었다. 그런데 DATA 컬럼은 Android 10부터 Deprecated가 됐고, targetSdkVersion 30으로 올리면 DATA 칼럼으로 얻을 수 있는 절대 경로로 파일이 접근이 되지 않는다. 

 

private suspend fun loadVideoContent(): Cursor? = coroutineScope {
    val where = MediaStore.Video.VideoColumns.SIZE + " > " + 0
    val sortOrder = MediaStore.Files.FileColumns.DATE_ADDED + " DESC"
    val projections = listOf(
        MediaStore.Video.Media._ID,
        MediaStore.Video.Media.DATA, // Deprecated됨
        MediaStore.Video.Media.DISPLAY_NAME
    ).toTypedArray()

    return@coroutineScope requireActivity().contentResolver.query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projections, where, null, sortOrder)
}

 

 

이제는 우리에게 익숙한 절대 경로 대신 Uri를 이용한 상대 경로를 사용해야한다. Uri는 content:// 로 시작하는 문자열인데, _ID 칼럼에서 얻어온 값과 ContentUri 클래스를 이용해서 얻어올 수 있다. 이 값도 파일을 찾는 경로로 사용되며 현재 Glide, MediaMetadataRetriever, Exoplayer처럼 유명한 안드로이드 라이브러리들은 Uri를 통해서도 파일을 불러올 수 있게끔 업데이트가 된 상태라 호환성은 크게 걱정하지 않아도 된다. 절대경로와 다른점은 실제 파일의 경로를 보여주지 않아 플랫폼 보안적인 요소가 강화된다. 반대로 개발자의 피로도는 악화되고.

 

val uriCol = cursor.getColumnIndex(MediaStore.Video.Media._ID)

do {
    mediaItems.add(
        MediaItem(
            ContentUris.withAppendedId(
                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                cursor.getLong(uriCol)
            ),

 

2. File 클래스를 선언해야하는 경우 

 

문제는 File 클래스를 선언해야하는 경우다. 오래된 오픈소스거나 Uri를 고려하지 않은 모듈인 경우엔 절대 경로가 필요한 File 클래스를 사용해야하는 경우가 종종 있다. 그런데 앞서 언급했듯이 Uri는 상대 경로다. Uri를 통해 File 클래스로 바꿀수 있긴 한데 이건 file 스키마를 가진 Uri인 경우에만 그렇다. Uri 클래스에 Kotlin에 확장 코드로 toFile() 함수가 있긴 한데 ContentUris로 얻어온 Uri 클래스에 쓰면 요런 에러가 뜬다.

 

 

이럴 때는 절대 경로를 읽을 수 있는 형태로 꼼수가 필요하다. Android 10부터 스토리지를 절대 경로로 접근하는 것은 안되지만 앱 전용 캐시 영역은 여전히 절대 경로로 접근 할 수 있다. 스토리지에 있는 파일을 캐시로 복사하면 복사한 파일의 절대 경로로 파일 클래스를 선언해줄 수 있다. copy 작업이 딜레이도 있고 불필요하게 캐시영역 써야해 완벽한 방법은 아니다. 하지만 라이브러리에서 Uri를 지원하기 전까지는 써먹을 수 있을 것 같다. 더 좋은 방법이 있다면 공유해주시면 좋겠다. 나는 이것 말고는 딱히 방법을 못찾겠다...

 

val dir = File(context.cacheDir.path + File.separator + effectFolderName)
val filePath = context.cacheDir.path + File.separator + effectFolderName + File.separator + filename
val file = File(filePath)
val inputStream = getApplication<App>().contentResolver.openInputStream(uri)

try {
    FileUtils.copyToFile(inputStream, it) // org.apache.commons.io 를 사용
} catch (e: IOException ) {
    e.printStackTrace()
}

 

3. 미디어 파일을 추가하는 경우

 

앱에서 이미지나 동영상을 다운받는 경우 예전에는 Environment.getExternalStorageDirectory().path 코드를 이용해서 직접 원하는 경로에 파일을 생성해서 추가할 수 있었으나 Android 10 부터는 ContentResolver를 이용해 Uri로 파일을 추가해야한다. 아래 코드는 이미지 파일을 저장소에 추가하는 코드다. ContentValues 값을 설정해 임의의 이미지 파일을 만든 후 insert 함수에서 생성된 Uri 변수로 FileOutputStream을 만들고 I/O 라이브러리를 이용해 기존 파일과 복사하는 작업이다. 관계형 데이터베이스에 새로운 행을 추가하고 값을 업데이트한다고 보면 쉬울 것 같다. 실제로 ContentResolver는 관계형 데이터베이스 쿼리랑 상당부분 흡사하다.

 

val collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)

val contentvalues = ContentValues().also {
    it.put(MediaStore.MediaColumns.RELATIVE_PATH, "Images")
    it.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
    it.put(MediaStore.MediaColumns.IS_PENDING, true)
}

val uri = context.contentResolver.insert(collection, contentvalues)
val fos = context.contentResolver.openOutputStream(uri!!, "w")

try {
    FileUtils.copyFile(sourceFile, fos)
} catch (e: IOException) {
    return false
}
values.put(MediaStore.MediaColumns.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)

 

728x90

URI (Uniform Resource Identifier)

기술 2018. 7. 2. 22:34 Posted by 아는 개발자

URI (Uniform Resource Identifier)


현재 URI을 간략하게 소개하는 글을 쓰기 위해 접속한 티스토리 에디터의 주소는 이렇다.


http://kwony91.tistory.com/admin/entry/post/?type=post&returnURL=/manage/posts/


위 주소를 가지고 URI을 이루는 요소에 대해서 간단히 알아보자


1. 스키마(Scheme)


주소 맨 앞에 있는 "http://" 은 많이들 봤을 것이다. 이것 말고도 https도 있고 개발하는 사람들은 ftp, ssh도 봤을 것이다. 일할 때는 특별한 명칭 없이 "http에요? https에요?"라고 말하곤 했는데 전문적인 용어로 스키라(Scheme)라고 부른다. 스키마의 역할은 웹브라우저나 스마트폰 같은 일종의 클라이언트가 서버에게 resource에 어떤 방식으로 접근 할 지를 결정하는 역할을 한다. 여기에서 이제껏 몰랐던 무수히 많은 스키마들을 확인 해볼 수 있다


2. 호스트(Host)


지금은 도메인 서버를 써서 selffish-developer.com을 사용하는데 티스토리 계정으로 로그인해서 글을 쓸 때는 나의 원래 주소(kwony91.tistory.com)을 이용해야지만 가능하다. 호스트는 리소스의 주소를 의미하며 클라이언트는 이 값을 이용해서 리소스를 찾을 수 있다. 내 블로그 진짜 주소 외에도 www.google.com, www.naver.com 이것들도 모두 호스트다. 식상한 얘기지만.


한가지 흥미로운건 mailto라는 스키마를 사용하면 개인 이메일 주소도 호스트로 본다.


3. 경로(Path)


호스트 뒤에 슬래쉬로 붇는 문장은 경로를 의미한다. 호스트 위치까지는 잘 찾아왔고 여기서 더 세분화된 리소스를 찾기 위한 세부 주소를 의미한다. /admin/entry/post 의 뜻은 호스트 주소에서 관리자(admin) 페이지의 내부(entry)에서 글을 작성(post)하는 리소스를 말한다. 현재 글을 쓰고 있는 에디터 페이지와 쿵짝이 맞는다. 쉽게 이해할 수 있을 것 같다.


물음표 뒷부분은 질의(query)다. 질의의 역할은 리소스를 찾은 서버에 전달할 정보를 의미한다. 질의는 '?'를 이용해서 시작하고 '&'를 이용해서 구분하며 각각의 값들은 key=value로 표현한다. 현재 접속한 페이지의 질의값은 총 두 개이며 type은 post이고 returnURL은 /manage/posts라고 전달했다. 이름만으로 역할을 유추해보면 현재 에디터의 타입은 post이고 마치고 돌아올 경로는 /manage/posts 라고 볼 수 있을 것 같다.

728x90

'기술' 카테고리의 다른 글

VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
libgdx - Renderer  (0) 2018.06.22
Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20