side project

[준비]하이브리드앱 환경 설정 With JAVA

UR'im 2021. 12. 29. 20:10

🌺앱의 변화, 다양한 앱의 형태🌺


Native APP

네이티브 앱은 우리가 흔히 말하는 어플리케이션으로 모바일 기기에 최적화된 언어로 개발된 앱으로 안드로이드 SDK를 이용해 자바언어로 만드는 앱과 IOS 기반의 SDK를 이용해 스위프트로 만드는 대부분의 앱이 여기 속한다고 할 수 있다.

장점

  • 성능이 웹앱, 하이브리드 앱에 비하여 가장 높다.
  • 네이티브 API를 호출하여 사용함으로써 플랫폼과 밀착되어있다.
  • 해당 언어에 익숙한 사용자라면 좀 더 쉽게 접근할 수 있다.

단점

  • 플랫폼에 한정적이다.
  • 해당 플랫폼에서 요구하는 언어에 제약적인다. 따라서 해당 언어와 플랫폼의 API를 다루는데 익숙해야 한다.
  • 변화를 빠르게 적용할 수 없다.

모바일 웹앱

웹앱은 모바일 웹과 네이티브 앱을 결합한 형태로 모바일 웹의 특징을 가지면서 네이티브앱의 장점을 가지고 있다.

여기서 모바일 웹은 모바일에서 PC용 사이트의 글자 폰트와 이미지, 터치 아이콘, 플래시 등 데스크탑 브라우저에서 실행되는 기능을 모바일에 맞추어 표현한 사이트를 의미한다. 정리하자면, PC용 홈페이지를 모바일 스크린의 크기에 맞춰 줄여 놓은 것이라고 생각하면 된다.

장점

  • 웹사이트를 보는 것이기 떄문에 따로 설치할 필요가 없다.
  • 모든 기기와 브라우저에서 접근할 수 있다.
  • 별도 설치 및 승인과정이 필요하지 않아 유지 보수가 용이하다.

단점

  • 플랫폼 API(카메라, 블루투스 등)를 사용할 수 없다. 오로지 브라우저 API만을 사용할 수 있다.
  • 친화적인 터치앱을 개발하기가 약간 번거로운점이 있다.
  • 브라우저를 열고 검색해 들어가야하는 플로우로 인해 네이티브, 하이브리드 앱보다 실행이 까다롭다.

하이브리드 앱

하이브리드 앱은 기본적으로 '네이티브앱+웹앱'이라고 생각하면 쉽다. 일반적으로 네이티브웹에서 웹뷰를 띄어 웹앱을 실행시키는 것이 보편적이다.

장점

  • 네이티브 API와 브라우저 API를 이용한 다양한 개발이 가능하다.
  • 웹 개발 기술을 사용해 앱을 개발할 수 있다.
  • 한번의 개발로 다수의 플랫폼에 대응할 수 있다.

단점

  • 네이티브 기능에 접근하기 위해선 네이티브 개발 지식이 필요하다.
  • 웹뷰에서 앱을 실행한는 경우이기 때문에 앱의 성능이 곧 브라우저의 성능이다.
  • UI 프레임워크 도구를 사용하지 않는다면 개발자가 UI를 제작해야한다.

하이브리드 앱을 만드는 방법


첫번째, Vue.js 프로젝트 시작하기.

간단한 vue.js 프로젝트를 만들고, command line 아래의 명령어를 입력한다.

npm run serve 

http://localhost:8080http://10.0.0.188:8080/ 같이 두개의 주소가 나타나면 성공적으로 실행 된 것이다.
10.0.0.188 은 IP 주소로 모두 다르게 나타난다.

두번째, JS 페이지를 호출하는 방법

https://developer.android.com/reference/android/webkit/WebView

  • webview.loadUrl()
    • void loadUrl(String url) : url을 받아 호출한다.
    • void loadUrl(String url, Map<String, String> additionalHttpHeaders) : 맵으로써 이름,값으로 명시된 추가 HTTP 헤더와 함께 URL을 로드한다.
  • webview.evaluateJavascript()
    • void evaluateJavascript(String script, ValueCallback<String> resultCallback) : 현재의 전시된 페이지의 컨텍스트의 자바스크립트를 비동기적으로 나타낸다.

세번째, JS 와 WEBVIEW 통신하기

