국비교육(22-23)

76일차(3)/Android App(35) : WebView

서리/Seori 2023. 1. 26. 23:51

76일차(3)/Android App(35) : WebView

 

 

- 새 모듈 생성-Empty Activity

 

MainActivity

package com.example.step15webview;

import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //WebView 객체의 참조값 얻어오기
        WebView webView=findViewById(R.id.webView);
        //WebView 의 설정 객체 얻어오기
        WebSettings ws=webView.getSettings();
        //javascript 해석 가능하게 설정
        ws.setJavaScriptEnabled(true);
        //WebView 클라이언트 객체 넣어주기
        webView.setWebViewClient(new WebViewClient());
        //webView.setWebChromeClient(new WebChromeClient());
        /*
            원하는 url 로딩시키기

            인터넷 자원을 사용해야 한다 => 비용이 발생할 가능성이 있다 => 사용자의 허가를 얻어내야 한다.

            허가(permission)

            인터넷을 사용하겠다는 permission이 AndroidManifest.xml 문서에 있어야 한다.
         */
        webView.loadUrl("https://naver.com");
    }
}

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/webView" />

</LinearLayout>

 

- 웹뷰에서는 html, css, js를 브라우저와 똑같이 해석해준다.

 

- 안드로이드 기기에서 어떤 기능에 access하려면 permission (허가)를 얻어내야 한다.

- 어떤 권한에 대한 허가를 Android Manifest 에 명시해야 한다.

 

- 앱을 첫 설치했을때 물어보는 필수항목들!

- AndroidManifest.xml 에 명시하지 않은 것을 몰래 코딩을 통해 접근할 수 없다.

 

- 앱에서 인터넷 자원에 접근하려면 허가가 필요하다.

- 이외에도 비용 발생, 인터넷 사용, 배터리를 많이 사용하는 앱 등등의 경우에는 특별한 허가가 필요하다.

 

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 인터넷 자원을 사용하겠다는 허가(permission) -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyAndroid"
        android:usesCleartextTraffic="true">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

 

- manifest에 설정추가

- usesClearTextTraffic="true" : http:// 요청을 가능하게 하는 권한이다. (https 가 아니다)

 

- 이렇게 화면에 네이버가 노출된다.

 

- 안드로이드 웹뷰에 Spring 프로젝트 작업물을 띄우고 싶다면 이렇게 경로를 넣어주면 된다.

- 만약 반응형으로 되어있다면 폰화면으로도 잘 보일 것이다.

 

- 레이아웃에 웹뷰 띄우기

 

- 웹뷰 클라이언트 객체를 생성해서 넣어주고, 아래 주소를 넣어주면 원하는 웹페이지가 나온다.

 


 

새 모듈 생성

step15webview2 - Empty Activity

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <WebView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/webView"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <EditText
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="wrap_content"
            android:id="@+id/inputUrl"
            android:hint="http://"/>
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/moveBtn"
            android:text="이동"/>
    </LinearLayout>
</LinearLayout>

 

- WebView 와 LinearLayout이 세로 높이를 나누어 가지도록 UI 설정!

 

 

- 이런 레이아웃이 된다.

 


 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 인터넷을 사용하겠다는 permission -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- 외부 저장장치(sdcard)를 사용하겠다는 허가 얻기 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyAndroid"
        android:usesCleartextTraffic="true">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>

 

- manifest에 인터넷 사용 관련 설정추가

 

 

- 2개의 권한 추가

 


MainActivity

package com.example.step15webview2;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;

