240206 TIL #314 Android #26 안드로이드 기본 앱과 연동

김춘복·2024년 2월 6일
0

TIL : Today I Learned

목록 보기
314/571

Today I Learned

오늘은 콘텐츠 프로바이더를 이용해 안드로이드 기본 앱인 주소록, 갤러리, 카메라, 지도, 전화 앱과 연동을 해봤다.


안드로이드 기본 앱과 연동

주소록 앱

  • 매니페스트 커미션 설정
<uses-permission android:name="android.permission.READ_CONTACTS"/>
  • 데이터 상수
    ContactsContract.Contacts.CONTENT_URI: 모든 사람의 데이터
    ContactsContract.CommonDataKinds.Phone.CONTENT_URI: 전화번호가 있는 사람
    ContactsContract.CommonDataKinds.Email.CONTENT_URI: 이메일 정보가 있는 사람
  • 주소록 목록 화면 띄우는 코드
// 주소록에서 한사람 선택후 돌아오면 실행되는 함수 정의
var requestContactsLancher = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult()
) {
    if (it.resultCode == Activity.RESULT_OK) Log.d("event", "${it.data?.data}")
}

// 주소록 목록 출력
val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
requestContactsLancher.launch(intent)
  • 주소록에서 전달한 데이터는 URL 문자열 형태이다. 맨 마지막 단어는 사용자가 선택한 사람의 식별값이다.

갤러리 앱

갤러리 앱 연동은 인텐트로 갤러리 앱의 목록 화면을 띄우거나 갤러리 앱의 콘텐츠 프로바이더로 데이터를 가져오는 작업이다.

고려사항

  • 안드로이드에서 이미지는 Drawable(주로 리소스 이미지)이나 Bitmap(파일이나 네트워크에서 읽은 이미지) 객체로 표현한다. 둘은 서로 호환된다.

  • Bitmap 객체는 BitmapFactory로 생성한다.

  • Glide나 Picasso 같은 이미지 처리 라이브러리를 이용하는 것이 효율적일 수 있다.

  • BitmapFactory 클래스의 decode로 시작하는 함수로 bitmap 이미지는 생성된다.

    BitmapFactory.decodeByteArray(): byte[] 배열의 데이터로 비트맵 생성
    BitmapFactory.decodeFile(): 파일 경로를 매개변수로 지정하면 그 파일에서 데이터를 읽을 수 있는 FileInputStream을 만들어
    decodeStream() 함수 이용
    BitmapFactory.decodeResource(): 리소스 이미지로 비트맵 생성
    BitmapFactory.decodeStream(): InputStream으로 읽은 데이터로 비트맵 생성

  • BitmapFactory로 이미지를 생성할 때는 OOM 오류를 고려해야 한다.
    이는 크기가 큰 이미지를 불러올 떄 앱의 메모리 부족 에러이다.
    이를 방지하기 위해 옵션으로 이미지 크기를 줄이면 된다.

val option = BitmapFactory.Options()
option.inSampleSize = 4 // inSampleSize의 비율 만큼 용량 절감
val bitmap = BitmapFactory.decodeStream(inputStream, null, option)

갤러리 앱 연동방법

  • 앱의 사진 목록을 불러오는 코드
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
intent.type = "image/*"
requestGalleryLauncher. launch(intent)
  • 이미지 비율 및 크기 수정
private fun calculateInSampleSize(fileUri: Uri, reqWidth: Int, reqHeight: Int): Int {
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true // true로 옵션만 설정
    try {
        var inputStream = contentResolver.openInputStream(fileUri)
        BitmapFactory.decodeStream(inputStream, null, options) // 이미지 정보들이 option에 설정
        inputStream!!.close()
        inputStream = null
    } catch (e: Exception) {
        e.printStackTrace()
    }
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1
    // inSampleSize 비율 계산
    if (height > reqHeight || width > reqwidth) {
        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2
        while (halfHeight / inSampleSize >= reqHeight &&
            halfWidth / inSampleSize >= reqWidth
        ) {
            inSampleSize *= 2
        }
    }
    return inSampleSize

}
  • 이미지 불러오는 코드
