테스트 환경 ( 갤럭시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 버전만 테스트 완료된 소스입니다.

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

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

 

 

 

by 개발자 CofS 2016.04.14 10:20
  • 니버 2017.02.12 20:43 신고 ADDR EDIT/DEL REPLY

    ESavingConstants.FILECHOOSER_LOLLIPOP_REQ_CODE 이 두 변수가 에러가 날 것이다. 이는 직접 구현한 int type의 상수 코드로서 필자는 1, 2 를 각각 주었다.
    이부분 해결을 하고 싶습니다. 현재
    private static final int ESavingConstants = 1;
    private static final int FILECHOOSER_NORMAL_REQ_CODE= 2; < 이부분이 에러가 나고 있습니다. 어떻게 해야하나요?

    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2017.02.13 09:02 신고 EDIT/DEL

      하드코딩으로 직접 1, 2 를 넣어주셔서 구분하셔도되고 static 변수로 사용하실꺼면 private 가 아니고 public 으로 선언하시면 될걸요 ㅎㅎ

  • 아이 2017.02.17 20:30 신고 ADDR EDIT/DEL REPLY

    질문 하나만 드려도 되나요?
    public static final int FILECHOOSER_NORMAL_REQ_CODE=2;해도 오류가 생기는데 어떻게 하면 좋은가요?

    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2017.02.17 20:53 신고 EDIT/DEL

      어떤오류인지 선언된곳이 어딘지 알아야 왜 오류가나는지 유추해볼수 있을것같네요ㅠ
      그냥 대뜸 오류가난다고하면ㅎㅎ알수가없어요

  • 강의잘봤습니다 2017.02.17 21:00 신고 ADDR EDIT/DEL REPLY

    ESavingConstants.FILECHOOSER_LOLLIPOP_REQ_CODE 이 두 변수가 에러가 날 것이다. 이는 직접 구현한 int type의 상수 코드로서 필자는 1, 2 를 각각 주었다.

    이부분에서 해결 하고싶은데
    어떻게 1 하고 2를 부여하는지 잘모르겠습니다 ㅠ



    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2017.02.17 22:14 신고 EDIT/DEL

      아ㅎㅎ
      저 상수는 제가 정의한것이라
      직접 하드코딩하시거나 다른곳에 변수나 상수로정의하시면되요ㅋㅋ

  • BlogIcon 안녕하세요 혹시 2017.02.27 20:49 신고 ADDR EDIT/DEL REPLY

    혹시 이 소스를 가지고 영리적 목적으로 사용 할 수있는건가요?

  • 알려주세요ㅠㅠ 2017.03.13 11:28 신고 ADDR EDIT/DEL REPLY

    이거는 전역에 선언하는건가요 아님 함수내에 선언하는건가요.아니면 위에 써잇는 함수들내에 선언해야하는건가요..? 선언 하는 위치를 모르겠어요 ㅠㅠ
    public static final int ESavingConstants = 1;
    public static final int FILECHOOSER_NORMAL_REQ_CODE= 2;

    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2017.03.13 15:15 신고 EDIT/DEL

      전역에 선언하시거나 상수 모아두는 곳에 선언하시면 되요 ㅎㅎ

  • 2017.04.20 11:43 ADDR EDIT/DEL REPLY

    비밀댓글입니다

  • 감사합니다. 2017.04.21 09:23 신고 ADDR EDIT/DEL REPLY

    덕분에 파일찾기를 누르면 카메라와 앨범 선택이 둘다 나오는데 카메라를 선택했을 때 아무런 반응이 없으면 무엇때문인지 혹시 아시나요 ㅠㅠ

    +비밀댓글 조회법을 모르겠어요...
    ㅠㅠㅠㅠ

    • 2017.04.21 09:25 EDIT/DEL

      비밀댓글입니다

    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2017.04.21 13:17 신고 EDIT/DEL

      그건 소스를 봐야알겠네요
      어떤 오류나 로그나 특정 부분을 알려주시면 혹시나 의심이 되는 부분에 대해서는 말씀드리겠습니다.

  • 2018.02.06 16:15 ADDR EDIT/DEL REPLY

    비밀댓글입니다

    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2018.02.06 16:44 신고 EDIT/DEL

      설정하신 소스도 다르고 해서 어떻게 말씀드리기가 어렵네요 ㅜㅜ

  • 질문남 2018.09.17 16:51 신고 ADDR EDIT/DEL REPLY

    처음엔 사진,갤러리 선택이 되는데요
    2번째는 선택이안되는데
    7.0 이후에 Provider 관련되서 무언가 설정 하는게 있나요?

    • Favicon of http://cofs.tistory.com BlogIcon 개발자 CofS 2018.09.19 14:11 신고 EDIT/DEL

      버전이 올라간 이후에는 document 를 확인해보지 않아서 추가적인 설정이 있는지 말씀드리긴 어려울거 같아요 ㅠㅠ

  • 하하맨 2018.11.13 08:37 신고 ADDR EDIT/DEL REPLY

    public static final int ESavingConstants = 1;
    public static final int FILECHOOSER_NORMAL_REQ_CODE= 2;
    로 선언을 했는데,

    startActivityForResult(Intent.createChooser(i, "File Chooser"), ESavingConstants.FILECHOOSER_NORMAL_REQ_CODE);에서
    ESavingConstants.FILECHOOSER_NORMAL_REQ_CODE 이 부분이 계속 에러가 뜹니다.
    "cannot resolve symble 'FILECHOOSER_NORMAL_REQ_CODE' 이라고 하는데요, 무슨 에러인지 알려주실 수 있나요?