import androidx.appcompat.app.AppCompatActivity;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //WebView 객체의 참조값 얻어오기
        WebView webView=findViewById(R.id.webView);
        //WebView 의 설정 객체 얻어오기
        WebSettings ws=webView.getSettings();
        //javascript 해석 가능하게 설정
        ws.setJavaScriptEnabled(true);
        //WebView 클라이언트 객체를 생성해서 넣어주기
        webView.setWebViewClient(new WebViewClient(){
            //재정의하고 싶은 메소드가 있으면 여기서 해준다.

        });
        /*
            WebView 가 로딩한 웹페이지에 <input type="file" /> 이런 내용이 있다면
            해당 input 요소를 클릭하면 파일을 선택할 수 있게 해야 한다.
            그렇게 하기 위해서는 AndroidManifest.xml 문서에 외부 저장장치를 사용하겠다는 퍼미션이 있어야한다.
            android에서 외부 저장장치는 sd card라고 부르기도 한다.
            sd card는 따로 install 하지 않아도 기본으로 장착되어 있다.

            그리고 아래의 WebChromeClient 객체를 설정해야 한다.
         */
        //아래의 MyWebViewClient 클래스로 생성한 객체를 전달한다.
        webView.setWebChromeClient(new MyWebViewClient());


        //EditText의 참조값
        EditText inputUrl=findViewById(R.id.inputUrl);

        //버튼의 참조값 얻어오기
        Button moveBtn=findViewById(R.id.moveBtn);
        moveBtn.setOnClickListener(view -> {
            //입력한 url 읽어와서
            String url=inputUrl.getText().toString();
            //webView에 로딩하기
            webView.loadUrl(url);
        });
    }


    public class MyWebViewClient extends WebChromeClient {
        // For Android Version < 3.0
        public void openFileChooser(ValueCallback<Uri> uploadMsg) {
            //System.out.println("WebViewActivity OS Version : " + Build.VERSION.SDK_INT + "\t openFC(VCU), n=1");
            mUploadMessage = uploadMsg;
            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
            intent.addCategory(Intent.CATEGORY_OPENABLE);
            intent.setType(TYPE_IMAGE);
            startActivityForResult(intent, INPUT_FILE_REQUEST_CODE);
        }

        // For 3.0 <= Android Version < 4.1
        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) {

            openFileChooser(uploadMsg, acceptType, "");
        }

        // For Android 4.1+

        public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {

            openFileChooser(uploadMsg, acceptType);
        }

        // For Android 5.0+
        @Override
        public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> uploadFile, WebChromeClient.FileChooserParams fileChooserParams) {


            if(mFilePathCallback !=null) {
                mFilePathCallback.onReceiveValue(null);
                mFilePathCallback = null;
            }

            mFilePathCallback = uploadFile;
            Intent i =new Intent(Intent.ACTION_GET_CONTENT);
            i.addCategory(Intent.CATEGORY_OPENABLE);
            i.setType("image/*");

            startActivityForResult(Intent.createChooser(i, "File Chooser"), INPUT_FILE_REQUEST_CODE);

            return true;

        }


        private void imageChooser() {
            Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if (takePictureIntent.resolveActivity(MainActivity.this.getPackageManager()) != null) {
                // Create the File where the photo should go
                File photoFile = null;
                try {
                    photoFile = createImageFile();
                    takePictureIntent.putExtra("PhotoPath", mCameraPhotoPath);
                } catch (IOException ex) {
                    // Error occurred while creating the File
                    Log.e(getClass().getName(), "Unable to create Image File", ex);
                }

                // Continue only if the File was successfully created
                if (photoFile != null) {
                    mCameraPhotoPath = "file:"+photoFile.getAbsolutePath();
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                            Uri.fromFile(photoFile));
                } else {
                    takePictureIntent = null;
                }
            }

            Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT);
            contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE);
            contentSelectionIntent.setType(TYPE_IMAGE);

            Intent[] intentArray;
            if(takePictureIntent != null) {
                intentArray = new Intent[]{takePictureIntent};
            } else {
                intentArray = new Intent[0];
            }

            Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
            chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent);
            chooserIntent.putExtra(Intent.EXTRA_TITLE, "Image Chooser");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);

            startActivityForResult(chooserIntent, INPUT_FILE_REQUEST_CODE);
        }
    }

    //변수
    private static final String TYPE_IMAGE = "image/*";
    private static final int INPUT_FILE_REQUEST_CODE = 1;

    private ValueCallback<Uri> mUploadMessage;
    private ValueCallback<Uri[]> mFilePathCallback;
    private String mCameraPhotoPath;

    private File createImageFile() throws IOException {
        // Create an image file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES);
        File imageFile = File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );
        return imageFile;
    }


    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == INPUT_FILE_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    if (mFilePathCallback == null) {
                        super.onActivityResult(requestCode, resultCode, data);
                        return;
                    }
                    Uri[] results = new Uri[]{data.getData()};

                    mFilePathCallback.onReceiveValue(results);
                    mFilePathCallback = null;
                } else {
                    if (mUploadMessage == null) {
                        super.onActivityResult(requestCode, resultCode, data);
                        return;
                    }
                    Uri result = data.getData();

                    Log.d(getClass().getName(), "openFileChooser : " + result);
                    mUploadMessage.onReceiveValue(result);
                    mUploadMessage = null;
                }
            } else {
                if (mFilePathCallback != null) mFilePathCallback.onReceiveValue(null);
                if (mUploadMessage != null) mUploadMessage.onReceiveValue(null);
                mFilePathCallback = null;
                mUploadMessage = null;
                super.onActivityResult(requestCode, resultCode, data);
            }
        }
    }
}

 

