[Android] FileProvider를 이용해 저장소 권한 없이 현재 화면 공유하기

 

안드로이드 게임 앱인 "스택" 이라는 게임을 아시나요? 단순히 터치만으로 블록을 쌓는 게임인데, 그건 중요하지 않고 이 앱은 공유하기를 이용하여 주변 사람들에게 자신의 게임 스코어를 자랑할 수 있습니다. 아래와 같이 말이죠.

 그런데 이렇게 전체 화면을 캡쳐한 후 공유를 하게 된다면, 스크린샷 이미지를 저장하기 위해 저장소에 접근할 수 있는 권한을 얻어야할 것 같은데 이 게임은 그렇지 않은 것 같네요. 앱 설정에 들어가서 확인해봐도 저장소 권한을 허용하지 않았는데도 위와 같이 이미지를 캡쳐한 후 공유가 가능했습니다. 서론이 좀 길었는데, 저장소 접근 권한 없이 스크린샷 이미지를 공유하는 기능을 만들어보겠습니다. 혹시 공유하기 기능을 구현해본 적이 없다면, 이 글에서도 소개하겠지만, 아래의 링크를 먼저 참고해도 좋습니다.

https://keykat7.blogspot.com/2021/02/android-mime-type.html



1. FileProvider란?

FileProvider는 ContentProvider의 하위 클래스입니다. ContentProvider는 앱 내에서 사용하는 데이터를 공유하기 위한 컴포넌트인데, 가령 자신이 만든 앱에서 전화번호부에 접근하려고 할 때와 같이 특정 앱이 가지고 있는 데이터를 주고 받고자 할 때 사용하게 됩니다. 자세하게 설명을 하자면 내용이 길어지니 이만 줄이겠습니다.
FileProvider를 사용하면 임시 액세스 권한을 사용하여 읽기 및 쓰기 권한을 부여하게 되고, 이를 이용해 저장소 접근 권한이 필요한 작업임에도 불구하고 권한 체크를 스킵하고 작업을 진행할 수 있게 됩니다. FileProvider에 대해 더 궁금하다면
그렇다면 이제 이 FileProvider를 이용해 캡쳐 후 공유하기 기능을 만들어봅시다. 작업은 아래의 과정을 거치게 될 것입니다.


1. AndroidManifest.xml에 FileProvider 선언하기
2. xml/filepaths.xml을 만든 후, filepaths.xml 내용 수정
3. 캡쳐 후 공유 기능 구현





2. AndroidManifest.xml에 FileProvider 선언해주기

AndroidManifest.xml에 FileProvider를 사용하겠다고 선언을 해주어야 하는데요, 아래의 코드를 먼저 보도록 합시다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test">


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Test">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

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


    </application>

</manifest>

<application> 안의 <provider>를 보면 android:authorities가 있는데, 이것의 이름을 자신의 앱 패키지에 맞게 지어주세요. 제가 만든 앱의 패키지 이름은 com.example.test이므로 com.example.test.fileprovider로 지었습니다. 그리고 아래에 android:resource 부분이 아마 에러 처리가 되어있을텐데, 아직 만들지 않아서 그렇습니다. 바로 다음에서 만들 것이니 지우지 말아주세요.





3. xml/filepaths.xml 만들기

res 폴더 안에 xml 디렉토리를 만든 후, 그 안에 filepaths.xml 파일을 만들어주세요. res 폴더를 우클릭 -> New -> Directory로 xml 디렉토리를 생성하고, 만든 xml 디렉토리를 우클릭 -> New -> XML Resource File로 xml 파일을 만든 후 이름을 filepaths로 지어주세요. 대충 아래 사진과 같은 구조가 되게 해주세요.

filepaths.xml 안에 디자인을 할 수 있는 창이 나오는데, 어차피 여기에 디자인을 할 일은 없으니 무시해도 좋습니다. filepaths.xml을 만든 후에 아래의 코드를 넣어주세요.

<?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>






4. 캡쳐 및 공유하기 기능 구현하기

이제 설정은 끝났습니다. 캡쳐 및 공유하기 기능을 사용하기 위한 버튼을 하나 만들어주시고, (맨 위의 링크에서 설명한 적이 있습니다. 잘 모른다면 위에서 확인해보세요.) 아래의 코드를 작성해보세요.

package com.example.test;

import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // activity_main의 버튼을 가져오기
        Button shareButton = findViewById(R.id.shareButton);

        // 클릭 리스너 구현
        shareButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ScreenShot();
            }
        });
    }

    //화면 캡쳐하기
    public void ScreenShot(){

        View view = getWindow().getDecorView().getRootView();
        view.setDrawingCacheEnabled(true);  //화면에 뿌릴때 캐시를 사용하게 한다

        //캐시를 비트맵으로 변환
        Bitmap screenBitmap = Bitmap.createBitmap(view.getDrawingCache());

        try {

            File cachePath = new File(getApplicationContext().getCacheDir(), "images");
            cachePath.mkdirs(); // don't forget to make the directory
            FileOutputStream stream = new FileOutputStream(cachePath + "/image.png"); // overwrites this image every time
            screenBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
            stream.close();

            File newFile = new File(cachePath, "image.png");
            Uri contentUri = FileProvider.getUriForFile(getApplicationContext(),
                    "com.example.test.fileprovider", newFile);

            Intent Sharing_intent = new Intent(Intent.ACTION_SEND);
            Sharing_intent.setType("image/png");
            Sharing_intent.putExtra(Intent.EXTRA_STREAM, contentUri);
            startActivity(Intent.createChooser(Sharing_intent, "Share image"));

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


getWindow().getDecorView().getRootView()를 이용해 전체화면을 담은 뷰를 선언하고 캐쉬 사용을 허용해 줍니다. 그 후 해당 뷰를 Bitmap으로 바꿔줍니다. 
getCacheDir를 이용해 해당 앱의 임시 캐쉬 파일을 저장하는 폴더로 진입해서 디렉토리를 하나 만든 후, 위에서 만들었던 비트맵을 포멧 변환한 후에 저장해주세요. 
만들었던 이미지를 newFile이라는 이름으로 선언해주고 해당 이미지로부터 Uri를 가져와주세요. 이 때 2번째 인자로는 AndroidManifest.xml에서 설정했던 authorities를 넣어주시면 됩니다. 
그 후 이전에 공부했던 공유하기 기능을 setType을 image로 설정해서 공유해주시면 됩니다.

한 번 실행해볼까요?

문제 없이 공유가 잘 되는 것을 확인할 수 있습니다.





마치며..

사실 위의 과정이 저장소 접근 권한을 설정하는 것과 비교해서 크게 쉬운 느낌은 들지 않지만, 권한 설정이 크게 필요하지 않은 경우에서는 조금 유용하다고 생각이 드네요. 자신이 만드는 앱의 특성을 잘 생각해서 어떤 것을 사용할지 결정해주세요. 



댓글