JS ⇒ 안드로이드로 통신하기

  • WebVIewBridge 클래스

https://developer.android.com/reference/android/webkit/WebView#addJavascriptInterface(java.lang.Object,%20java.lang.String)

안드로이드 내부 통신을 위한 통신 메소드가 담긴 클래스를 정의한다.

class WebViewBridge {
    @JavascriptInterface
    public void showMessage(String message) {
        console.log(message);
	  }
}

addJavascriptInterface 함수를 통하여 웹뷰에 정의한 클래스를 추가시켜준다.

WebView webView = (WebView)findViewById(R.id.webview);
webView.addJavascriptInterface(new WebViewBridge, "WebViewBridge");

안드로이드 웹뷰에 띄어진 vue.js는 아래와 같이 정의된 인터페이스를 사용할 수 있다.

//method 정의

const sendMessage = (message) => {
  const userAgent = navigator.userAgent.toLowerCase(); // 현재 사용하는 기기가 무엇인지 정보를 얻는 다.
  
  if (userAgent.indexOf('android') !== -1) {
    return WebViewBridge.showMessage(message);
  } else if (userAgent.indexOf('iphone') !== -1 || userAgent.indexOf('ipad') !== -1) {
    return window.webkit.messageHandlers.webViewMessageHandler.postMessage(message);
  } else { // 안드로이드, IOS 가 아닌 경우 (더 조건을 추가해서 처리해도 됨)
     return window.opener.postMEssage(message);
  }
}

안드로이드 ⇒ JS 통신하기

https://im-designloper.tistory.com/19

안드로이드에서 웹뷰를 열 때, url에 데이터 값을 붙혀서 호출 한다.

WebView web = (WebView)findViewById(R.id.webview01); //WebView 생성
web.getSettings().setJavaScriptEnabled(true); //WebView 자바스크립트 true

web.loadUrl("<http://ip주소:8080/MyServer/member_join.jsp?id=>"+id); 

호출 된 vue.js는 url에서 파라미터 값을 추출한다.
1. $route.params

**https://hello-bryan.tistory.com/search/hello/20**

router 설정

{
		path:'/search/:text/:size',
		components: {
				header: Header,
				default: searchBody,
				footer: Footer
		}
}
<template>
	{{ $route.params }} <!-- {text: 'hello', size: '20'} -->
	{{ $route.params.text }}
	{{ $route.params.size }}
</template>

2. $route.query
파라메터값이 ?로 이뤄져 있을 때

https://hello-bryan.tistory.com/search**?text**=hello**&size**=30

router 설정

{
    path: '/search',
    components: {
      header: Header,
      default: SearchBody,
      footer: Footer
    }
 }
<template>
  {{ $route.query.text }} <!-- hello -->
  {{ $route.query.size }} <!-- 20 -->
</template>

Vue.js 공식 문서

https://v3.ko.vuejs.org/api/composition-api.html#라이프사이클-훅-lifecycle-hooks

Vue.js의 화면 전환


Vue Router를 이용한 SPA 구현하기

SPA란?

single page application의 약자로, 전통적인 Multi Page Application 방식이 아닌 앱에 최적화된 성능을 구현할 수 있는 방법을 말한다.
SPA를 구현하기 위한 고려사항

  • 클라이언트 사이드에서 히스토리를 관리하는 페이지 이동(라우팅 관리)
  • 비동기로 데이터 받아오기
  • 뷰 렌더링
  • 모듈화된 코드 관리

→ 라우팅 설정에 따라 URL 별로 특정 컴포넌트를 선택적으로 표시하는 방법으로 페이지 이동을 구현할 수 있다.

Vue Router란?

Vue에서 SPA를 구현 시, 사용자 요청 경로에 따라 해당하는 컴포넌트에 매핑하여 렌더링을 결정해주는 플러그인이 Vue Router이다.
원래 사용자의 URI Request가 들어오면 서버( 백엔드 단) 에서 Controller가 그 경로에 따라 해당하는 Response로써 정적파일(html, css, js 등)을 보내준다.
Vue Router는 프론트엔드에서 요청 URI에 따라 전체 새로운 돔을 변경하는 것이 아니라, 브라우저에 변화가 있는 부분의 돔을 변경하는 방식이다.

Vue Router의 기능

