android webview 에서 카메라 호출 및 사진첩(갤러리) 호출하여 이미지 파일 업로드 하기

2016. 4. 14. 10:20mobile/android

 

 

테스트 환경 ( 갤럭시s5, android 5.0 롤리팝 )

 

본 포스팅은 webview 에서 호출한 page의 input file 로 카메라 호출 및 사진첩(갤러리) 을 호출하는 방법에 대한 포스팅이다.

 

 

 

아래 링크는 이전에 포스팅한 html5 에서 web과 스마트폰의 카메라 또는 사진첩(갤러리)를 호출하는 방법이다.

android html5 스마트폰 카메라와 연결하기, 사진(갤러리) 및 동영상 찍기 예제 ( URL.createObjectURL )

이 링크는 단순히 모바일 웹에서 호출할 경우만 해당되는 것이였다.

 

 

하이브리드 형식의 네이티브로 web을 감싸고 webview 로 페이지를 호출하고 input file 을 열심히 클릭해도 동작하지 않았다.

아무런 반응이 없다.

구글링 해보니 webview 에서 호출한 경우는 네이티브에서 별도로 구현을 해 주어야 한다고 한다.

이 포스팅에서의 설명은 완벽하지 않는다는 점을 미리 공지한다. 킷켓은 완전 별도 구현을 해야한다는 다른 개발자들의 의견글들이 많이 있었고 4.1+ 이하 버전은 테스트 조차 못해봣다. 5.0 롤리팝에서의 기능만 중점으로 구현하였다.

 



 

자세한 설명은 여기까지 하고 바로 본문으로 들어간다.

 

 

요약

webview의 ChromeClient 를 생성해서 연결해야한다.

그리고 openFileChooser 함수들을 구현해 주어야한다. openFileChooser 함수는 input file 을 클릭했을 시 실행되는 함수이다. api 상에서는 히든처리기 때문에 Override를 선언하면 애러가 난다. 그냥 버전에 맞게 openFileChooser 함수를 구현하면 된다.

 

필자는 카메라도 같이 사용해야 했다.

5.0 롤리팝 부분만 집중 구현 및 테스트하였다.

그 이하 버전들은 구글링해서 발췌한 소스들인데 카메라와 연동은 안되어 있고 사진첩이나 외부 파일들과의 연동만 되는 상태이다. 테스트를 해보지는 못했다. 혹시 다른 버전을 테스트 해보신 분들은 댓글을 남겨주시면 감사하겠습니다ㅠㅠ

 

 

기본적인 webview 셋팅에 대한 설명은 생략한다.

 

전역변수

1
2
3
    private ValueCallback<Uri> filePathCallbackNormal;
    private ValueCallback<Uri[]> filePathCallbackLollipop;
    private Uri mCapturedImageURI;
cs

onCreate 내부에서 셋팅

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
66
67
68
69
70
71
// web view 셋팅
        webView = (WebView) findViewById(R.id.webView);
        webView.getSettings().setJavaScriptEnabled(true);
        mWebViewInterface = new WebViewInterface(MainActivity.this, webView); //JavascriptInterface 객체화
        webView.addJavascriptInterface(mWebViewInterface, "Android"); //웹뷰에 JavascriptInterface를 연결
        // 현재 웹뷰에서 처리하도록...
        webView.setWebViewClient(new EsavingWebViewClient());
 
        // 크롬 클라이언트 생성
        webView.setWebChromeClient(new WebChromeClient() {
            // For Android < 3.0
            public void openFileChooser(ValueCallback<Uri> uploadMsg) {
                openFileChooser(uploadMsg, "");
            }
 
            // For Android 3.0+
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {
                filePathCallbackNormal = uploadMsg;
                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                i.addCategory(Intent.CATEGORY_OPENABLE);
                i.setType("image/*");
                startActivityForResult(Intent.createChooser(i, "File Chooser"), ESavingConstants.FILECHOOSER_NORMAL_REQ_CODE);
            }
 
            // For Android 4.1+
            public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
                openFileChooser(uploadMsg, acceptType);
            }
 
 
            // For Android 5.0+
            public boolean onShowFileChooser(
                    WebView webView, ValueCallback<Uri[]> filePathCallback,
                    WebChromeClient.FileChooserParams fileChooserParams) {
                if (filePathCallbackLollipop != null) {
//                    filePathCallbackLollipop.onReceiveValue(null);
                    filePathCallbackLollipop = null;
                }
                filePathCallbackLollipop = filePathCallback;
 
 
                // Create AndroidExampleFolder at sdcard
                File imageStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "AndroidExampleFolder");
                if (!imageStorageDir.exists()) {
                    // Create AndroidExampleFolder at sdcard
                    imageStorageDir.mkdirs();
                }
 
                // Create camera captured image file path and name
                File file = new File(imageStorageDir + File.separator + "IMG_" + String.valueOf(System.currentTimeMillis()) + ".jpg");
                mCapturedImageURI = Uri.fromFile(file);
 
                Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedImageURI);
 
                Intent i = new Intent(Intent.ACTION_GET_CONTENT);
                i.addCategory(Intent.CATEGORY_OPENABLE);
                i.setType("image/*");
 
                // Create file chooser intent
                Intent chooserIntent = Intent.createChooser(i, "Image Chooser");
                // Set camera intent to file chooser
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Parcelable[]{captureIntent});
 
                // On select image call onActivityResult method of activity
                startActivityForResult(chooserIntent, ESavingConstants.FILECHOOSER_LOLLIPOP_REQ_CODE);
                return true;
 
            }
 
        });
