인스턴트 앱(Instant App) 샘플

진행할 항목

  1. 샘플 프로젝트 준비
  2. 인스턴트 앱 구조에 맞춰 프로젝트 변경
  3. 안드로이드 스튜디오에서 실행해보기
  4. Heroku 구성

1. 샘플 프로젝트 준비

Android Architecture Blueprints 프로젝트로 샘플 프로젝트 준비

git clone https://github.com/googlesamples/android-architecture.git
git checkout -t origin/todo-mvp

구글의 인스턴트 앱 예제를 보고 싶다면

https://github.com/googlesamples/android-instant-apps

2. 인스턴트 앱 구조에 맞춰 프로젝트 변경

가. 기존 app 모듈명을 base feature명으로 수정 (ex. base_module)

  • Project 창에서 모듈 선택 후 우클릭 Refactor - Rename - Rename module 선택 후 변경

  • app/build.gradle 수정 (apk_module 은 추가할 APK Module명) applicationId, applicationIdSuffix 항목 주석 처리 room schemaLocation 오류 수정을 위해 javaCompileOptions 추가

    // apply plugin: 'com.android.application'
    apply plugin: 'com.android.feature'
    ...
    android {
    	...
        baseFeature true
        ...
        defaultConfig {
            //applicationId "com.example.android.architecture.blueprints.todomvp"
            ...
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
                }
            }
        }
        ...    
        // If you need to add more flavors, consider using flavor dimensions.
        productFlavors {
            mock {
                dimension "default"
                //applicationIdSuffix = ".mock"
            }
            prod {
                dimension "default"
            }
        }
        ...
    }
        
    dependencies {
        application project(":apk_module")
    }
  • base_module/src/main/AndroidManifest.xml 수정

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.android.architecture.blueprints.todoapp">
    ...
    </manifest>

나. APK Module 추가 (ex. apk_module)

  • Module 추가 (File > New > New Module)

  • Phone & Table Module 선택

  • 모듈명 기입.(ex. apk_module)

    앱 이름 및 최소 SDK 버전 등을 확인해야 함.

  • Add No Activity 선택.

  • values/strings.xml 에서 app_name 리소스 주석 처리

    <resources>
        <!--<string name="app_name">APK Module</string>-->
    </resources>
  • apk_module에 base_module/proguard-rules.pro와 proguardTest-rules.pro를 복사

  • apk_module/build.gradle 수정 buildTypes나 Flavors는 base_module에 있던 내용을 사용 base_module에서 주석처리한 applicationId, applicationIdSuffix 항목 사용

    android {
        ...
        defaultConfig {
            // applicationId "com.mycompany.apk_module"
            applicationId "com.example.android.architecture.blueprints.todomvp"
            ...
        }
        ...
        
        /*
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        */
        buildTypes {
            debug {
                minifyEnabled true
                // Uses new built-in shrinker http://tools.android.com/tech-docs/new-build-system/built-in-shrinker
                useProguard false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardTest-rules.pro'
            }
    
            release {
                minifyEnabled true
                useProguard true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardTest-rules.pro'
            }
        }
    
        flavorDimensions "default"
    
        // If you need to add more flavors, consider using flavor dimensions.
        productFlavors {
            mock {
                dimension "default"
                applicationIdSuffix = ".mock"
            }
            prod {
                dimension "default"
            }
        }
    
        // Remove mockRelease as it's not needed.
        android.variantFilter { variant ->
            if (variant.buildType.name == 'release'
                    && variant.getFlavors().get(0).name == 'mock') {
                variant.setIgnore(true)
            }
        }
    
        // Always show the result of every unit test, even if it passes.
        testOptions.unitTests.all {
            testLogging {
                events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
            }
        }
    }
    ...
    dependencies {
    	implementation project(':base_module')
    }
  • apk_module/src/main/AndroidManifest.xml 수정

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.nobasedev.apk_module">
    </manifest>
  • gradle sync 중 아래 에러 로그시

    Manifest merger failed : Attribute application@allowBackup value=(true) from AndroidManifest.xml:5:9-35
    	is also present at [:base_module] AndroidManifest.xml:28:9-36 value=(false).
    	Suggestion: add 'tools:replace="android:allowBackup"' to <application> element at AndroidManifest.xml:4:5-10:43 to override.

    apk_module/src/main/AndroidManifest.xml의 allowBackup 옵션을 base_module과 동일하게 설정

        <application
            android:allowBackup="false"
            ... />
  • gradle sync 후 apk_module 빌드 및 run 시켜서 동작 확인 아래와 같은 에러들이 발생할 경우 있음.

    Compilation failed; see the compiler error output for details.
    error: constant expression required

    보통 아래와 같은 구문에서 오류로 base_module로 변경된 부분들에서 주로 발생함. 이유는 안드로이드 리소스 id를 switch ~ case 문으로 비교 처리하는 부분인데, base_module로 변경되며 기존 application 모듈이 feature 모듈로 변경되었고, 이에 R.java 값들이 final로 선언되지 않기 때문에 if ~ else로 변경해야 함. (라이브러리 프로젝트에서 주로 발생하는 문제)

    switch문 앞에 커서를 놓고 alt + Enter 후 Replace 'switch' with 'if' 선택