https://router.vuejs.org/kr/ (Vue Router 공식 문서)

  • 중첩된 라우트/뷰 매핑
  • 모듈화된, 컴포넌트 기반의 라우터 설정
  • 라우터 파라미터, 쿼리, 와일드카드
  • Vue.js의 트랜지션 시스템을 이용한 트랜지션 효과
  • 세밀한 네비게이션 컨트롤
  • active CSS 클래스를 자동으로 추가해주는 링크
  • HTML5 히스토리 모드 또는 해시 모드(IE9에서 자동으로 폴백)
  • 사용자 정의 가능한 스크롤 동작

Vue.js 시작하기


Vue 프로젝트 실행

npm install i -g @vue/cli #3.x 버전
cd myDirectory
vue create <projectname>
.
.
.
#Y누르고 엔터쳐주기

cd <projectname>

yarn serve

#결과
DONE  Compiled successfully in 42ms                                                             오후 6:07:29

  App running at:
  - Local:   <http://localhost:8081/> 
  - Network: <http://192.168.35.216:8081/>

Vue router 설치

npm install vue-router 
#정상적인 설치가 된다면 router 폴더가 생성된다. 정상적으로 설치가 되지 않아 생성되지 않았다면, 
vue add router #vue의 힘을 빌려 router을 설치한다.

여기서 router > **index.js**는 Django에서 **urls.py**와 같은 역할을 한다.
vue.js 프로젝트를 설치하는 방법이 vue와 vue/cli를 설치하는 등 다양한 방법이 있다. 만약 vue/cli 명령어를 통해 설치할 경우 시작 페이지는 App.vue 파일이고, 다른 방법일 경우 public 폴더의 index.html 파일이 시작 파일이 될 수 있다.

경로 추가 하기

  • views > Page1.vue, Page2.vue
//Page1.vue
<template>
    <div>
        <h1>Page1</h1>
        //<button><router-link to='/page2'>go to page2</router-link></button>
    </div>
</template>
//Page2.vue
<template>
    <div>
        <h1>Page2</h1>
        //<button><router-link to='/page1'>go to page1</router-link></button>
    </div>
</template>

우선 vue 파일을 생성하여 모듈화 한다.

  • router > index.js
import Page1 from '../views/Page1.vue'
import Page2 from '../views/Page2.vue'

const routers = [
	{
		path: '/page1',
    	name: 'Page1',
    	component: Page1,
	},
	{
		path: '/page2',
    	name: 'Page2',
    	component: Page2,
	}
]

경로를 설정하는 index.js에 page1, page2에 대한 경로를 명시해준다.
이후 vue에 주석 처리된 부분을 지워준다.
이 화면은 매우 간단하게 router-view 태그 한줄로부터 화면이 변경된다. → App.vue 파일에 존재한

안드로이드 웹뷰에 vue 띄우기


안드로이드단 수정

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.webviewtest" android:targetSandboxVersion="1"> <!--version이 2 이면 http url 로드하지 못한다.-->
    <uses-permission android:name="android.permission.INTERNET"/>
    <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" <!--http 허용하기 위해서는 추가해야한다.-->
        android:theme="@style/Theme.WebviewTest">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/Theme.WebviewTest.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

MainActivity.class

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
	onCheckPermission();

	WebView webView = (WebView)findViewById(R.id.mainWebview);
	**webView.getSettings().setJavaScriptEnabled(true); //1**
	webView.loadUrl("<http://172.30.1.25:8080/>"); //2
	**webView.addJavascriptInterface(new WebViewBridge(),"WebViewBridge");** //3
}

#1, webView.getSettings().setJavaScriptEnabled(true);
이와 같은 코드를 추가함으로써 웹뷰 사용시 자바스크립트를 사용할 수 있게 한다.
만약 이 코드가 없다면 vue의 index.html 파일의 noscript 태그가 실행 됨을 볼 수 있다.
#2. loadUrl 호출 시 localhost:~ 형식의 주소값을 입력하면 사용하지 못한다.
#3. webView.addJavascriptInterface(new WebViewBridge(),"WebViewBridge");
addJavascriptInterface(@JavascriptInterface이 명시된 클래스 , js에서 호출할 이름 );
왼쪽 값에는 웹에서 안드로이드로 호출할 함수들이 나열된 클래스를 삽입한다. ex) new WebViewBridge
오른쪽에서는 웹 js에서 사용할 수 있도록 이름을 명시해준다.

