안드로이드 WebView에서 카메라 및 사진 갤러리 이미지 업로드 하기

개발/안드로이드 2018. 6. 14. 09:31
반응형

안드로이드 WebView에서 카메라 호출 및 사진 갤러리 호출 하는 방법에 대해서 알아보고 업로드 되는 기능까지 알아봅시다.

아래 참고한 사이트를 찾아보고 작성하긴 하였는데 대부분은 카메라 or 사진 갤러리 둘 중 하나만  호출 하는 방법만 설명되어 있습니다.  (제가 찾은 자료는 전부 인거 같네요.)


html 코드의 <input type="file">속성에 따라 카메라 or 사진 갤러리 선택 적으로 호출할 수 있는 방법을 설명하도록 하겠습니다.

그리고 인용된 일부 소스는 하단에 출처를 남겨 놓도록 하겠습니다.


1. Manifest Permission

1
2
3
4
5
6
7
8
9
<!-- 카메라 퍼미션 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<!-- 5.0 버전 파일업로드 퍼미션 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
<!-- 외부 저장소 사용 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
<uses-permission android:name="android.permission.INTERNET" />
cs



2. API 23 + 권한 획득

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@TargetApi(Build.VERSION_CODES.M)
public void checkVerify()
{
 
    if (    checkSelfPermission(Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED ||
            checkSelfPermission(Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED ||
            checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
            checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
            checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
            )
    {
        // Should we show an explanation?
        if (shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE))
        {
            ;
        }
 
        requestPermissions(new String[]{Manifest.permission.INTERNET,Manifest.permission.CAMERA,
                        Manifest.permission.ACCESS_NETWORK_STATE,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE},
                            }, 1);
    }
    else
    {
        //startApp();
    }
}
 
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 
    if (requestCode == 1)
    {
        if (grantResults.length > 0)
        {
            for (int i=0; i<grantResults.length++i)
            {
                if (grantResults[i] == PackageManager.PERMISSION_DENIED)
                {
                    // 하나라도 거부한다면.
                    new AlertDialog.Builder(this).setTitle("알림").setMessage("권한을 허용해주셔야 앱을 이용할 수 있습니다.")
                            .setPositiveButton("종료"new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    dialog.dismiss();
                                    finish();
                                }
                            }).setNegativeButton("권한 설정"new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                                    .setData(Uri.parse("package:" + getApplicationContext().getPackageName()));
                            getApplicationContext().startActivity(intent);
                        }
                    }).setCancelable(false).show();
 
                    return;
                }
            }
            //Toast.makeText(this, "Succeed Read/Write external storage !", Toast.LENGTH_SHORT).show();
            //startApp();
        }
    }
}
cs


일단 간단하게 실행할때 한방에 권한 획득 하도록 하겠습니다. onCreate에서 checkVerify 함수를 실행해 주시면 됩니다. 



3. 전역 변수

1
2
3
4
5
public ValueCallback<Uri> filePathCallbackNormal;
public ValueCallback<Uri[]> filePathCallbackLollipop;
public final static int FILECHOOSER_NORMAL_REQ_CODE = 2001;
public final static int FILECHOOSER_LOLLIPOP_REQ_CODE = 2002;
private Uri cameraImageUri = null;
cs



4. WebView의 setWebChromeClient 셋팅 및 Input 선택기 구현

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
WebView mWebView = (WebView) findViewById(R.id.activity_main);
webSettings.setJavaScriptEnabled(true);
//그외 WebView 셋팅..
 
mWebView.setWebChromeClient(new WebChromeClient() {
    // For Android < 3.0
    public void openFileChooser( ValueCallback<Uri> uploadMsg) {
        Log.d("MainActivity""3.0 <");
        openFileChooser(uploadMsg, "");
    }
 
    // For Android 3.0+
    public void openFileChooser( ValueCallback<Uri> uploadMsg, String acceptType) {
        Log.d("MainActivity""3.0+");
        m_oInstance.filePathCallbackNormal = uploadMsg;
        Intent i = new Intent(Intent.ACTION_GET_CONTENT);
        i.addCategory(Intent.CATEGORY_OPENABLE);
        i.setType("image/*");                
        m_oInstance.startActivityForResult(Intent.createChooser(i, "File Chooser"), m_oInstance.FILECHOOSER_NORMAL_REQ_CODE);
    }
 
    // For Android 4.1+
    public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
        Log.d("MainActivity""4.1+");
        openFileChooser(uploadMsg, acceptType);
    }
 
    // For Android 5.0+
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public boolean onShowFileChooser(
            WebView webView, ValueCallback<Uri[]> filePathCallback,
            FileChooserParams fileChooserParams) {
        Log.d("MainActivity""5.0+");
 
        // Callback 초기화 (중요!)
        if (filePathCallbackLollipop != null) {
            filePathCallbackLollipop.onReceiveValue(null);
            filePathCallbackLollipop = null;
        }
        filePathCallbackLollipop = filePathCallback;
 
        boolean isCapture = fileChooserParams.isCaptureEnabled();
        runCamera(isCapture);
        return true;
    }
});
cs

WebView의 setWebChromeClient를 생성해서 연결해야 HTML <input type="file"> 태그를 선택했을  때 반응하게 됩니다.

