일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 인코더
- c
- hackerrank
- DP
- ADAS
- Kotlin
- 백준
- BFS
- Rebase
- 블로그개설
- c++
- 머신러닝
- TensorFlow
- 백트래킹
- stl
- 안드로이드
- python3
- 스프링 프레임워크
- 스프링
- 프로그래밍
- 카카오인코더
- spring
- Android
- Map
- 연결리스트
- vue.js
- retrofit
- git
- 스프링프레임워크
- 프로그래머스
- Today
- Total
이것저것 공부한 기록
[Android] 어플리케이션 간 파일 공유하기 (broadcast로) 본문
이번 프로젝트 추가 구현 사항으로 서로 다른 패키지의 어플리케이션 간에 파일을 옮기는 구현이 필요하여 저장소 권한에 대해서 찾아보게 되었다.
안드로이드 Q(버전 10) 이후 저장소 사용 방법이 변경됨에 따라, 안드로이드 어플리케이션들은 외부저장소에 공용공간과 각자의 영역을 나누어 갖게 되었다. 각자의 영역에는 서로 접근이 불가능하고, 공용공간에는 다같이 접근이 가능하나 사진, 동영상, 음악의 경우엔 MediaStore 등 안드로이드에서 제공하는 API를 사용해야하고, 그 외 기타 파일들은 익히 아는 Downloads 폴더에 접근해야하며 파일탐색기 등의 UI를 통해 명시적으로 사용자가 지정한 파일에만 접근할 수 있다.
아래 블로그에 간단히 잘 설명되어 있음.
내가 필요한 구현사항은 아래와 같다.
1. 기본 설치 된 앱 A가 있음
2. 추가 data를 asset에 가진 앱B가 기본 앱 A에 파일을 넘김 (Broadcast)
3. 파일을 받은 A는 추가 data파일을 외부저장소에 저장해서 사용
사용할 것들 : AssetManager, FileProvider, BroadcastReceiver callback, ContentResolver
우선 앱 B를 생성하여 asset에 필요한 파일들을 추가해준다.
실제 복사할 디렉토리 구조와 동일하게 구성해줌.
실제 코드를 쓰긴 좀 힘들고 그냥 대충 pseudo 코드 혼합으로..
1. 데이터 전달 앱 B
FileProvider를 사용하기위해 AndroidMenifest.xml 파일 application영역에 provider 설정을 추가한다.
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.프로젝트명.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
위에 기재한대로 xml폴더에 file_paths.xml을 생성해준다. (위치: res/xml)
다른 앱에 FileProvider를 통해 생성한 URI를 공유할 경로를 지정한다.
<paths>
<external-files-path
name="external_files_path"
path="/"/>
<external-path
name="external_files"
path="/"/>
</paths>
이후 assetManager에서 파일을 가져와 외부저장소에 copy한다.
var inputStream = assetManager.open(filename)
var newFileName = context.getExternalFilesDir(null)!!.absolutePath + "/" + filename
var outputStream = FileOutputStream(newFileName)
// 파일 복사
// inputstream 및 outputstream 닫고 flush
FileProvider에서 getUriForFile을 해준다
var file = File(newFileName)
var fileUri: Uri = FileProvider.getUriForFile(
this,
"com.example.프로젝트명.fileprovider",
file
)
Uri를 여러개 넘겨야 했고, 각 파일별로 다른 path에 저장해야 했는데
마땅한 방법을 찾지못해 uri list, Filepath list, Filename list를 별도로 전달했다.
//uri와 마지막 접근 경로/ 파일 이름 저장
uriList.add(fileUri)
pathList.add(lastFilePath)
nameList.add(lastFileName)
원래 StartActivity로 intent를 전달해줄 경우 전달하는 Intent에 permission을 지정해서 전달하면 되는데,
아무리 전달해도 권한이 없다고 자꾸 중지되어 왜 이러는지 확인해보니
나는 sendBroadcast로 intent를 전달하고 있는데, sendBroadcast는 intent에 permission을 지정해서 넘길 수 없다고 한다.
때문에 각 file uri별로 지정한 패키지에서 접근할 수 있는 permission을 지정해줬다.
this.grantUriPermission("받을 패키지명", fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
추가로 Broadcast 전달 후 결과에 따라 토스트 팝업을 띄우고 추가 작업을 하기 위해 broadcastReceiver를 구현해서
sendOrderedBroadcast전달 시 인자로 넘겨 결과를 받았다.
val notificationResultReceiver : BroadcastReceiver = object:BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
if( resultCode == RESULT_OK ){
val toast = makeText(applicationContext, "data deliver success", Toast.LENGTH_SHORT)
toast.show()
} else {
val toast = makeText(applicationContext, "data deliver failed", Toast.LENGTH_SHORT)
toast.show()
}
}
}
//----intent 선언 및 extra에 list추가----//
try {
sendOrderedBroadcast(전달할 intent, null, notificationResultReceiver, null, RESULT_OK, null, null )
} catch (e: Exception) {
e.printStackTrace()
log.e("Intent", "file copy failed")
val toast = makeText(applicationContext, "Intent deliver failed", Toast.LENGTH_SHORT)
toast.show()
}
startActivity에서 결과를 받아올 수 있는 구현은 아래 블로그 참조.
2. 데이터 수신 앱 A
file uri list를 받아 ContentResolver를 사용해 파일을 가져온 뒤
복사하여 외부 저장소에 저장하고, result code를 세팅하는 Broadcast receiver를 구현해준다.
class DataReceiverA : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if ("지정한 액션명"== intent.action ) {
Timber.d("received update broadcast")
val uris = intent.getParcelableArrayListExtra<Uri>("Extra명")
val pathList = intent.getStringArrayListExtra("Extra명")
val nameList = intent.getStringArrayListExtra("Extra명")
Timber.d("receivedDatasize : ${uris!!.size}")
try {
for (i in 0 until uris!!.size) {
val newUri = uris[i]
val newFilePath = pathList?.get(i)
val newFileName = nameList?.get(i)
val resolver = context.contentResolver
val filesDirPath = context.getExternalFilesDir(null)?.absolutePath ?: context.filesDir.absolutePath
val mTargetBasePath = filesDirPath + "/app/"
Timber.d("filepath : $mTargetBasePath")
Timber.d("copyfileoutname : $mTargetBasePath$newFilePath")
val dir = File("$mTargetBasePath$newFilePath")
dir.mkdirs()
val inputStream = resolver.openInputStream(newUri!!)
val outputStream = FileOutputStream(mTargetBasePath + newFilePath + newFileName)
val buffer = ByteArray(1024)
var read: Int
while (inputStream!!.read(buffer).also { read = it } != -1) {
outputStream.write(buffer, 0, read)
}
inputStream!!.close()
outputStream.flush()
outputStream.close()
resultCode=RESULT_OK
}
} catch (e: FileNotFoundException) {
Timber.e("file not found")
resultCode= RESULT_CANCELED
}
Timber.d("end Copy")
}
}
}
Manifest 내에 Receiver를 선언해준다.
<receiver
android:name=".receiver.DataReceiverA"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="Intent명" />
</intent-filter>
</receiver>
휴 이번 난관도 어째저째 넘어갔다...
'Study > Kotlin&Android' 카테고리의 다른 글
[Android] bindService : unable to start service intent u=0 not found 에러 (0) | 2023.05.23 |
---|---|
[Android] Foreground Service App 만들기 (0) | 2023.05.08 |
[Android] Retrofit 서비스 인터페이스 정의 (애너테이션) (0) | 2021.12.12 |
[Android] 서비스 (0) | 2021.11.21 |
[Android] 뷰 이벤트 처리 방법 네 가지( Listener 등록 ) (0) | 2021.11.16 |