Vue.js와 안드로이드 함수 통신하기


Vue.js단 코드보기

<template>
    <div>
        <input v-model="userData" @input="changeId" id="user_id" placeholder="아이디를 입력하세요."/>
        <p>id : {{userData}}</p>
        <button v-on:click="sendMessage(userData)">정상 회원 확인하기</button>
        <p>결과 : {{callResult}}</p>
    </div>
</template>

<script>
export default {
    data: function() {
        return {
            userData: '',
            callResult: ''
        }
    },
    methods: {
        sendMessage(message) {
            const userAgent = navigator.userAgent.toLowerCase();
            if (userAgent.indexOf('android') !== -1) {
                this.callResult = window.WebViewBridge.callbackUserData(message);
            } else if (userAgent.indexOf('iphone') !== -1 || userAgent.indexOf('ipad') !== -1) {
                return window.webkit.messageHandlers.webViewMessageHandler.postMessage(message);
            } else { // 안드로이드, IOS 가 아닌 경우 (더 조건을 추가해서 처리해도 됨)
                return window.opener.postMEssage(message);
            }
        },
        changeId(e) { //만약 input 태그값을 변경해서 P 태그 값이 변경되지 않는 것을 방지
            this.userData = e.target.value;
        }

    } 
}
</script>

OpenSSL 설정하기


안드로이드 규정상, 인증서가 붙어있지 않은 url을 열지 못한다. 따라서 인증서를 만들어 vue에 적용시켜준다.

ROOT CA 인증서 생성

openssl로 root ca의 개인키와 인증서를 만들자.

  1. CA가 사용할 RSA 생성 (2048bit) 개인키 생성
openssl genrsa -aes256 -out rootca.key 2048

chmod 600 rootca.key

개인키 분실에 대비해 AES256bit로 암호화한다. AES 이므로 암호(pass phrase)를 분실하면 개인키를 얻을 수 없으니 꼭 기억해야 한다. *보안 경고 : 개인키 유출 방지를 위해 소유자만 읽을 수 있도록 group과 other의 permission을 모두 제거하는게 좋다.

  1. CSR(Certificate Signing Request) 생성을 위한 openssl 설정 파일을 만들고 rootca_openssl.conf로 저장한다.

rootca_openssl.conf

[ req ]
default_bits            = 2048
default_md              = sha1
default_keyfile         = lesstif-rootca.key
distinguished_name      = req_distinguished_name
extensions             = v3_ca
req_extensions = v3_ca
 
