본문 바로가기

Project/Lifehelper

화면 캡처와 공유 (Bitmap, PixelCopy, File, FileOutputStream, FileProvider, Intent)

728x90

Toolbar의 옵션메뉴에 공유 버튼을 클릭하면 현재 화면을 캡처한 후 공유하는 기능

1. getBitmapFromView() Usage 

    getBitmapFromView(binding.root) { bitmap ->
                        screenShot(bitmap)
                    }

2. 화면 캡처

2-1. 고차함수

고차함수란 다른 함수를 인자로 받거나, 함수를 반환하는 함수입니다.

해당 예제에선 함수 반환 타입을 Unit으로 하여 객체를 반환함을 선언했습니다.

 

2-2. Bitmap

Bitmap이란 안드로이드에서 이미지 파일을 다루는 객체

 

createBitmap() : Bitmap 생성합니다.

  1. Int : 생성할 Bitmap의 폭
  2. Int : 생성할 Bitmap의 높이
  3. Bitmap.Config : Bitmap의 구성을 의미(pixel별 저장되는 방법)Bitmap.Config.ARGB_8888 : 한 픽셀당 4바이트를 이용해 우수한 색 표현을 합니다.

2-3. PixelCopy 

PixelCopy란 *Surface에서 Bitmap으로 복사 작업을 허용하기위해 픽셀 복사 요청을 발행하는 메커니즘을 제공합니다.

 

request() : 제공된 Bitmap에 복사하기 위해 window로부터 제공된 Rect에 있는 픽셀의 복사본을 요청합니다.

  1. Window : 뭔가를 그릴 수 있는 창이며, 하나의 Surface를 가집니다.애플리케이션은 windowManager와 상호작용하여 window를 생성하고 window 표면에 그리기 위한 surface를 만드며, 일반적으로 activity가 window를 가지게 됩니다.
  2. Rect : 사각형에 대해 4개의 정수 좌표를 보유하는 객체
  3. Bitmap : 안드로이드에서 이미지 파일을 다루는 객체
  4. PixelCopy.OnPixelCopyFinishedListener : 픽셀 복사 요청의 완료를 관찰하기위한 리스너
  5. Handler : listenerThread를 의미하며, 복사하가 완료되면 Handler에서 콜백이 호출됩니다.

2-4. 기타

getLocationInWindow() : 해당 뷰의 좌표를 계산하여 배열에 x, y가 반환됩니다.

 

invoke() : 메소드를 호출합니다.

 

즉, Bitmap을 생성하고, View의 좌표를 구하여 window의 영역에 대한 복사 요청을 보내고, Handler가 콜백을 호출하면 copyResult를 확인하여 callback() 호출합니다.

 

*surface : 화면에 합성되는 픽셀을 보유한 객체

fun getBitmapFromView(view: View, callback: (Bitmap?) -> Unit) {
        Log.d(Constants.TAG, "사진 캡처 시작!")

        requireActivity().window?.let { window ->
            val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
            // 비트맵 생성
            
            val locationOfViewInWindow = IntArray(2)
            view.getLocationInWindow(locationOfViewInWindow)
            // 해당 뷰의 좌표를 계산하여 배열에 x, y가 반환

            try {
                PixelCopy.request(window,
                    Rect(locationOfViewInWindow[0], locationOfViewInWindow[1], locationOfViewInWindow[0] + view.width, locationOfViewInWindow[1] + view.height),
                    bitmap, { copyResult ->
                        
                    if (copyResult == PixelCopy.SUCCESS) callback.invoke(bitmap)
                    else callback.invoke(null)
                    
                }, Handler(Looper.getMainLooper()))
                /*
                복사할 출처인 window를 Rect 영역만큼 복사 요청 / 완료 시 Handler가 콜백을 호출 / copyResult를 확인하여 callback() 호출
                 */

            } catch (e: IllegalArgumentException) {
                callback.invoke(null)
            }
        }
    }

3. 컨텐츠 공유

3-1. File

File() : 미리 생성된 File 객체의 경로명 + 경로명으로 새로운 인스턴스를 만듭니다.

  1. File : 부모 경로명cacheDir : 파일 시스템의 애플리케이션 특정 *cache 디렉토리에 대한 경로를 반환
  2. String : 자식 경로명

3-2. FileOutputStream

FileOutputStream(String) : 데이터를 파일에 Byte Stream으로 저장하기위해 사용됩니다.