다. Instant App Module 만들기

  • Module 추가 (File > New > New Module)

  • Instant App 선택

  • 모듈명 기입. (ex. instant_module)

  • instantapp 모듈의 build.gradle에 아래와 같이 추가

    apply plugin: 'com.android.instantapp'
    
    android {
        flavorDimensions "default"
        productFlavors {
            prod {
                dimension "default"
            }
        }
    }
    
    dependencies {
        implementation project(':base_module')
    }
  • Instant App에서 사용 가능한 권한 확인

    • ACCESS_COARSE_LOCATION
    • ACCESS_FINE_LOCATION
    • ACCESS_NETWORK_STATE
    • BILLING
    • CAMERA
    • INSTANT_APP_FOREGROUND_SERVICE (API level 26 or higher)
    • INTERNET
    • READ_PHONE_NUMBERS (API level 26 or higher)
    • RECORD_AUDIO
    • VIBRATE

3. 안드로이드 스튜디오에서 실행해보기

  • base_module 매니페스트에 인스턴트 앱 구동할 URL 정의 추가

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.example.android.architecture.blueprints.todoapp">
        ...
        
            <activity
                android:name="com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity"
                android:theme="@style/AppTheme.OverlapSystemBar">
                ...
                <intent-filter android:autoVerify="true">
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data
                        android:scheme="http"
                        android:host="todoapp.com"
                        android:pathPrefix="/tasks" />
                    <data android:scheme="https" />
                </intent-filter>
            </activity>
        	...    
            <activity
                android:name="com.example.android.architecture.blueprints.todoapp.statistics.StatisticsActivity"
                android:parentActivityName=".tasks.TasksActivity"
                tools:ignore="UnusedAttribute">
                ...
                <intent-filter android:autoVerify="true">
                    <action android:name="android.intent.action.VIEW" />
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
                    <data
                        android:scheme="http"
                        android:host="todoapp.com"
                        android:pathPrefix="/stats" />
                    <data android:scheme="https" />
                </intent-filter>
            </activity>    
  • Run메뉴의 Edit Configurations... 선택

  • Android App의 instant_module 선택 후 Launch Options의 URL 항목 입력

  • Run 하여 동작 확인.

  • apk_module과 instant_module의 차이는 멀티태스킹 화면에서 확인 가능 (인스턴트 앱의 경우 앱 아이콘 우측 하단의 번개 표시됨)