val requestGalleryLauncher = registerForActivityResult(
        ActivityResultContracts.StartActivityForResult())
{
    try {
        // inSampleSize 비율 계산, 지정
        val calRatio = calculateInSampleSize(it !!. data !!. data !! ,
            resources.getDimensionPixelSize(R.dimen.imgSize),
            resources.getDimensionPixelSize(R.dimen.imgSize))
        val option = BitmapFactory.Options()
        option.inSampleSize=calRatio

        // 이미지 로당
        var inputStream = contentResolver.openInputStream(it !!. data !!. data !! )
        val bitmap = BitmapFactory.decodeStream(inputStream, null, option)
        inputStream !!. close()
        inputStream = null
        bitmap ?. let {
            binding.galleryResult.setImageBitmap(bitmap)
        } ?: let {
            Log.d("kkang", "bitmap null")
        }
    } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

카메라 앱

  • 카메라 앱을 실행해 사진을 촬영해 사진 데이터를 가져오는 방법에는 두 가지 방법이 있다.

1. 사진 데이터 가져오기

카메라 앱으로 사진 촬영 후 파일로 저장하지 않고 데이터만 넘겨주는 방식.
쉽게 구현할 수 있지만 데이터의 크기가 작다는 단점이 있다.

  • 인텐트로 카메라 앱의 사진 촬영 액티비티 실행
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
requestCameraThumbnailLauncher. launch(intent)
  • 카메라 앱에서 넘어온 사진 데이터를 ActivityResultCallback에서 아래의 코드로 가져오기
requestCameraThumbnailLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()){
val bitmap = it?.data?.extras?.get("data") as Bitmap
}

2. 사진 파일을 공유하는 방법

카메라 앱에서 촬영한 사진을 저장한 후 성공, 실패만 넘겨주는 방식
카메라 성능만큼 큰 크기의 사진을 촬영해 앱에서 이용가능 하지만 몇가지 준비작업을 해야한다.

  • 절차
  1. 앱에서 사진을 저장할 파일 생성
  2. 사진 파일 정보를 포함한 인텐트를 전달해 카메라 앱을 실행
  3. 카메라 앱으로 사진을 촬영하여 공유된 파일에 저장
  4. 카메라 앱을 종료하면서 성공 또는 실패를 반환
  5. 카메라 앱이 저장한 사진 파일을 앱에서 이용
  • 앱에서 외장 메모리에 파일을 만들려면 getExternalStoragePublicDirectory() 또는 getExternal FilesDir() 함수를 이용해야한다.

  • 이때 API 레벨 19 하위버전에서는 아래의 퍼미션이 필요하다.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  • API 24버전 부터는 file:// 프로토콜로 구성된 URI를 외부에 노출할 수 없다. 따라서 content:// 프로토콜 이용 후 여기에 임시로 접근할 수 있는 권한을 부여해야하는데, FileProvider 클래스를 이용한다.

  • FileProvider 클래스는 androidx 라이브러리에서 제공하며 XML 설정을 기반으로 해서 content:// 프로토콜로 구성된 URI를 생성한다.

<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="myfiles" path="Android/data/com.example.test16/files/Pictures" />
</paths>
  • 파일 프로바이더용 XML 파일을 매니페스트 파일에 등록한다.
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.test16.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
  • 파일 생성
val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File.createTempFile("JPEG_${timeStamp]_", ".jpg", storageDir)
filePath = file.absolutePath
  • FileProvider를 이용해 Uri 객체를 만들고 이를 카메라 앱을 실행하는 인텐트의 엑스트라 데이터로 설정
val photoURI: Uri = FileProvider.getUriForFile(this, "com.example.test16.fileprovider", file)

val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
requestCameraFileLauncher.launch(intent)
  • 카메라 앱에서 사진을 촬영한 후 다시 앱으로 돌아왔을 때 실행되는 코드
requestCameraFileLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
	val option=BitmapFactory.Options()
	option.inSampleSize = 10
	val bitmap = BitmapFactory.decodeFile(filePath, option)
	bitmap?.let {
    	binding.cameraFileResult.setImageBitmap(bitmap)
	}
}

지도 앱

앱이 위도와 경도 값을 가지고 있다면 지도 앱을 연동해 위치를 보여줄 수 있다.

  • 지도앱 실행하는 인텐트
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("geo: 37.123456,126.977123"))
startActivity(intent)
  • URL은 반드시 geo:로 시작해야 하고, 위도와 경도는 쉼표로 구분한다.

전화 앱

전화를 거는 기능은 전화 앱의 액티비티에 이미 구현되어 있으므로 전화 앱과 연동해 전화번호 데이터만 넘겨주면 된다.

• 전화를 거는 퍼미션을 매니페스트에 설정

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

• 전화 앱을 실행하는 인텐트

val intent = Intent(Intent.ACTION_CALL, Uri.parse("tel:02-123"))
startActivity(intent)
  • 전화앱의 인텐트 액션 문자열은 Intent.ACTION_CALL, 데이터 URL은 tel: 로 시작해야 한다.
profile
Backend Dev / Data Engineer

0개의 댓글