cs

1# ~ 7# : 기본적인 webview 셋팅이니 설명 생략 ( 4# , 7# 등은 오류가 날 수 있음, 그럴 그냥 라인자체를 지우거나 주석처리하거나 직접 클라이언트나 인터페이스를 구현하세요)

10# ~ : 본격적인 ChromeClient 구현 부분이다. 주석에서 나와있듯이 버전별로 함수들이 다르게 구현되어있다. 4.1+ 하위 버전들을 테스트 해 볼 수 없어서 자세한 설명은 할 수 없지만 구글링으로 발췌한 소스들이니 기본동작에는 무리가 없으리라 생각한다.

22#, 66# : ESavingConstants.FILECHOOSER_NORMAL_REQ_CODE, ESavingConstants.FILECHOOSER_LOLLIPOP_REQ_CODE 이 두 변수가 에러가 날 것이다. 이는 직접 구현한 int type의 상수 코드로서 필자는 1, 2 를 각각 주었다. 5.0의 롤리팝과 그 이하의 버전의 구분을 주기 위해 설정한 부분이다. 이는 onActivityResult 에서 분기하여 처리해야 하기 때문이다.

32# : 5.0 롤리팝에서의 구현부분이다. 함수의 명이 살짝 바뀐 것이 보인다. (기타 설명은 주석 참고)

 

 



 

 

onActivityResult 구현부분

onActivityResult 에 대해서 설명하자면 말이 길어지니 간략히 설명하겠다.

startActivityForResult 를 통해서 엑티비티의 이동을 구현하고 이동한 엑티비티가 종료되면 onActivityResult 를 호출한다. 이 과정에서 여러 결과값들을이나 코드들을 넘겨받을 수 있다.

위에서 카메라나 사진첩을 호출할 때 startActivityForResult 를 통해서 구현하였다. 그 말인 즉슨 startActivityForResult 를 통해서 카메라나 사진첩을 호출하고 onActivityResult 에서 결과값으로 사진이나 이미지를 넘겨받는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == ESavingConstants.FILECHOOSER_NORMAL_REQ_CODE) {
            if (filePathCallbackNormal == nullreturn;
            Uri result = (data == null || resultCode != RESULT_OK) ? null : data.getData();
            filePathCallbackNormal.onReceiveValue(result);
            filePathCallbackNormal = null;
        } else if (requestCode == ESavingConstants.FILECHOOSER_LOLLIPOP_REQ_CODE) {
            Uri[] result = new Uri[0];
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                if(resultCode == RESULT_OK){
                    result = (data == null) ? new Uri[]{mCapturedImageURI} : WebChromeClient.FileChooserParams.parseResult(resultCode, data);
                }
                filePathCallbackLollipop.onReceiveValue(result);
            }
        }
    }
cs

3# : 5.0 롤리팝이냐 그 이하 버전에냐에 따라 사용하는 함수가 달라 분기하였다.

 

Manifest의 퍼미션

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

 

 

 

 

 

다시 한번 말하지만 5.0 버전만 테스트 완료된 소스입니다.

그 이하버전을 테스트해보신 분들이 계시다면 필히 피드백을 주시면 감사하겠습니다.

혹시나 다른 방법이 있으신 분들도 연락주세요.