4. Heroku 구성

  • Heroku 회원가입 및 로그인

  • New - Create new app 선택

  • App name 지정 (ex. testinstant) 후 Create app버튼 클릭

  • Settings 탭 메뉴에서 Buildpacks 메뉴의 Add buildpakc 선택

  • nodejs 선택 후 Save changes 버튼 클릭

  • Deploy 탭 메뉴에서 Heroku Git 선택 후 하단의 설명대로 git 설정

  • Heroku CLI 설치 (https://devcenter.heroku.com/articles/heroku-command-line)

  • 로컬에 저장할 폴더 생성 (ex. heroku_todo_sample) 후 그 경로에서 cmd 띄움.

  • 아래 명령으로 로그인 시도 및 완료

    > heroku login
  • git 초기화

    > git init
    > heroku git:remote -a testinstant
  • nodejs 테스트 코드 작성

    • package.json

      {
        "name": "express-tutorial",
        "version": "1.0.0",
        "dependencies": 
        {
          "express": "~4.13.1",
          "ejs": "~2.4.1"    
        }
      }
    • Procfile

      web: node index.js
    • index.js

      var express = require('express')
      var app = express()
      
      app.set('port', (process.env.PORT || 5000))
      app.use(express.static(__dirname + '/public'))
      app.set('views', __dirname + '/views');
      app.set('view engine', 'ejs');
      app.engine('html', require('ejs').renderFile);
      
      app.get('/', function(request, response) {  
      	console.log("\n *index START* \n");
      	response.send('it\'s index');
      	console.log("\n *index EXIT* \n");
      })
      
      app.get('/.well-known/assetlinks.json',function(request, response){
      	//response.render('assetlinks.json');
      	var fs = require("fs");
      	console.log("\n *START* \n");
      	var content = fs.readFileSync("assetlinks.json");
      	response.setHeader('Content-Type', 'application/json');
      	response.send(content);
      	console.log("Output Content : \n"+ content);
      	console.log("\n *EXIT* \n");
      });
      
      app.listen(app.get('port'), function() {
        console.log("Node app is running at localhost:" + app.get('port'))
      })
  • assetlinks.json 만들기 참고 : https://developers.google.com/digital-asset-links/tools/generator 위 링크에서 Hosting site domainApp package nameApp package fingerprint (SHA256) 등을 입력하고 GENERATE STATEMENT 버튼을 누르면 아래와 같은 형태를 출력해주며, 그 내용을 assetlinks.json 파일로 만들면 됨.

    [{
      "relation": ["delegate_permission/common.handle_all_urls"],
      "target": {
        "namespace": "android_app",
        "package_name": "com.sample.app",
        "sha256_cert_fingerprints":
        ["75:D1:12:1A:85:EC:C7:2E...53:3E:84:E3:8C:0B"]
      }
    }]

    그리고 TEST STATEMENT 버튼을 눌러서 실제 서버에 json파일이 있는지 확인해볼 수 있음. (아래 과정 진행 후 테스트)

  • git commit 및 push (push시 Heroku에서 자동으로 deploy됨)

    > git add .
    > git commit -am "just do it"
    > git push heroku master

    위 과정 중 git push 안될 경우 (회사 보안으로 안되는 경우가 있는듯) 아래 명령 실행 후 token 복사

    > heroku auth:token
    ed2e5b99-97bc-...

    다시 git push heroku master 시도 후 Windows 보안 팝업이 뜨면 사용자 이름 없이 암호만 token 입력 후 확인하면 됨. 

  • 브라우저에서 아래 링크들 확인

  • base_module 매니페스트에서 정의한 URL 인텐트 필터들의 URL 주소를 수정함.

5. 주의할 점

Google Play 배포시 인스턴트 앱을 따로 배포함.

안드로이드 스튜디오의 Buid - Generate Signed APK... 메뉴에서 instant_module을 선택하여 생성함.

인스턴트 앱용 파일은 apk가 아니라 zip 형태로 나오며, 이를 Google Play 인스턴트 앱 배포 메뉴에서 올림.

실제 배포 요청시 base_module 매니페스트에 정의된 인텐트 필터의 URL 경로를 확인함.

해당 URL의 ./well-known/assetlinks.json이 확인 안된다면 배포가 안됨.

배포 후에도 단말마다 되는 경우가 있고, 안되는 경우가 있어 확인이 필요함.

+ Recent posts