[ v3_ca ]
basicConstraints       = critical, CA:TRUE, pathlen:0
subjectKeyIdentifier   = hash
##authorityKeyIdentifier = keyid:always, issuer:always
keyUsage               = keyCertSign, cRLSign
nsCertType             = sslCA, emailCA, objCA
[req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = KR
countryName_min                 = 2
countryName_max                 = 2

# 회사명 입력
organizationName              = Organization Name (eg, company)
organizationName_default      = lesstif Inc.
 
# 부서 입력
#organizationalUnitName          = Organizational Unit Name (eg, section)
#organizationalUnitName_default  = Condor Project
 
# SSL 서비스할 domain 명 입력
commonName                      = Common Name (eg, your name or your server's hostname)
commonName_default             = lesstif's Self Signed CA
commonName_max                  = 64

Root CA용 CSR 요청 파일을 생성

  1. 인증서 요청
openssl req -new -key rootca.key -out rootca.csr -config rootca_openssl.conf

10년짜리 셀프 사인 인증서를 생성한다. 인증서는 -out 옵션 뒤에 기술한 파일명으로 생성된다.

openssl x509 -req -days 3650 \\
-extensions v3_ca \\
-set_serial 1 \\
-in rootca.csr \\
-signkey rootca.key \\
-out rootca.crt \\
-extfile rootca_openssl.conf

정상적으로 생성되었는지 확인하기 위해 인증서 정보를 출력해본다.

openssl x509 -text -in rootca.crt

SSL 인증서 발급

위의 root ca 서명키로 ssl 인증서를 발급해보자

키 쌍 생성

  1. SSL 호스트에서 사용할 RSA key pair 생성 2048bit 개인키 생성
openssl genrsa -aes256 -out test.key 2048 #키 이름은 보통 도메인 이름으로 지정한다.
  1. Remove Passphrase from key

개인키를 보호하기 위해 Key-Derived Function 으로 개인키 자체가 암호화되어 있다. 인터넷 뱅킹등에 사용되는 개인용 인증서는 당연히 저렇게 보호되어야 하지만 SSL 에 사용하려는 키가 암호가 걸려있으면 웹 서버 구동때마다 pass phrase 를 입력해야 하므로 암호를 제거한다.

개인키 pass phrase 제거

cp  test.key  test.key.enc
openssl rsa -in  test.key.enc -out test.key

<aside> 💡 보안 경고 | 개인키의 유출 방지를 위해 group과 other의 permission을 모두 제거한다.
</aside>

chmod 600 test.key*

CSR 생성

  1. CSR 생성을 위한 openssl config 파일을 만들고 host_openssl.conf 라는 이름을 저장한다.

host_openssl.conf

[ req ]
default_bits            = 2048
default_md              = sha1
default_keyfile         = lesstif-rootca.key
distinguished_name      = req_distinguished_name
extensions             = v3_user
## 인증서 요청시에도 extension 이 들어가면 authorityKeyIdentifier 를 찾지 못해 에러가 나므로 막아둔다.
## req_extensions = v3_user

[ v3_user ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
authorityKeyIdentifier = keyid,issuer
subjectKeyIdentifier = hash
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
## SSL 용 확장키 필드
extendedKeyUsage = serverAuth,clientAuth
subjectAltName          = @alt_names
[ alt_names]
## Subject AltName의 DNSName field에 SSL Host 의 도메인 이름을 적어준다.
## 멀티 도메인일 경우 *.lesstif.com 처럼 쓸 수 있다.
DNS.1   = www.lesstif.com
DNS.2   = lesstif.com
DNS.3   = *.lesstif.com

[req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = KR
countryName_min                 = 2
countryName_max                 = 2

# 회사명 입력
organizationName              = Organization Name (eg, company)
organizationName_default      = lesstif Inc.
 
# 부서 입력
organizationalUnitName          = Organizational Unit Name (eg, section)
organizationalUnitName_default  = lesstif SSL Project
 
# SSL 서비스할 domain 명 입력
commonName                      = Common Name (eg, your name or your server's hostname)
commonName_default             = lesstif.com
commonName_max                  = 64

인증서 발급 요청(CSR) 파일 생성한다.

openssl req -new -key test.key -out test.csr -config host_openssl.conf

5년짜리 test용 SSL 인증서 발급 (서명시 ROOT CA 개인키로 서명)

openssl x509 -req -days 1825 -extensions v3_user -in test.csr \\
-CA rootca.crt -CAcreateserial \\
-CAkey  rootca.key \\
-out test.crt  -extfile host_openssl.conf

이것으로 인증서 발급을 마치겠다.

Vue + SSL


Vue 프로젝트에 SSL 인증서 붙히기

앞서 만든 SSL 인증서를 vue 프로젝트에 붙혀 준다.
다음 vue 프로젝트 바로 밑에 vue.config.js 파일을 생성해준다.

	touch vue.config.js # path = ./vue.config.js
const fs = require('fs');
module.exports = {
    devServer: {
        https: true,
        https: {
            key: fs.readFileSync('./test.key'),
            cert: fs.readFileSync('./test.crt'),
            ca: fs.readFileSync('./rootca.crt')
        }
    }
};

 

Android View 정리


상태바 + 액션바 없애기

res > values > styles.xml or themes.xml 파일을 열어준다. >> 나는 themes.xml 파일이 있음으로 이 파일을 열어준다.
values/themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.WebviewTest" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
    </style>

    <style name="Theme.WebviewTest.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

    <style name="Theme.WebviewTest.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

위의 코드 중 <style> 태그 안에 아래의 코드를 입력한다.

<item name="windowActionBar">false</item> <!-- 액션바 제거 -->
<item name="windowNoTitle">true</item>  <!-- 두번째 줄과 세번째 줄은 상태바 제거 -->
<item name="android:windowFullscreen">true</item>

values/themes.xml(night) 라고 적힌 파일에도 똑같은 코드를 입력해준다.

'side project' 카테고리의 다른 글

[준비] Kotlin으로 Vue.js 와 통신하기  (0) 2021.12.31
2020 한이음 공모전 후기  (0) 2021.01.19
한이음 개발완료 후기  (0) 2019.10.29