그러나 안드로이드 버전마다 다른 메소드를 호출 하게 됩니다. 

5.0 미만 버전에서도 테스트를 해보았으나 5.0 이상을 타겟으로 삼아 테스트 하였으니 참고 하시기 바랍니다.

 (5.0 미만은 갤러리만 뜨도록 되어 있습니다.)


42째줄 보면 isCaptureEnabled() 함수로 boolean 값을 받도록 되어 있습니다. 

이부분이 <input type="file"> 태그 속성값중 capture 값이 있는지 판단하는겁니다.


<input type="file" capture="camera"> : isCaptureEnabled 메소드에서 true로 리턴됨

<input type="file"> : isCaptureEnabled 메소드에서 false로 리턴됨



5. runCamera 구현 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void runCamera(boolean _isCapture)
{
    if (!_isCapture)
    {// 갤러리 띄운다.
        Intent pickIntent = new Intent(Intent.ACTION_PICK);
        pickIntent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        pickIntent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
 
        String pickTitle = "사진 가져올 방법을 선택하세요.";
        Intent chooserIntent = Intent.createChooser(pickIntent, pickTitle);
 
        startActivityForResult(chooserIntent, FILECHOOSER_LOLLIPOP_REQ_CODE);
        return;
    }
 
    Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
    File path = getFilesDir();
    File file = new File(path, "fokCamera.png");
    // File 객체의 URI 를 얻는다.
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
    {
        String strpa = getApplicationContext().getPackageName();
        cameraImageUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".fileprovider", file);
    }
    else
    {
        cameraImageUri = Uri.fromFile(file);
    }
    intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, cameraImageUri);
 
    if (!_isCapture)
    { // 선택팝업 카메라, 갤러리 둘다 띄우고 싶을 때..
        Intent pickIntent = new Intent(Intent.ACTION_PICK);
        pickIntent.setType(MediaStore.Images.Media.CONTENT_TYPE);
        pickIntent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
 
        String pickTitle = "사진 가져올 방법을 선택하세요.";
        Intent chooserIntent = Intent.createChooser(pickIntent, pickTitle);
 
        // 카메라 intent 포함시키기..
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{intentCamera});
        startActivityForResult(chooserIntent, FILECHOOSER_LOLLIPOP_REQ_CODE);
    }
    else
    {// 바로 카메라 실행..
        startActivityForResult(intentCamera, FILECHOOSER_LOLLIPOP_REQ_CODE);
    }
}
cs


실제 카메라 호출 구현부 입니다.

_isCapture 변수를 통해 카메라를 띄울지 아니면 사진 갤러리를 띄울지 판단을 하고 상황에 맞게 호출하고 있습니다.


여기서 유심히 보셔야 할 부분은 25째줄의 FileProvider 입니다. 

이건 안드로이드 7.0 (API 24)에서 추가된 기능으로 액세스 권한을 부여받고 그에 맞도록 구현되어야 합니다. 

권한을 부여받는 부분도 포스팅 되어 있으니 참고 하시면 도움 되실껍니다. 

http://mixup.tistory.com/98




6. onActivityResult 구현

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    //super.onActivityResult(requestCode, resultCode, data);
 
    switch (requestCode)
    {
        case FILECHOOSER_NORMAL_REQ_CODE:
            if (resultCode == RESULT_OK)
            {
                if (filePathCallbackNormal == nullreturn;
                Uri result = (data == null || resultCode != RESULT_OK) ? null : data.getData();
                filePathCallbackNormal.onReceiveValue(result);
                filePathCallbackNormal = null;
            }
            break;
        case FILECHOOSER_LOLLIPOP_REQ_CODE:
            if (resultCode == RESULT_OK)
            {
                if (filePathCallbackLollipop == nullreturn;
                if (data == null)
                    data = new Intent();
                if (data.getData() == null)
                    data.setData(cameraImageUri);
 
                filePathCallbackLollipop.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
                filePathCallbackLollipop = null;
            }
            else
            {
                if (filePathCallbackLollipop != null)
                {
                    filePathCallbackLollipop.onReceiveValue(null);
                    filePathCallbackLollipop = null;
                }
 
                if (filePathCallbackNormal != null)
                {
                    filePathCallbackNormal.onReceiveValue(null);
                    filePathCallbackNormal = null;
                }
            }
            break;
        default:
 
            break;
    }
 
    super.onActivityResult(requestCode, resultCode, data);
}
cs


onActivityResult 는 액티비티가 종료될때 결과를 받고자 할때 호출 됩니다. 

실제 카메라를 찍거나 갤러리에서 사진을 선택 하고 외부앱이 종료될때 호출됩니다. 

아까 4번에서 전역변수로 셋팅했던 callback 의 onReceiveValue 메소드를 실행하시면 파일전송이 이루어 집니다.

만약 resultCode 변수에  RESULT_OK 상수가 들어오지 않으면 null  처리 해주셔야 합니다. 

안그러면 그 다음부턴 input 태그를 클릭해도 반응을 하지 않습니다. 중요합니다.!



일부 자료 출처들

http://cofs.tistory.com/182

http://andriod77.blogspot.com/2014/08/android.html

반응형
admin