Notice
Recent Posts
Recent Comments
Tags
- SSD 복사
- javascript redirection
- Mac Oracle
- 하드 마이그레이션
- jquery 바코드생성
- XSS PHP
- javascript 유효성체크
- ViewBag
- TempData
- asp.net Select
- 강제이동
- php 캐쉬제거
- javascript 바코드스캔
- 맥 오라클설치
- javascript 바코드 생성
- 바코드 생성하기
- 404에러페이지
- 파일업로드 체크
- ViewData
- asp.net dropdownlist
- XSS방어
- 말줄임표시
- asp.net core Select
- django 엑셀불러오기
- 타임피커
- 하드 윈도우 복사
- 파일업로드 유효성체크
- jquery 바코드
- 바코드 스캔하기
- ASP.Net Core 404
웹개발자의 기지개
[안드로이드] WebView에서 카메라 및 이미지 업로드 (선택적용가능) 본문
이번 포스팅은 WebView 방식의 하이브리드 앱 작업시 참으로 중요한 내용이다.
WebView 형식으로 웹과 앱을 혼용하여 작업을 할때 파일업로드 기능은 별도로 앱으로 작업을 해주어야 한다.
이번에는 카메라 캡쳐 이미지와 원하는 이미지 파일업로드 (갤러리) 를 선택적으로 고를수 있도록 동시에 작업해본다.
[ Android 10 에서 확인함 ] - compileSdkVersion 29
[ 실행화면 ]
[ AndroidManifest.xml ]
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
|
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="wonpa.alwaysweb.com.webviewfileupload1">
<uses-permission android:name="android.permission.INTERNET"/>
<!--네트워크 상태 퍼미션-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 카메라 퍼미션 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CAMERA2" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<!-- 5.0 버전 파일업로드 퍼미션 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="18"/>
<!-- 외부 저장소 사용 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--android os 9 pie 버전부터 "http://" url 사용을 위한 보안설정 필요-->
<!--android:networkSecurityConfig="@xml/network_security_config" appication 태그에 삽입-->
<!--가로 세로 모드 변경돼도 새로 로딩하지 않도록 함 -->
<!--android:configChanges="orientation|keyboardHidden|screenSize" appication 태그에 삽입-->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<activity android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
cs |
[ file_paths.xml ] - /res/xml 내
https://stackoverflow.com/ 에서 검색하여 내외부저장소의 다양한 xml 링크주소를 모두 나타내었다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="sdcard"
path="." />
<external-files-path
name="external_files"
path="." />
<cache-path
name="cache"
path="." />
<external-cache-path
name="external_cache"
path="." />
<files-path
name="files"
path="." />
</paths>
|
cs |
[ network_security_config.xml ] - /res/xml 내
1
2
3
4
|
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true"/>
</network-security-config>
|
cs |
[ MainActivity.java ]
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
|
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import android.Manifest;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.webkit.JsResult;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceRequest;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import java.io.File;
|
cs |
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
|
public class MainActivity extends AppCompatActivity {
private WebView webView1;
private WebSettings webSettings;
private long time = 0;
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
webView1 = (WebView) findViewById(R.id.webView1);
// 각종 권한 획득
checkVerify();
webSettings = webView1.getSettings();
webSettings.setJavaScriptEnabled(true); // 자바스크립트 사용
webSettings.setSupportMultipleWindows(true); // 새창 띄우기 허용
webSettings.setJavaScriptCanOpenWindowsAutomatically(true); // 자바스크립트 새창 띄우기 허용
webSettings.setLoadWithOverviewMode(true); // 메타태그 허용
webSettings.setUseWideViewPort(true); // 화면 사이즈 맞추기 허용
webSettings.setSupportZoom(false); // 화면줌 허용 여부
webSettings.setBuiltInZoomControls(false); // 화면 확대 축소 허용 여부
// webSettings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.SINGLE_COLUMN); // 컨텐츠 사이즈 맞추기
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE); // 브라우저 노캐쉬
webSettings.setDomStorageEnabled(true); // 로컬저장소 허용
webView1.loadUrl("http://도메인/app_file/uploadfile.php");
webView1.setWebChromeClient(new WebChromeClientClass()); //웹뷰에 크롬 사용 허용. 이 부분이 없으면 크롬에서 alert가 뜨지 않음
webView1.setWebViewClient(new WebViewClientClass());
}
@Override
public void onBackPressed() {
//super.onBackPressed();
if(System.currentTimeMillis()-time>=2000){
time=System.currentTimeMillis();
Toast.makeText(getApplicationContext(),"뒤로 버튼을 한번 더 누르면 종료합니다.",Toast.LENGTH_SHORT).show();
}else if(System.currentTimeMillis()-time<2000){
finish();
return;
}
}
|
cs |
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 (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,Manifest.permission.INTERNET) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_NETWORK_STATE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
// Log.d("checkVerify() : ","if문 들어옴");
//카메라 또는 저장공간 권한 획득 여부 확인
if (ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) || ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.CAMERA)) {
Toast.makeText(getApplicationContext(),"권한 관련 요청을 허용해 주셔야 카메라 캡처이미지 사용등의 서비스를 이용가능합니다.",Toast.LENGTH_SHORT).show();
} else {
// Log.d("checkVerify() : ","카메라 및 저장공간 권한 요청");
// 카메라 및 저장공간 권한 요청
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.INTERNET, Manifest.permission.CAMERA,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
}
}
//권한 획득 여부에 따른 결과 반환
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//Log.d("onRequestPermissionsResult() : ","들어옴");
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();
}
}
}
|
cs |
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
|
//액티비티가 종료될 때 결과를 받고 파일을 전송할 때 사용
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d("onActivityResult() ","resultCode = " + Integer.toString(requestCode));
switch (requestCode)
{
case FILECHOOSER_NORMAL_REQ_CODE:
if (resultCode == RESULT_OK)
{
if (filePathCallbackNormal == null) return;
Uri result = (data == null || resultCode != RESULT_OK) ? null : data.getData();
// onReceiveValue 로 파일을 전송한다.
filePathCallbackNormal.onReceiveValue(result);
filePathCallbackNormal = null;
}
break;
case FILECHOOSER_LOLLIPOP_REQ_CODE:
Log.d("onActivityResult() ","FILECHOOSER_LOLLIPOP_REQ_CODE = " + Integer.toString(FILECHOOSER_LOLLIPOP_REQ_CODE));
if (resultCode == RESULT_OK)
{
Log.d("onActivityResult() ","FILECHOOSER_LOLLIPOP_REQ_CODE 의 if문 RESULT_OK 안에 들어옴");
if (filePathCallbackLollipop == null) return;
if (data == null)
data = new Intent();
if (data.getData() == null)
data.setData(cameraImageUri);
filePathCallbackLollipop.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, data));
filePathCallbackLollipop = null;
}
else
{
Log.d("onActivityResult() ","FILECHOOSER_LOLLIPOP_REQ_CODE 의 if문의 else문 안으로~");
if (filePathCallbackLollipop != null)
{ // resultCode에 RESULT_OK가 들어오지 않으면 null 처리하지 한다.(이렇게 하지 않으면 다음부터 input 태그를 클릭해도 반응하지 않음)
Log.d("onActivityResult() ","FILECHOOSER_LOLLIPOP_REQ_CODE 의 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 |
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
|
// 카메라 기능 구현
private void runCamera(boolean _isCapture)
{
Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//intentCamera.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//File path = getFilesDir();
File path = Environment.getExternalStorageDirectory();
File file = new File(path, "sample.png"); // sample.png 는 카메라로 찍었을 때 저장될 파일명이므로 사용자 마음대로
// File 객체의 URI 를 얻는다.
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
{
String strpa = getApplicationContext().getPackageName();
cameraImageUri = FileProvider.getUriForFile(this, strpa + ".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);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode==KeyEvent.KEYCODE_BACK) && webView1.canGoBack()) {
webView1.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
|
cs |
Intent chooserIntent = Intent.createChooser(pickIntent, pickTitle); 처럼 chooserIntent 인텐트를 만들고 두개의 인텐트를 동시에 선택하는 화면으로 구성한다.
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
72
73
74
75
76
|
private class WebChromeClientClass extends WebChromeClient {
// 자바스크립트의 alert창
@Override
public boolean onJsAlert(WebView view, String url, String message, final JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Alert")
.setMessage(message)
.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
})
.setCancelable(false)
.create()
.show();
return true;
}
// 자바스크립트의 confirm창
@Override
public boolean onJsConfirm(WebView view, String url, String message,
final JsResult result) {
new AlertDialog.Builder(view.getContext())
.setTitle("Confirm")
.setMessage(message)
.setPositiveButton("Yes",
new AlertDialog.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
result.confirm();
}
})
.setNegativeButton("No",
new AlertDialog.OnClickListener(){
public void onClick(DialogInterface dialog, int which) {
result.cancel();
}
})
.setCancelable(false)
.create()
.show();
return true;
}
// For Android 5.0+ 카메라 - input type="file" 태그를 선택했을 때 반응
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public boolean onShowFileChooser(
WebView webView, ValueCallback<Uri[]> filePathCallback,
FileChooserParams fileChooserParams) {
Log.d("MainActivity - onShowFileChooser", "5.0+");
// Callback 초기화 (중요!)
if (filePathCallbackLollipop != null) {
filePathCallbackLollipop.onReceiveValue(null);
filePathCallbackLollipop = null;
}
filePathCallbackLollipop = filePathCallback;
boolean isCapture = fileChooserParams.isCaptureEnabled();
Log.d("onShowFileChooser : " , String.valueOf(isCapture));
runCamera(isCapture);
return true;
}
}
private class WebViewClientClass extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
//Log.d("WebViewClient URL : " , request.getUrl().toString());
view.loadUrl(request.getUrl().toString());
return true;
//return super.shouldOverrideUrlLoading(view, request);
}
}
}
|
cs |
[ activity_main.xml ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:id="@+id/webView1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
|
cs |
참고페이지 : https://minasb.tistory.com/28
'안드로이드' 카테고리의 다른 글
[Android] 다음 우편번호 검색창을 팝업띄우지 않고 연결하기 (0) | 2020.07.31 |
---|---|
[Android] WebView 작업할때 Net::ERR_UNKNOWN_URL_SCHEME 에러 발생할때 (전화걸기,문자보내기 안된다) (0) | 2020.07.31 |
[안드로이드] android.support.v4.content.FileProvider not found (0) | 2020.06.19 |
[안드로이드] Volley 로 웹요청하고 응답받기3 - Get방식 , json 읽기 (php,mysql) (0) | 2020.06.11 |
[안드로이드] 구글맵 snippet을 두줄이상으로 구현하기 (0) | 2020.06.08 |
Comments