즉, 인자로 주어진 이름의 파일을 쓰기위한 객체를 생성합니다.

 

FileOuputStream.close() : FileOuputStream을 닫고 관련된 모든 시스템 리소스를 해제합니다.

 

Bitmap.compress() : Bitmap 객체를 이미지 파일로 저장할 때 사용합니다.

  1. Bitmap.CompressFormat : 압축된 이미지의 형식
  2. Int : 압축률을 의미
  3. OutputStream : 압축된 데이터를 쓸 객체로, Byte Stream을 나타내는 모든 클래스의 수퍼 클래스

 

3-3. FileProvider

FileProvider란 *ContentProvider의 하위 클래스로,  앱 내부 파일을 공유하는데 주로 사용됩니다. 

FileProvider를 사용하기위해선 2가지가 준비되어야 합니다.

  1. AndroidManifest.xml에 FileProvider를 사용하겠다고 선언해주어야 합니다.name : FileProvider의 이름을 의미authorities : 고유 권한을 의미grantUriPermissions : 외부 앱에 임시 일회성 키를 제공할 것인지 여부exported : 모든 사용자가 해당 접근이 가능한지의 여부<meta-data> subelement : 공유하려는 디렉터리를 지정하는 xml 파일을 의미(name, resource)                                
  2. 위에서 선언해준 <meta-data>의 resource에 맞는 경로와 명의 xml 파일을 생성해주어야 합니다.

FileProvider.getUriForFile() : 파일을 공유하기위한 파일 URI를 생성합니다.

  1. Context 
  2. String : AndroidManifest.xml에 정의한 authorities 속성, 즉 고유권한
  3. File : 원하는 파일 객체의 경로명

3-4. Intent

ACTION_SEND(수행할 작업을 나타내는 문자열) : 공유 인텐트라고 하며, 다른 앱을 사용해 공유를 수행하는 경우 사용

setType : MIME 데이터 유형을 설정(위 작업할 데이터를 참조하는 URI MIME 타입을 의미)

putExtra() : 인텐트에 실제 데이터를 추가 (/값 쌍 구조)

  1. 문자 데이터 경우 Intent.EXTRA_TEXT, 바이너리 데이터의 경우 Intent.EXTRA_STREAM
  2. Bundle 데이터 값

Intent.createChooser() : Android Sharesheet를 표시하려면 이것을 호출해 Intent 객체에 전달합니다.

  1. 사용자가 수행할 활동을 선택
  2. 공유가능한 앱의 리스트의 Title

 

*Cache : 앱을 서비스할 때 접속 정보, 아이디 등과 같이 서버에 저장할 필요가 없거나, 서버에 저장하기 어려운 정보를 해결하기위해 사용자 기기에 저장하려는데, READ,WRITE_EXTERNAL_STORAGE 퍼미션을 받아서 파일을 다루기가 까다로워 퍼미션 없이 사용하기 위해 캐시가 등장

 

*ContentProvider : ContentProvider는 모든 종류의 데이터를 안전하게 공유할 수 있는 구성요소입니다.

 

    <application   
    	...
        >
        
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.gondroid.lifehelper.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>

    </application>
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <cache-path name="shared_images" path="images/"/>
</paths>
  private fun screenShot(bitmap: Bitmap?) {
        try {
            Log.d(Constants.TAG, "사진 공유 시작!")

            val cachePath = File(App.instance.cacheDir, "images")
         
            cachePath.mkdirs()    // 해당 디렉토리 생성 / 이것을 잊어선 안됨
           
           val stream = FileOutputStream("$cachePath/image.png")
            bitmap?.compress(Bitmap.CompressFormat.PNG, 100, stream)
            stream.close()
            
            val newFile = File(cachePath, "image.png")
            
            val contentUri: Uri = FileProvider.getUriForFile(
                App.instance,
                "com.gondroid.lifehelper.fileprovider", newFile
            )
            // URI를 이용해 파일을 공유하기 위해 해당 컨텐츠의 URI를 생성

            val Sharing_intent = Intent(Intent.ACTION_SEND)
          
            Sharing_intent.type = "image/png"

            Sharing_intent.putExtra(Intent.EXTRA_STREAM, contentUri)
       
            startActivity(Intent.createChooser(Sharing_intent, "Share image"))

        }catch (e: IOException){
            Log.d(Constants.TAG, "사진 공유 실패!")
            e.printStackTrace()
        }
    }

 

해당 내용을 공부했더니 하루가 삭제되버렸습니다....