- WebViewClient 에서 가지고 있는 메소드를 사용하고 싶으면

 익명 이너클래스로 오버라이드해서 넣어주기 (alt+insert)

 

 

- 여러가지 이벤트 발생시 호출되는 메소드들이 들어있다.

- 이렇게 메소드를 override해서 각각의 상황에 따른 동작을 정해두면 

  웹뷰를 세밀하게 제어할 수 있다.

 

- inputType="file" 의 기본 동작은? 해당 파일시스템의 어딘가에서 파일을 찾는 것!

- 안드로이드는 파일 시스템이 공개되어 있지 않다.

- 공개된 자원, 어떤 저장장치에 access 할 수 있게 설정을 해주어야 한다.

 

 

AndroidManifest 수정

- External Storage 에 대한 권한 설정

- write 권한을 얻어내면 read는 기본으로 된다!

 

 

- 내부클래스로 MyWebViewClient 추가하기

 

- SimpleDateFormat, Date는 각각 이 패키지에 들어있는 클래스를 import하기

 

- 이미지를 사용하거나 파일을 가져오는 webview

 

- 파일을 클릭하면 이 메소드가 수행된다.

(파일 시스템 접근, 업로드할 파일 선택 및 업로드하기)

 


 

- url을 입력하고 버튼을 클릭하면 해당 경로로 이동하는 기능

 

- url 내용을 읽어와서 웹뷰에 로딩시키기

- loadUrl 메소드로 입력한 곳으로 이동할 수 있도록 작성

 

- Activity를 리스너로 만들어도 되고, 이렇게 리스너를 익명 이너클래스로 바로 등록해도 된다.

 

- 이너클래스 안에서 바로 입력받은 url을 읽어와서 웹뷰에 로딩시켜주면 된다.

 

- 버튼을 클릭하면 이렇게 페이지로 이동한다.

 


 

http://localhost:9000/boot07/

- 기존에 spring으로 만들었던 페이지로 들어가기!

- 기존에 입력해서 들어갔던 주소이다.

 

 

- 여기서 localhost란?

→ 데스크탑의 웹브라우저에서 localhost란 이 동일한 컴퓨터를 가리킨다.

→ 하지만 안드로이드 기기 webview에서는 localhost는 이 안드로이드 기기를 가리킨다.

 

- 그럼 ip주소를 알아야 한다.

- 명령 프롬프트에서 ipconfig

 

- ip주소가 192.168.0.34 로 나오는것을 볼 수 있다.

 

 

- ip주소:9000/boot07/ 을 입력하면 이렇게 해당 웹페이지가 나온다.

 

- 자료실의 사진올리는 기능도 사용 가능!

- 안드로이드에서 사진을 업로드하는 코드를 넣어주었기 때문에 동작하는 것

 

- 가상기기의 카메라로 샘플 사진을 찍고

 

 

- 이런 식으로 사진을 업로드할 수 있다.

 

- 레이아웃을 반응형으로 잘 짜놓으면 이렇게 모바일기기로 봐도 크게 문제가 없다.

 

- 처음부터 코드를 짤 때 url에 이렇게 적어놓으면 해당 경로가 맨처음에 뜬다.

 (회사 앱의 홈페이지 등 설정 가능)

 

- 웹뷰 화면을 띄우고, 일부는 native UI (텍스트부분)를 넣는다.

 이렇게 레이아웃을 섞어놓을 수 있다.

 


 

- 웹페이지가 모바일에 최적화되지 않은 경우 좌우로 스크롤되는데, 개선하는 방법!

 

<meta name="viewport" content="width=device-width, initial-scale=1">

 

- Bootstrap 홈페이지에 있는 "viewport" 설정을 더해주면 된다.

- 이 설정이 있어야 모바일 화면에서도 깔끔하게 나온다.

 

- MywebView 객체를 생성해서 여기에 넣어주어야만 모든 기능을 쓸 수 있다.