Android OS 9 Pie

참고 : https://www.android.com/versions/pie-9-0/

Accessibility

  • Accessibility Menu : 스크린샷 찍기나 한손 탐색 같은 일반 동작들이 장애가 있는 사용자에게 더 쉬워짐.
  • Select to Speak : 카메라에서 OCR 지원하여 텍스트 선택 및 읽기 지원
  • Sound amplifier : 다양한 환경에서의 오디오 기능 향상을 위해 100개 이상의 설정을 할 수 있음.

Battery & Brightness

  • Battery Saver : Always-0n 디스플레이와 같은 기능을 끄고 충전시간 유지 가능.
  • Adaptive Battery : machine learning을 통해 사용할 앱과 그렇지 않을 앱을 예측하여 배터리 전원을 사용.
  • Adaptive Brightness : 다양한 조명 환경에서 화면 밝기 설정하는 방법을 배워서 자동으로 조정해줌.
  • Background restrictions : 배터리 사용량이 너무 많은 특정 앱을 제한하는 권장사항이 표시되어 배터리를 효율적으로 제어함. 

Camera

  • Multi-camera 지원
  • External camera 지원 (USB/UVC)

Digital Wellbeing

  • Do Not Disturb : 모든 알림과 시각적 기능이 침묵. 별표 표시된 연락처는 전달됨.
  • App dashboard : 폰에서 보낸 시간들을 확인 및 앱 사용 빈도와 알림 수르 확인.
  • Wind Down : 일일 일정을 설정하여 폰을 잠자게 함. 회색으로 화면을 표시하고 알림을 조용히 시킴.
  • App timers : 앱의 일일 시간 제한을 설정하여 한도에 도달시 앱이 일시 중지됨. 

Display

  • Display cutout : 컷 아웃이 있는 장치 지원함.
  • Edge-to-edge screens : 18:9 이상의 비율이나 컷 아웃이 있는 장치를 지원함. 

Enterprise

  • Multiple users on dedicated devices : 교대 근무자나 공용 키오스크에 적합한 단일 장치를 쉽게 공유할 수 있음.
  • Work tab in launcher : 작업용 앱을 시각적으로 분리 후 작업 탭을 탭하여 한 곳에서 볼 수 있음
  • Postpone Over-the-air(OTA) updates : 엔터프라이즈 IT 관리자가 Android OS 업데이트를 못하도록 최대 90일의 동결 기간을 정의할 수 있음.

Media

  • Multiple Bluetooth connections : 최대 5개의 장치에 연결하고 수신 전화는 가능한 모든 장치로 전송됨.
  • Sound delay reporting : 해당 기능이 있는 헤드셋을 지원하여 비디오와 헤드셋의 오디오 동기화 상태를 유지함.
  • Volume memory per Bluetooth device : 각 장치에 대해 마지막으로 설정한 불륨을 기어함.
  • HDR(High Dynamic Range) : HDR VP9 프로파일 2에 대한 지원 내장. 유튜브 등에서 HDR 지원 영화 볼 수 있음.
  • HD Audio : 향상된 성능과 HD 오디오 지원.
  • HEIF : HEIF 사진을 지원함.

Notifications

  • Notification enhancements for messaging : 메시징 앱 알림에 smart replies를 제안할 수 있어 탭으로 회신 가능함. 이동한 경우 모든 인라인 답장 초안이 안사라지며, 알림에서 받은 이미지를 볼 수 있음.
  • Manage Notifications : 앱에서 알림을 신속하게 끌 수 있음.

Privacy & Security

  • Android Backups : 클라이언트 측 비밀번호(기기 PIN, 패턴 또는 비밀번호)로 백업 암호화 가능.
  • Android biometric prompt : 표준 생체 인증 프롬프트를 포함하고, 다양한 새로운 보안 기능을 도입함.
  • Android Protected Confirmation : 호환 가능한 하드웨어에서 앱은 보안 하드웨어가 제어하는 UI를 사용하여 결제와 같은 민감한 거래의 확인을 받을 수 있음.
  • StrongBox : 호환 가능한 하드웨어에서 앱은 개인 키를 보호하기 위해 변조 방지 하드웨어를 활용할 수 있음.
  • Privacy enhancements : 앱이 idle이나 백그라운드에서 실행 중일때 폰의 마이크, 카메라, 기타 센서에 대한 액세스를 제한함. (앱이 센서에 액세스해야 하는 경우 폰에 알림 표시됨.) 모든 웹 통신을 보호하고 개인 웹 서핑을 제공하는 중요한 개선 기능을 제공함.

System Usability Enhancements

  • At-a-Glance on Always-on-Display : 락스크린 및 Always-on-Display에서 캘린더 이벤트나 날씨를 볼 수 있음.
  • Redesigned Quick Settings : 모든 토글을 포함한 퀵 셋팅의 보다 일관된 사용자 환경. 시각 디자인 업데이트 및 설명문구 추가.
  • Volume controls : 더 간단하고 볼륨 컨트롤을 통해 미디어/통화/알림 불륨 등을 바로 제어할 수 있음.
  • Screenshots : 파워메뉴에서 스크린 샷을 쉽게 가져와서 빠르게 그리기, 주석달기, 짜르기 등을 할 수 있음.
  • Rotation : 방향을 고정한 경우에도 기기의 회전을 언제 변경할 것인지 확인하는 간단한 버튼으로 제어 가능.
  • New system navigation : 앱 간 이동이나 검색을 보다 쉽게 만듦. 어디서든 위로 스와이프시 최근 사용한 앱의 전체화면 프리뷰를 볼 수 있음. 
  • App Actions : 사용자의 맥락을 기반으로 다음 원하는 것을 예측하여 표시해줌. 
  • Slices : 좋아하는 앱의 반응형 조각을 구글 검색과 같은 다른 곳에서 볼 수 있음. 
  • Overview Selection : Overview 모드에서 선택한 텍스트나 이미지를 길게 누르면 선택한 항목 기반으로 한 작업을 볼 수 있음.


Android OS 9 Pie

참고 : https://www.android.com/versions/pie-9-0/

Accessibility

  • Accessibility Menu : 스크린샷 찍기나 한손 탐색 같은 일반 동작들이 장애가 있는 사용자에게 더 쉬워짐.
  • Select to Speak : 카메라에서 OCR 지원하여 텍스트 선택 및 읽기 지원
  • Sound amplifier : 다양한 환경에서의 오디오 기능 향상을 위해 100개 이상의 설정을 할 수 있음.

Battery & Brightness

  • Battery Saver : Always-0n 디스플레이와 같은 기능을 끄고 충전시간 유지 가능.
  • Adaptive Battery : machine learning을 통해 사용할 앱과 그렇지 않을 앱을 예측하여 배터리 전원을 사용.
  • Adaptive Brightness : 다양한 조명 환경에서 화면 밝기 설정하는 방법을 배워서 자동으로 조정해줌.
  • Background restrictions : 배터리 사용량이 너무 많은 특정 앱을 제한하는 권장사항이 표시되어 배터리를 효율적으로 제어함. 

Camera

  • Multi-camera 지원
  • External camera 지원 (USB/UVC)

Digital Wellbeing

  • Do Not Disturb : 모든 알림과 시각적 기능이 침묵. 별표 표시된 연락처는 전달됨.
  • App dashboard : 폰에서 보낸 시간들을 확인 및 앱 사용 빈도와 알림 수르 확인.
  • Wind Down : 일일 일정을 설정하여 폰을 잠자게 함. 회색으로 화면을 표시하고 알림을 조용히 시킴.
  • App timers : 앱의 일일 시간 제한을 설정하여 한도에 도달시 앱이 일시 중지됨. 

Display

  • Display cutout : 컷 아웃이 있는 장치 지원함.
  • Edge-to-edge screens : 18:9 이상의 비율이나 컷 아웃이 있는 장치를 지원함. 

Enterprise

  • Multiple users on dedicated devices : 교대 근무자나 공용 키오스크에 적합한 단일 장치를 쉽게 공유할 수 있음.
  • Work tab in launcher : 작업용 앱을 시각적으로 분리 후 작업 탭을 탭하여 한 곳에서 볼 수 있음
  • Postpone Over-the-air(OTA) updates : 엔터프라이즈 IT 관리자가 Android OS 업데이트를 못하도록 최대 90일의 동결 기간을 정의할 수 있음.

Media

  • Multiple Bluetooth connections : 최대 5개의 장치에 연결하고 수신 전화는 가능한 모든 장치로 전송됨.
  • Sound delay reporting : 해당 기능이 있는 헤드셋을 지원하여 비디오와 헤드셋의 오디오 동기화 상태를 유지함.
  • Volume memory per Bluetooth device : 각 장치에 대해 마지막으로 설정한 불륨을 기어함.
  • HDR(High Dynamic Range) : HDR VP9 프로파일 2에 대한 지원 내장. 유튜브 등에서 HDR 지원 영화 볼 수 있음.
  • HD Audio : 향상된 성능과 HD 오디오 지원.
  • HEIF : HEIF 사진을 지원함.

Notifications

  • Notification enhancements for messaging : 메시징 앱 알림에 smart replies를 제안할 수 있어 탭으로 회신 가능함. 이동한 경우 모든 인라인 답장 초안이 안사라지며, 알림에서 받은 이미지를 볼 수 있음.
  • Manage Notifications : 앱에서 알림을 신속하게 끌 수 있음.

Privacy & Security

  • Android Backups : 클라이언트 측 비밀번호(기기 PIN, 패턴 또는 비밀번호)로 백업 암호화 가능.
  • Android biometric prompt : 표준 생체 인증 프롬프트를 포함하고, 다양한 새로운 보안 기능을 도입함.
  • Android Protected Confirmation : 호환 가능한 하드웨어에서 앱은 보안 하드웨어가 제어하는 UI를 사용하여 결제와 같은 민감한 거래의 확인을 받을 수 있음.
  • StrongBox : 호환 가능한 하드웨어에서 앱은 개인 키를 보호하기 위해 변조 방지 하드웨어를 활용할 수 있음.
  • Privacy enhancements : 앱이 idle이나 백그라운드에서 실행 중일때 폰의 마이크, 카메라, 기타 센서에 대한 액세스를 제한함. (앱이 센서에 액세스해야 하는 경우 폰에 알림 표시됨.) 모든 웹 통신을 보호하고 개인 웹 서핑을 제공하는 중요한 개선 기능을 제공함.

System Usability Enhancements

  • At-a-Glance on Always-on-Display : 락스크린 및 Always-on-Display에서 캘린더 이벤트나 날씨를 볼 수 있음.
  • Redesigned Quick Settings : 모든 토글을 포함한 퀵 셋팅의 보다 일관된 사용자 환경. 시각 디자인 업데이트 및 설명문구 추가.
  • Volume controls : 더 간단하고 볼륨 컨트롤을 통해 미디어/통화/알림 불륨 등을 바로 제어할 수 있음.
  • Screenshots : 파워메뉴에서 스크린 샷을 쉽게 가져와서 빠르게 그리기, 주석달기, 짜르기 등을 할 수 있음.
  • Rotation : 방향을 고정한 경우에도 기기의 회전을 언제 변경할 것인지 확인하는 간단한 버튼으로 제어 가능.
  • New system navigation : 앱 간 이동이나 검색을 보다 쉽게 만듦. 어디서든 위로 스와이프시 최근 사용한 앱의 전체화면 프리뷰를 볼 수 있음. 
  • App Actions : 사용자의 맥락을 기반으로 다음 원하는 것을 예측하여 표시해줌. 
  • Slices : 좋아하는 앱의 반응형 조각을 구글 검색과 같은 다른 곳에서 볼 수 있음. 
  • Overview Selection : Overview 모드에서 선택한 텍스트나 이미지를 길게 누르면 선택한 항목 기반으로 한 작업을 볼 수 있음.


인스턴트 앱(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이 확인 안된다면 배포가 안됨.

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

인스턴트 앱(Instant App) 특정 웹 링크 연결

참고링크 :

https://developer.android.com/training/app-links/deep-linking

https://developer.android.com/training/app-links/verify-site-associations

https://developers.google.com/digital-asset-links/tools/generator

인스턴트 앱을 만들기는 했으나, 실제 Google Play 배포까지는 또 다른 문제들이 있음.

인스턴트 앱에서 사용할 URL의 실제 웹 링크 소유자 확인이 가능해야 등록이 가능함.

Google Digital Asset Link 참조 : https://developers.google.com/digital-asset-links/

앱 콘텐츠에 대한 딥 링크 만들기

링크 클릭시나 프로그래밍 요청으로 웹 URI 인텐트가 호출되면 안드로이드 시스템에서 아래 동작을 순차적으로 시도함.

  1. URI가 지정된 경우 URI 처리 가능한 사용자 기본 설정 앱을 실행
  2. URI를 처리할 수 있는 앱 실행
  3. 다이얼로그에서 선택 가능한 앱들에서 사용자가 선택

수신 링크를 위한 인텐트 필터 추가

<action> : ACTION_VIEW 인텐트 액션 지정.

<data> : 하나 이상의 <data> 태그 추가.

각 태그는 URI 형식을 나타내며, 최소한 android:scheme 속성이 포함되어야 함.

속성들을 이용하여 URI 유형을 구체화 할 수 있음. android:path, pathPattern, pathPrefix 등

<category> : BROWSABLE 카테고리 포함. Intent 필터가 웹 브라우저에서 접근하기 위해 필요함.

DEFAULT 카테고리도 포함. 앱이 암시적 인텐트에도 응답할 수 있음.

이 방법이 아니라면 인텐트가 앱 구성 요소명을 지정하는 경우에만 적용이 가능함.

<activity
    android:name="com.example.android.GizmosActivity"
    android:label="@string/title_gizmos" >
    <intent-filter android:label="@string/filter_view_http_gizmos">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with "http://www.example.com/gizmos” -->
        <data android:scheme="http"
              android:host="www.example.com"
              android:pathPrefix="/gizmos" />
        <!-- note that the leading "/" is required for pathPrefix-->
    </intent-filter>
    <intent-filter android:label="@string/filter_view_example_gizmos">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <!-- Accepts URIs that begin with "example://gizmos” -->
        <data android:scheme="example"
              android:host="gizmos" />
    </intent-filter>
</activity>

위 2개의 인텐트 필터는 <data> 요소에 의해서만 서로 다름.

동일한 필터에 여러 개의 <data> 요소를 포함할 수 있지만 고유한 URL을 선언하려는 경우 별도의 필터로 만들어야 함.

아래의 경우 https://www.example.com 이나 app://open.my.app 만 지원하는 것처럼 보임.

하지만 실제로는 app://www.example.com 이나 https://open.my.app 형식도 지원하게 됨.

<intent-filter>
  ...
  <data android:scheme="https" android:host="www.example.com" />
  <data android:scheme="app" android:host="open.my.app" />
</intent-filter>

보다 자세한 내용은 developer 문서를...

수신 인텐트에서 데이터 읽기

시스템이 인텐트 필터를 통해 액티비티를 시작한다면, 인텐트가 제공하는 데이터를 사용할 수 있음.

getData()나 getAction() 메소드를 호출하여 사용함. 일반적으로 onCreate()나 onStart() 시기에 사용함.

딥 링크 테스트

adb(Android Debug Bridge)를 통해서 액티비티 매니저 툴로 인텐트 필터 URI 지정한 앱을 테스트할 수 있음.

$ adb shell am start -W -a android.intent.action.VIEW -d <URI> <PACKAGE>
$ adb shell am start -W -a android.intent.action.VIEW -d "example://gizmos" com.example.android

Android 앱 링크 확인

안드로이드 앱 링크는 웹 URL이 앱의 특정 컨텐츠로 바로 연결되도록 해주는 특별한 타입의 딥 링크임.

안드로이드 앱 링크를 앱에 추가하려면 HTTP URL을 사용해 인텐트 필터를 정의하고,

앱과 웹 사이트 URL을 모두 소유하고 있는지 확인이 필요함.

시스템이 사용자의 URL 소유권을 확인하면 자동으로 해당 URL 인텐트를 앱으로 라우팅함.

앱과 웹 소유권을 확인하기 위한 단계

딥 링크와 앱 링크의 차이점

딥 링크는 앱의 특정 활동을 직접 입력할 수 있도록 하는 인텐트 필터임.

이러한 링크 중 하나를 클릭하면 사용자가 주어진 URL을 사용할 수 있는 여러 앱 중 하나를 선택할 수 있음.

안드로이드 앱 링크는 웹에 속한 것으로 확인된 웹 URL을 기반으로 하는 딥 링크임.

이 중 하나를 클릭하면 앱이 즉시 열리며, 앱 선택 화면이 나타나지 않음.

사용자가 링크 처리에 대한 선호도를 변경할 수도 있음.

Deep linksApp links
Intent URL schemehttphttps, or a custom schemeRequires http or https
Intent actionAny actionRequires android.intent.action.VIEW
Intent categoryAny categoryRequires android.intent.category.BROWSABLE and android.intent.category.DEFAULT
Link verificationNoneRequires a Digital Asset Links file served on you website with HTTPS
User experienceMay show a disambiguation dialog for the user to select which app to open the linkNo dialog; your app opens to handle your website links
CompatibilityAll Android versionsAndroid 6.0 and higher

앱 링크 확인 요청하기

앱의 링크 처리 확인을 활성화하려면 앱 매니페스트에서 아래를 포함하는 웹 URL 인텐트 필터 중 하나에서 android:autoVerify="true" 설정을 함.

  • <action> : android.intent.action.VIEW
  • <category> : android.intent.category.BROWSABLE, android.intent.category.DEFAULT
  • <data> : http 또는 https
<activity ...>

    <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="www.example.com" />
        <data android:scheme="https" />
    </intent-filter>

</activity>

android:autoVerify="true"가 인텐트 필터 중에 존재하는 앱일 경우,

Android 6.0 이상인 기기에 앱이 설치되면 시스템이 앱의 인텐트 필터에 있는 URL과 연결된 호스트를 확인함.

안드로이드 시스템은 위의 인텐트 필터의 각각의 고유한 호스트 이름에 대해 Digital Asset Links 파일을 쿼리함.

ex) https://hostname/.well-known/assetlinks.json

여러 호스트를 위한 앱 링크 지원시

시스템이 매니페스트의 모든 호스트에 대해 일치하는 Digital Asset Links 파일을 찾은 경우에만 앱이 지정된 URL 패턴의 기본 처리기로 설정됨.

<application>

  <activity android:name=”MainActivity”>
    <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="www.example.com" />
      <data android:scheme="https" />
    </intent-filter>
  </activity>
  <activity android:name=”SecondActivity”>
    <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="www.example.net" />
    </intent-filter>
  </activity>

</application>

위의 경우 https://www.example.com/.well-known/assetlinks.json 과 https://www.example.net/.well-known/assetlinks.json이 없을 경우 앱 인텐트 필터 확인이 실패함.

여러 서브 도메인을 위한 앱 링크 지원시

Digital Asset Links 프로토콜은 인텐트 필터의 서브 도메인을 유니크한 개별 호스트로 취급함.

서브 도메인이 호스트가 다를 경우 각 도메인에 유효한 assetlinks.json을 가져야 함.

<application>
  <activity android:name=”MainActivity”>
    <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="https" android:host="www.example.com" />
      <data android:scheme="https" android:host="mobile.example.com" />
    </intent-filter>
  </activity>
</application>

위의 경우 https://www.example.com/.well-known/assetlinks.json 와 https://mobile.example.com/.well-known/assetlinks.json가있어야 함.

호스트 명에 와일드카드(ex. *.example.com)를 선언한 경우 루트 호스트에 assethosts.json 파일을 게시해야 함.

<application>
  <activity android:name=”MainActivity”>
    <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="https" android:host="*.example.com" />
    </intent-filter>
  </activity>
</application>

위의 경우 https://example.com/.well-known/assetlinks.json 가 있어야 함.

웹 사이트 연결 선언

Digital Asset Links JSON 파일은 웹 사이트와 연결된 앱을 표시하고 앱 URL 인텐트를 확인하기 위해 게시함.

JSON 파일은 아래 필드들을 사용.

  • package_name : 앱의 build.gradle 에 선언된 앱 ID

  • sha256_cert_fingerprints : 앱의 서명 인증서의 SHA256 fingerprints. 아래 명령으로 얻을 수 있음.

    $ keytool -list -v -keystore my-release-key.keystore

    이 필드는 여러 fingerprints를 지원하며, 디버그와 프로덕션 빌드와 같은 버전의 앱을 지원하는데 사용함.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

웹 사이트를 여러 앱과 연결시

한 assetlinks.json 파일에서 여러 앱과 연결을 선언할 수 있음.

인스턴트 앱의 경우 각 웹사이트 도메인에 하나의 인스턴트 앱만 지정 가능

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.puppies.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
  },
  {
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.monkeys.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]

서로 다른 앱이 동일한 웹 호스트에서 여러 리소스에 대한 링크 처리 가능함.

ex) app1은 https://example.com/articles에 대한 인텐트 필터를 선언하고 app2는 https://example.com/videos에 대한 인텐트 필터를 선언 할 수 있음.

여러 웹 사이트를 단일 앱과 연결

여러 웹 사이트는 각가의 assetlinks.json 파일에서 동일한 앱을 선언할 수 있음.

아래는 app1과 example.com 및 example.net의 연결을 선언하는 예제임.

  1. https://www.example.com/.well-known/assetlinks.json

[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.mycompany.app1", "sha256_cert_fingerprints": ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"] } }]

https://www.example.net/.well-known/assetlinks.json 

```json
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
  "namespace": "android_app",
  "package_name": "com.mycompany.app1",
  "sha256_cert_fingerprints":  ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
}
}]

JSON 확인 파일 게시

아래 위치에 JSON 확인 파일이 게시해야 함.

https://domain.name/.well-known/assetlinks.json

  • assetlinks.json 파일의 content-type을 application/json 형식으로 제공.
  • assetlinks.json 파일은 앱의 인텐트 필터가 HTTPS를 데이터 스킴으로 선언하는지 여부와 관계없이 HTTPS 연결을 통해 액세스할 수 있어야 함.
  • assetlinks.json 파일은 리다이렉션(301 또는 302 리다이렉션 아님) 없이 액세스할 수 있어야 함. 로봇(robots.txt) 에 assetlinks.json파일 크롤링을 허용해야 함.
  • 앱 링크가 여러 호스트 도메인을 지원하는 경우 각 도메인에 assetlinks.json 파일을 게시해야 함.
  • 매니페스트 파일에 dev/test URL이 있는 앱을 공개하지 말 것. 예를 들어 공개 키를 사용하여 액세스 할 수 없는 경우(VPN에서만 액세스 할 수 있는 URL 등) 이러한 경우 빌드 variants를 구성하여 dev 빌드에 대한 다른 매니페스트 파일 생성.

앱 링크 테스트

앱 연결 기능 구현시 시스템이 앱과 웹 사이트를 연결하고 URL 요청을 처리할 수 있는지 확인.

기존 명령문 파일을 테스트 하려면 Statement List Generator and Tester 툴을 사용.

https://developers.google.com/digital-asset-links/tools/generator

확인한 호스트 리스트 확인

앱에서 확인해야 하는 관련 호스트 목록을 확인.

인텐트 필터에 다음 속성 및 요소가 포함된 모든 URL 목록을 만듬.

  • android:scheme 속성 값이 http나 https인 것
  • android:host 속성이 도메인 URL 패턴과 같이 있는 것
  • android.intent.action.VIEW 카테고리 요소
  • android.intent.category.BROWSABLE 카테고리 요소

이 목록을 사용하여 각 호스트 및 하위 도메인에 Digital Asset Links JSON 파일이 제공되는지 확인.

Digital Asset Links 파일 확인

각 웹 사이트에 대해 Digital Asset Links API를 사용하여 JSON 파일이 제대로 호스팅되고 있는지 확인.

https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://<<domain.name:optional_port>>&relation=delegate_permission/common.handle_all_urls

URL 인텐트 테스트

앱과 연결할 웹 사이트 목록 확인 후 호스팅된 JSON 파일이 유효하다는 것을 확인 후 기기에 앱 설치.

비동기 확인 프로세스 완료시까지 최소 20초 대기.

아래 명령을 사용하여 시스템에서 앱을 확인하고 올바른 처리 설정됐는지 확인.

adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "http://<<domain.name:optional_port>>"

링크 정책 확인

테스트 과정으로 링크 처리를 위한 현재 시스템 설정을 확인할 수 있음.

아래 명령을 사용하여 연결된 장치의 모든 앱에 대한 기존 링크 처리 정책 리스트를 가져옴.

adb shell dumpsys package domain-preferred-apps

또는 아래와 같이 함

adb shell dumpsys package d

시스템이 확인 프레세스를 완료할 수 있도록 앱 설치 후 적어도 20초 정도 필요할 수 있음

위 명령은 장치에 정의된 각 사용자 또는 프로필의 목록을 반환하며, 아래 형식의 헤더가 있음.

App linkages for user 0:

위 헤더에 따른 내용은 아래와 같이 나열되며, 도메인과 연결된 앱이 표시됨.

Package: com.android.vending
Domains: play.google.com market.android.com
Status: always : 200000002
  • Package : 매니페스트에 선언된 패키지 명으로 연결된 앱 표시
  • Domains : 앱이 웹 링크를 처리하는 호스트의 전체 목록 표시. 빈 칸으로 구분.
  • Status : 앱의 현재 링크 처리 설정 표시. 인증을 통과했고, 매니페스트에 android:autoVerify=true 가 포함된 앱은 always를 표시함. 이 이후의 16진수는 시스템의 사용자 앱 연결 환경설정에 대한 기록이며, 이 값이 확인이 성공했는지 여부를 나타내지는 않음.

테스트

앱의 인텐트 필터를 지정하고 앱 링크 기준을 충족하는 모든 웹 사이트로 앱을 인증할 수 있어야 함.

<application>

    <activity android:name=”MainActivity”>
        <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="https" android:host="www.example.com" />
            <data android:scheme="https" android:host="mobile.example.com" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="www.example2.com" />
        </intent-filter>
    </activity>

    <activity android:name=”SecondActivity”>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="account.example.com" />
        </intent-filter>
    </activity>

      <activity android:name=”ThirdActivity”>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="https" android:host="map.example.com" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="market" android:host="example.com" />
        </intent-filter>
      </activity>

</application>

플랫폼에서 위 목록에서 확인하려는 호스트 목록은 아래와 같음.

확인하지 않으려는 호스트 목록은 아래와 같음.

  • map.example.com (android.intent.category.BROWSABLE 없음)
  • market://example.com (http나 https 스킴이 없음.)


인스턴트 앱(Instant App)

참고링크 :

https://codelabs.developers.google.com/codelabs/android-instant-apps/index.html

https://developer.android.com/topic/google-play-instant/getting-started/create-base-feature-module

https://developer.android.com/topic/google-play-instant/getting-started/convert-to-instant-app

https://developer.android.com/topic/google-play-instant/guides/config-splits

인스턴트 앱이란?

https://developer.android.com/topic/instant-apps/overview?hl=ko

구글 I/O 2017에서 공개된 것으로 설치없이 앱의 일부 기능을 사용해보는 것.

Play Store, Google 검색, SNS 및 링크를 공유하는 모든 곳에서 인스턴트 앱을 사용할 수 있다 함.

Android 6.0 (API 23) 이상에서만 실행됨.

자주 묻는 질문(FAQ)

필요한 것들

앱을 인스턴트 앱에 맞춰 feature 모듈과 APK용 / Instant App용 모듈로 구성해야 함.

인스턴트 앱은 링크 기반으로 진입점을 작업하며, 인증을 위한 웹 도메인이 필요함.

(간단히 로컬 테스트는 실제 도메인 없어도 가능하나, 구글 플레이 배포시는 필요함.)

인스턴트 앱 구조 맞추기

기존 앱을 인스턴트 앱 구조에 맞춰 변경해보자.

기존 프로젝트의 기본 모듈을 base feature로 수정 후 이를 참조하여 APK, Instant App용 모듈을 추가한다.

기존 앱 base feature module로 변경

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

  2. base_module의 gradle 파일을 열어 아래와 같이 수정

    // apply plugin: 'com.android.application'
    apply plugin: 'com.android.feature'
    ...
    android {
        ...
        baseFeature true
        ...
    }

APK Module 만들기

  1. Module 추가 (File > New > New Module)

  2. Phone & Table Module 선택

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

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

  4. Add No Activity 선택.

Base Module과 APK Module 연결

  1. base_module의 gradle 파일을 열어 아래와 같이 수정 (applicationIdSuffix 가 있다면 주석 처리)

    android {
        ...
        defaultConfig {
            ...
            //applicationId "com.mycompany.example"
            ...
        }
    }
    ...
    dependencies {    
        ...
        application project(":apk_module")
        ...
    }
  2. apk_module의 gradle 파일을 열어 아래와 같이 수정

    android {
        ...
        defaultConfig {
            // applicationId "com.mycompany.example.apk_module"
            applicationId "com.mycompany.example"
            ...
        }
        ...
        dependencies {
            implementation project(':base_module')
        }
    }
  3. base_module의 AndroidManifest.xml 파일을 열어 아래와 같이 수정

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              package="com.mycompany.example">
    ...
    </manifest>
  4. apk_module의 AndroidManifest.xml 파일을 열어 아래와 같이 수정

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.mycompany.example.app">
    </manifest>

Instant App Module 만들기

  1. Module 추가 (File > New > New Module)

  2. Instant App 선택

  3. 모듈명 기입. (ex. instantapp)

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

    dependencies {
        implementation project(':base_module')
    }
  5. 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

Google Play에서 "Try Now" 을 위한 처리 추가

  1. base_module의 기본 액티비티 사용

  2. base_module의 기본 URL 생성

    <activity android:name="com.example.base_module.MainActivity" ... >
        <meta-data android:name="default-url"
                   android:value="https://example.com/welcome" />
    </activity>

인스턴트 앱 접근을 위한 URL 제공

  1. base_module의 AndroidManifest.xml 파일에서 액티비티에 URL 매핑 적용

    HTTP와 HTTPS 모두 지원해야 함

    <activity android:name="com.example.base_module.MainActivity" ... >
        <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="example.com"
                android:pathPrefix="/welcome" />
            <data android:scheme="https" />
        </intent-filter>
    </activity>
  2. 인스턴트 앱 실행시 진입할 액티비티의 해당 URL에 autoVerify 옵션 추가

    <activity android:name="com.example.base_module.MainActivity" ... >
         <intent-filter android:autoVerify="true">
             ...
         </intent-filter>
    </activity>

인스턴트 앱 테스트

  1. Android Studio 의 "Run" -> "Edit Configurations"
  2. instantapp 모듈 선택
  3. "General" 탭의 "Launch Options"에서 "Launch" 값을 "URL" 선택
  4. "General" 탭의 "Launch Options"에서 "URL" 값을 실행할 액티비티의 매핑 URL로 입력
  5. 멀티태스킹 화면 등에서 아이콘에 번개표시로 인스턴트 앱 확인 

인스턴트 앱 링크 연결

Android 앱 링크를 설정하여 앱에서 직접 링크의 특정 콘텐츠로 이동하는 용도로 Deep Link와 App Link가 사용됨.

Deep Link

앱의 특정 콘텐츠로 바로 연결하는 URL. 인텐트 필터를 추가하여 인텐트에서 데이터를 추출하여 처리함.

App Link

Android 6.0 (API 23) 이상에서 앱이 주어진 유형의 링크의 기본 처리기로 지정되도록 허용함.

  • 앱 링크는 소유하고 웹 사이트 도메인에 연결되는 HTTP URL을 사용하므로 다른 앱이 링크 사용 불가 (Google 웹 사이트 연결 방법 중 하나를 통해 도메인 소유권 확인 필요)
  • 웹 사이트와 앱의 동일한 콘텐츠에 대해 하나의 HTTP URL을 사용. 앱이 없을 경우 웹 사이트로 이동됨.
  • 인스턴트 앱 지원을 하기 위해 앱 링크 설정
  • 모바일 브라우저, Google 검색 앱, Android 화면 검색, Google Assistant를 통해 Google 에서 URL을 클릭하여 열 수 있음.

Android App Link 추가하기

  1. 특정 컨텐츠에 대한 딥링크를 앱에 생성. (위 내용 중 "인스턴트 앱 접근을 위한 URL 제공" 참조)
  2. 딥링크 확인 추가. 인스턴트 앱에서 사용할 URL의 실제 웹 링크 소유자 확인. (Google Search Console에서 소유자 확인한 사이트에 Digital Asset Links JSON 파일이 있어야 함.) (Developers 문서에는 위와 같지만, Google Search Console에 대한 부분은 안해도 인스턴트 앱이 되긴 했음.) 참조 : https://developer.android.com/training/app-links/


앱 APK에 포함된 버전 코드가 특정 권한(android.permission.CAMERA, android.permission.FLASHLIGHT)을 필요로 합니다. APK에서 이 권한을 사용하는 앱에는 개인정보처리방침 세트가 필요합니다.

android.permission.CAMERA
android.permission.FLASHLIGHT
기기의 Flash Light 제어 외에는 사용되지 않습니다.

인증을 위한 서버 접속 이외에 해당 프로그램은 개인정보를 서버로 전송하지 않습니다. 
서버에서는 개인정보를 저장하지 않습니다.


'Android개발' 카테고리의 다른 글

[Android][InstantApp] 인스턴트 앱 2  (0) 2018.07.24
[Android][InstantApp] 인스턴트 앱 1  (0) 2018.07.24
[Android] DataBinding Library 3  (0) 2018.06.12
[Android] DataBinding Library 2  (0) 2018.06.12
[Android] DataBinding Library 1  (2) 2018.06.12

데이터 바인딩 라이브러리 3

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

데이터 객체

데이터 바인딩에 임의의 POJO(Plain Old Java Object)를 사용 가능하지만, POJO를 수정하더라도 UI가 업데이트 되지는 않음. 데이터가 변경될 때 이를 알려주는 기능을 데이터 객체에 부여해야 데이터 바인딩을 제대로 활용할 수 있음. Observable 객체, 필드, 컬렉션이라는 세 가지 다른 데이터 변경 알림 매커니즘이 있음.

이러한 Observable 데이터 객체 중 하나가 UI에 바인딩되어 있고 데이터 객체 속성이 변경되면 UI가 업데이트 됨.

Observable 객체

Observable 인터페이스를 구현하는 클래스를 사용하면 바인딩이 바인딩된 객체 단일 리스너를 연결.

Observable 인터페이스에는 리스너를 추가/제거하는 매커니즘이 있지만, 알림은 개발자의 선택에 따라 결정됨.

더 쉽게 개발하도록 BaseObservable을 만들어 리스너 등록 매커니즘 구현함. 속성이 변할 때 이를 알릴 책임은 데이터 클래스 구현자에게 있으며, getter에 Bindable 주석을 할당하고 setter에서 이를 알림

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

Bindable 주석은 컴파일 중에 BR 클래스 파일에 항목을 생성함. BR 클래스 파일은 모듈 패키지에 생성됨.

데이터 클래스에 대한 기본 클래스를 변경할 수 없는 경우, 리스너를 효율적으로 저장하고 알리기 편한 PropertyChangeRegistry를 사용하여 Observable 인터페이스를 구현 가능.

Observable Field

ObservableFields는 단일필드를 가진 Observable 객체.데이터 클래스에 public final 필드를 생성함.

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

ObservableField 와 형제들

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable

값 액세스시 set 및 get 접근자 메서드 사용

user.firstName.set("Google");
int age = user.age.get();

Observable 컬렉션

데이터 객체에 키 입력 방식으로 액세스 가능. 키가 String과 같은 참조 형식일 경우 ObservableArrayMap이 유용함.

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);

레이아웃에서 String키를 통해 맵에 액세스시

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap&lt;String, Object&gt;"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

키가 정수일 때는 ObservableArrayList가 유용

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);ㅔ]

레이아웃에서 인덱스를 통해 액세스시

<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</data>
…
<TextView
   android:text='@{user[Fields.LAST_NAME]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

생성되는 바인딩

생성되는 바인딩 클래스는 레이아웃 내에서 레이아웃 변수를 View와 연결함.

Binding의 이름과 패키지는 사용자 지정 가능하며, 바인딩 클래스는 모두 ViewDataBinding을 확장함.

생성

일반적으로 정적 메서드 사용하여 생성.

MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater, viewGroup, false);

또는 레이아웃을 확장시킨 후 따로 바인딩

MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);

바인딩을 미리 알 수 없을 때는 DataBindingUtil 클래스를 사용하여 바인딩 생성

ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);

###View 적용

레이아웃에서 ID가 있는 각각의 View에 대한 공용 최종 필드 생성됨.

바인딩은 ID가 있는 View를 추출함.

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
   android:id="@+id/firstName"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
  android:id="@+id/lastName"/>
   </LinearLayout>
</layout>

위 코드는 아래 바인딩 클래스를 생성함.

public final TextView firstName;
public final TextView lastName;

ID는 데이터 바인딩이 없으면 거의 필요 없지만, 코드에서 View에 액세스해야 할 경우가 있음.

###변수

각 변수에는 접근자 메서드가 주어짐

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

위 코드는 바인딩에 setter와 getter를 생성함

public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);

###ViewStub

ViewStub은 보이지 않게 시작되어 보이게 되거나 명시적으로 확장 명령이 있을때 다른 레이아웃을 확장하여 레이아웃에서 스스로를 대체함.

ViewStubProxy 객체가 ViewStub을 대신하여 ViewStub 확장시 확장된 View 계층 구조에 액세스하기도 할때 개발자에게 ViewStub에 대한 액세스 권한을 부여함.

다른 레이아웃을 확장할 때는 새 레이아웃에 대해 바인딩이 설정되어 있어야 함. ViewStubProxy는 하나만 존재할 수 있으며, 개발자는 ViewStubProxy에 대한 OnInflateListener를 설정하여 바인딩 설정 후에 호출하도록 함.

###고급 바인딩

####동적 변수

임의의 레이아웃에 대해 작동하는 RecyclerView.Adapter가 특정 바인딩 클래스를 알지 못하는 경우 onBindViewHolder(VH, int) 중에 바인딩 값을 할당해야 함.

BindingHolder에는 ViewDataBinding 베이스를 반환하는 getBinding 메서드가 있음.

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

####즉각적인 바인딩

변수나 Observable이 변경되면 바인딩이 다음 프레임 전에 변경되도록 예약됨.

바인딩을 즉시 실행해야 할 경우 ViewDataBinding#executePendingBindings() 메서드를 사용.

####백그라운드 스레드

데이터 모델이 컬렉션이 아닌 한, 백그라운드 스레드에서 데이터 모델을 변경할 수 있음.

데이터 바인딩은 동시 실행 문제 방지를 위해 각각의 변수/필드를 지역 범위로 설정함.

특성 Setter

바인딩된 값이 변경시마다 바인딩 클래스는 바인딩 식과 함께 View에 대한 setter 메서드를 호출해야 함.

데이터 바인딩 프레임워크에는 값을 설정하기 위해 호출할 메서드를 사용자 지정하는 방법이 있음.

###자동 Setter

데이터 바인딩은 메서드 setAttribute 찾기를 시도. 특성 이름 자체만 찾으면 됨.

TextView의 android:text와 연관된 식은 setText(String)을 찾음.

식에서 int가 반환되는 경우 데이터 바인딩은 setText(int) 메서드를 검색함.

참고로 주어진 이름을 가진 특성이 존재하지 않더라도 데이터 바인딩은 작동함.

예를 들어 DrawerLayout에 특성이 없지만 setter는 많이 있다고 가정해보면 자동 setter를 사용하면 이런 setter 중 하나를 사용할 수 있음.

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

이름이 바뀐 Setter

setter의 이름이 일치하지 않는 특성은 BindingMethods 주석을 통해 특성을 setter와 연결할 수 있음.

메서드는 클래스와 연결되어야 하며 이름이 바뀐 메서드마다 BindingMethod 주석이 하나씩 포함됨.

예를 들어 android:tint 특성은 실제로 setTint가 아니라 setImageTintList(ColorStateList)와 연결됨.

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

Android 프레임워크 특성이 이미 구현되었으므로, 개발자가 setter의 이름을 바꿔야 할 일은 거의 없음.

사용자 지정 Setter

일부 특성은 사용자 지정 바인딩 로직이 필요함.

예를 들어 android:paddingLeft 특성에 대해 연결되 setter가 없고 setPadding(left, top, right, bottom)이 존재함.

개발자는 BindingAdapter 주석이 있는 정적 바인딩 어댑터 메서드를 사용하여 특성의 setter를 호출도록 사용자 지정이 가능함.

Android 특성에 이미 BindingAdapter가 생성되어 있음.

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

바인딩 어댑터는 다른 형식의 사용자 지정에 유용함. 예를 들어 이미지 로드를 위해 오프스레드 상태로 맞춤 로더 호출 가능함.

개발자가 만든 바인딩 어댑터는 충돌이 있을 경우 데이터 바인딩 기본 어댑터를 무시함.

여러 매개변수를 받는 어댑터를 사용할 수도 있음.

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

imageUrl과 error가 둘 다 ImageView에 사용되고 imageUrl이 문자열이고 error가 드로어블인 경우에 이 어댑터가 호출됨.

  • 맞춤 네임스페이스는 일치 확인 중에 무시됨.
  • Android 네임스페이스용 어댑터를 작성할 수도 있음.

바인딩 어댑터 메서드는 선택적으로 핸들러의 기존 값을 취할 수도 있음.

기존 값과 새 값을 취하는 메서드는 특성의 모든 기존 값을 먼저 가진 후 새 값을 가져야 함.

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

이벤트 핸드러는 한 개의 추상 메서드를 가진 추상 클래스나 인터페이스와 함께 사용할 수 있을 뿐임.

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

리스너에 여러 개의 메서드가 있을 때는 여러 개의 리스너로 분할해야 함.

View.OnAttachStateChangeListener에는 onViewAttachedToWindow()와 onViewDetachedFromWindow()의 두 메서드가 있음.

이들 메서드의 특성과 핸들러를 구분하기 위해 두 개의 인터페이스를 만들어야 함.

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

한 리스너를 변경하면 다른 리스너에도 영향을 미치기 때문에 세 가지의 각기 다른 바인딩 어댑터가 있어야 함.

즉 각각의 특성을 위한 바인딩 어댑터 하나와 두 리스너 모두에 하나씩 있어야 함.

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

View 가 View.OnAttachStateChangeListener에 대한 set 메서드를 사용하는 대신 리스너에 대한 add와 remove를 사용하기 때문에 위 예시는 좀 더 복잡함.

ListenerUtil 클래스는 Binding Adapter에서 이전의 리스너를 제거할 수 있도록 이들을 추적하는 데 도움이 됨.

인터페이스 OnViewDetachedFromWindow와 OnViewAttachedToWindow에 @TargetApi(VERSION_CODES.HONEYCOMB_MR1) 주석을 추가하면 데이터 바인딩 코드 생성기가 addOnAttachStateChangeListener(View.OnAttachStateChangeListener) 에 의해 지원되는 같은 버전인 HoneyComb MR1과 새 기기에서 실행 시 리스너만 생성되어야 한다는 점을 인식하게 됨.

변환기

객체 변환

바인딩 식에서 Object가 반환되면 자동 setter, 이름이 바뀐 setter, 맞춤 setter 중에서 setter가 선택됨.

Object는 선택된 setter의 매개변수 형식으로 형변환됨.

이는 ObservableMaps를 사용하여 데이터를 유지하는 경우에 편리함.

<TextView
   android:text='@{userMap["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

userMap은 Object를 반환하고 그 Object는 setter setText(CharSequence)에서 찾은 매개변수 형식으로 자동 형변환됨.

매개변수 형식에 대한 혼돈이 있을 수 있을 때는 개발자가 식을 통해 형변환해야 함.

사용자 지정변환

특정 형식 간 자동 변환이 이루어져야 할 경우. 예를 들어 배경 설정시.

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

배경은 Drawable을 취하지만 색은 정수 형식임.

Drawable이 실행되고 정수가 반환될 때마다 int가 ColorDrawable로 변환되어야 함.

이 변환은 BindingConversion 주석과 함께 정적 메서드를 사용하여 수행됨.

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

변환은 setter 레벨에서만 이루어지므로 다음과 같이 형식을 혼합할 수 없음

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Android Studio의 데이터 바인딩 지원

데이터 바인딩 식에 대해 다음과 같은 기능 지원함

  • 구문 강조표시
  • 식 언어 구문 오류의 플래그 지정
  • XML 코드 완성
  • (선언 탐색 등의) 탐색과 빠른 문서화를 포함한 참조

배열과 제너릭 형식은 오류가 없을 때 오류를 표시할 수도 있음.

Preview 창에 데이터 바인딩 식의 기본값이 표시됨(제공되는 경우).

Preview 창은 TextView에 PLACEHOLDER 기본 텍스트 값을 표시하는 레이아웃 XML 파일의 한 예시.

<TextView android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.firstName, default=PLACEHOLDER}"/>


데이터 바인딩 라이브러리 2

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

식 언어

공통

  • 수학 + - / * %
  • 문자열 연결 +
  • 논리 && ||
  • 이항 & | ^
  • 단항 **+ - ! ~**
  • 시프트 >> >>> <<
  • 비교 == > < >= <=
  • instanceof
  • 그룹화 ()
  • 리터럴 - 문자, 문자열, 숫자, null
  • 형변환
  • 메서드 호출
  • 필드 액세스
  • 배열 액세스 []
  • 삼항 연산자 ?:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

연산 누락

Java에서 사용 가능한 식 구문에서 몇 가지 연산 누락됨

  • this
  • super
  • new
  • 명시적 일반 호출

null 병합 연산자

null 병합 연산자는 왼쪽 피연산자가 null 이 아니면 왼쪽, null이면 오른쪽 피연산자를 선택함.

android:text="@{user.displayName ?? user.lastName}"

아래와 동일함

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

속성 참조

식이 클래스의 한 속성을 참조할 떄, 필드, getter, ObservableField 에 똑같은 형식이 사용됨.

android:text="@{user.lastName}"

NullPointerException 방지

생성되는 데이터 바인딩 코드는 자동으로 null 검사하여 null 포인터 예외를 피함.

예를 들면

  • user가 null 인 경우
  • user.name은 기본값(null)
  • user.age는 기본값(0) (여기서 age는 int)

컬렉션

편의상 [] 연산자를 사용하여 배열, 목록, 희소 목록, 맵 등의 공통 컬렉션에 액세스할 수 있음.

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"

문자열 리터럴

작은 따옴표로 묶어서 사용하면 식에서 큰 따움표를 사용하기 쉬움.

특성 값을 큰 따움표로 묶을 경우 문자열 리터럴에는 ' 또는 ` 중 하나를 사용해야 함.

android:text='@{map["firstName"]}'
or
android:text="@{map[`firstName`]}"
android:text="@{map['firstName']}"

리소스

일반적인 구문을 사용하는 식의 일부로 리소스에 액세스할 수 있음.

<!-- 일부로 리소스에 액세스할 수 있음 -->
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

<!-- 매개변수를 제공하여 형식 문자열과 복수형을 평가할 수 있음 -->
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

<!-- 복수형이 여러 매개변수를 취할 때는 모든 매개변수가 전달되어야 함 -->
  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

plurals 는 단수/복수형에 대한 처리시 사용.

https://developer.android.com/guide/topics/resources/string-resource?hl=ko#Plurals

형식일반적인 참조식 참조
String[]@array@stringArray
int[]@array@intArray
TypedArray@array@typedArray
Animator@animator@animator
StateListAnimator@animator@stateListAnimator
color int@color@color
ColorStateList@color@colorStateList


데이터 바인딩 라이브러리 1

참고링크 : https://developer.android.com/topic/libraries/data-binding/?hl=ko

데이터 바인딩 라이브러리는 지원 라이브러리로 Android 2.1(API 7) 이상의 모든 버전에서 사용 가능.

데이터 바인딩을 사용하려면 Android Plugin for Gradle 1.5.0-alpha1 이상이 필요.

빌드 환경

  • Android SDK Manager의 Support 저장소에 라이브러리 다운로드.

  • build.gradle 파일에 dataBinding 요소 추가

    android { ... dataBinding { enabled = true
    } }

데이터 바인딩 레이아웃 파일

데이터 바인딩 레이아웃 파일은 layout 루트 태그로 시작되고 그 뒤에 data 요소와 view 루트 요소가 나옴.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

data 내에 있는 user 변수는 레이아웃 내에서 사용할 수 있는 속성에 대한 설명임

   <data>
       <variable name="user" type="com.example.User"/>
   </data>

"@{}" 구문을 사용하여 특성 속성에 기록됨.

       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>

데이터 객체

User에 대한 POJO(plain-old java project) 예시

(결코 변경되지 않는 데이터가 있는 경우)

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

JavaBeans 객체 예시

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

데이터 바인딩 관점에서 위 두 클래스는 동일함.

TextView의 android:text 특성에 사용된 @{user.firstName} 은 전자의 firstName 필드와 후자의 getFirstName() 메서드에 액세스함. 또는 firstName() 메서드가 존재할 경우 메서드로 해석하기도 함.

데이터 바인딩

BInding 클래스는 레이아웃 파일명 기준으로 생성되어 파일 이름을 파스칼 표기법(Pascal Case : 합성어 첫 글자를 대문자로 표기)으로 변환하고 그 뒤에 "Binding"을 접미사로 붙임.

main_activity.xml일 경우 생성되는 클래스는 MainActivityBinding 임.

이 클래스는 레이아웃 속성(예: user 변수)에서 레이아웃 View까지 모든 바인딩을 유지하고 바인딩 식에 대해 값을 할당하는 방법을 알고 있음.

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

다음을 통해 뷰를 얻을 수도 있음

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

ListView 어댑터나 RecyclerView 어댑터 내에서 데이터 바인딩 항목 사용시 다음을 선호하기도 함.

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

이벤트 처리

데이터 바인딩을 사용하여 뷰에서 발소오디는 이벤트 처리식을 작성할 수 있으며, 이벤트 특성 이름은 리스너 메서드명에 따라 결정됨.

예를 들면 View.OnLongClickListener 의 onLongClick() 이벤트의 특성은 android:onLongClick

  • 메서드 참조 : 식에서 리스너 메서드의 서명을 준수하는 메서드를 참조함.
  • 리스너 바인딩 : 이벤트 발생 시 계산되는 람다 식. 뷰에서 설정하는 리스너를 항상 생성하며, 이벤트가 발송될 때 람다 식을 계산함.

메서드 참조

이벤트를 핸들러 메서드에 직접 바인딩함.

컴파일 시 처리되므로, 메서드가 존재하지 않거나 메서드 서명이 올바르지 않을 경우 컴파일 시 오류 발생

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

바인딩 식이 View 에 대해 click 리스너를 할당할 수 있음.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

리스너 바인딩

이벤트 발생시 실행되는 바인딩 식.

임의의 데이터 바인딩 식을 실행할 수 있으며, Android Gradle Plugin for Gradle 2.0 이상에서 사용 가능.

반환 값만 리스너의 예상 반환 값과 일치해야 함.

public class Presenter {
    public void onSaveClick(Task task){}
}

click 이벤트를 클래스에 바인딩

  <?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>

리스너는 식의 루트 요소로만 허용되는 람다 식으로 표현됨.

식에서 콜백이 사용될 때 데이터 바인딩이 필요한 리스너를 자동으로 생성하고 이벤트에 등록함.

뷰가 이벤트를 발생시키면 데이터 바인딩이 주어진 식을 계산.

정규 바인딩 식에서처럼 리스너 식이 평가되는 동안 여전히 데이터 바인딩의 null 및 스레드의 안전이 보장됨.

위의 예시에서는 onClick(android.view.View)로 전달되는 view 매개변수를 정의 안함.

리스너 바인딩에서 리스너 매개변수로 두 가지 중 선택 가능.

메서드에 대한 모든 매개변수를 무시하거나 모든 매개변수의 이름을 지정하는 것

매개변수의 이름을 지정하기로 선택하면 매개변수를 사용할 수 있음.

  android:onClick="@{(view) -> presenter.onSaveClick(task)}"

또는 식에 매개변수를 사용할 경우

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

두 개 이상의 매개변수를 포함한 람다 식 사용 가능

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

수신 중인 이벤트가 void 형식이 아닌 값을 반환하는 경우

식도 그와 동일한 형식의 값을 반환해야 함

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
  android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

null 객체로 인해 식의 계산이 불가능할 경우, 데이터 바인딩은 그 형식에 대한 기본 java 값을 반환.

예를 들어, 참조 형식에는 null, int 에는 0, boolean 에는 false 등을 반환함.

조건자(예:ternary)가 있는 식을 사용해야 할 경우 void를 기호로 사용 가능.

  android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

복잡한 리스너 방지

리스너 식은 UI에서 콜백 메서드로 사용 가능한 데이터를 전달하는 것만큼이나 단순해야 함.

비즈니스 로직은 리스너 식에서 호출한 콜백 메서드 내에 구현해야 함.

몇 가지 특화된 click 이벤트 핸들러 중 충돌 방지를 위해 android:onClick 이외의 특성이 필요함.

클래스리스너 Setter특성
SearchViewsetOnSearchClickListener(View.OnClickListener)android:onSearchClick
ZoomControlssetOnZoomInClickListener(View.OnClickListener)android:onZoomIn
ZoomControlssetOnZoomOutClickListener(View.OnClickListener)android:onZoomOut

레이아웃 세부정보

가져오기

data 요소 내에서 import 요소가 사용될 수도 있음.

Java에서처럼 이러한 요소를 사용하여 레이아웃 파일 내에 클래스를 쉽게 참조할 수 있음.

<data>
    <import type="android.view.View"/>
</data>
...
<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

클래스 이름 간에 충돌이 발생할 때 해당 클래스 중 하나의 이름을 alias 속성으로 바꿀 수 있음

그리고 import 요소로 가져온 타입을 variable 요소에서 사용할 수 있음.

<data>
    <import type="android.view.View"/>
    <import type="com.example.real.estate.View" alias="Vista"/>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>
</data>

Android Studio는 아직 가져오기를 처리하지 않으므로 자동 완선 기능이 작동하지 않을 수 있음.

그래도 컴파일은 제대로 되므로, 변수 정의에서 정규화된 이름을 사용하여 IDE 문제를 해결할 수 있음.

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

식에서 정적 필드와 메서드를 참조할 때 가져온 형식을 사용 가능

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Java에서처럼 java.lang.* 을 자동으로 가져오게 됨.

변수

data 요소 내에서 사용할 수 있는 variable 요소의 개수 제한은 없음.

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user"  type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note"  type="String"/>
</data>

변수 형식은 컴파일 시에 검사되므로, 변수가 android.databinding.Observable을 구현하거나 변수가 Observable 컬렉션인 경우 그 점이 형식에 반영되어야 함. 변수가 기본 클래스거나 Observable* 인터페이스를 구현하지 않은 경우 변수가 식별되지 않음.

다양한 구성(예: 가로/세로 모드)에 대한 다양한 레이아웃 파일이 있을 경우 변수들이 결합하므로 충동하는 변수 정의가 없어야 함.

생성되는 바인딩 클래스는 설명되어 있는 변수마다 각각 setter와 getter가 있음. 변수는 setter가 호출될 때까지 기본 Java 값을 취함. 즉, 참조 형식에는 null, int에는 0, boolean 에는 false 값을 취함.

필요에 따라 바인딩 식에 사용하기 위해 context 로 명명된 별도의 변수가 생성됨. context는 루트 View의 getContext()에서 가져온 값임. context 변수는 그 이름을 가진 명시적 변수 선언으로 재정의 됨.

사용자 지정 바인딩 클래스 이름

기본적인 Binding 클래스 : 레이아웃 파일명을 바탕으로 생성되며 파스칼 표기법으로 바꾼 후 "Binding"이 접미사로 붙음. 이 클래스는 모듈 패키지 아래 데이터 바인딩 패키지에 배치됨.

예를 들면

  • 레이아웃 파일 : contact_item.xml
  • 바인딩 클래스 : ContactItemBinding
  • 모듈 패키지 : com.example.my.app
  • 배치 위치 : com.example.my.app.databinding

data 요소의 class 특성을 조정하여 바인딩 클래스명을 바꾸거나 다른 패키지에 배치할 수 있음.

<!-- 바인딩클래스명 변경 -->
<data class="ContactItem">
    ...
</data>

or

<!-- 모듈 패키지 내에서 다른 패키지에 배치할 경우 -->
<data class=".ContactItem">
    ...
</data>

or

<!-- 전체 패키지가 제공될 경우 어떤 패키지든 사용 가능 -->
<data class="com.example.ContactItem">
    ...
</data>

Include

특성에 앱 네임스페이스와 변수 이름을 사용하여 포함하는 레이아웃에서 포함되는 레이아웃의 바인딩으로 변수를 전달할 수 있음.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </LinearLayout>
</layout>

name.xml 및 contract.xml 모두 user 변수가 있어야 함.

데이터 바인딩은 include를 병합 요소의 직접 하위 요소로 지원하지 않음.

지원하지 않는 레이아웃의 예

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge>
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </merge>
</layout>


[Android] 프로세스(process) 및 스레드(thread)

참고링크 : https://developer.android.com/guide/components/processes-and-threads?hl=ko

애플리케이션 구성 요소가 시작되고, 앱에 실행 중인 다른 구성 요소가 없으면 하나의 실행 스레드로 앱의 Linux 프로세스 시작함. 기본적으로 같은 애플리케이션의 모든 구성 요소는 같은 프로세스와 스레드에서 실행됨. 하지만 여러가지 구성 요소가 각자 별도의 프로세스에서 실행되도록 할 수도 있고, 어느 프로세스에든 추가 스레드를 만들 수 있음.


프로세스

기본적으로 같은 앱의 모든 구성 요소는 같은 프로세스와 스레드에서 실행되고 이를 바꾸면 안됨. 그러나 어느 프로세스가 특정 구성 요소에 속하는지 확인해야 할 경우 매니페스트 파일에서 확인 가능.(android:process 속성)

  • 지원하는 요소 : <application>, <activity>, <service>, <receiver>, <provider>

안드로이드 시스템에서 어느 프로세스를 삭제할지 결정할 때, 이들의 상대적 중요성을 가늠함.


프로세스 수명 주기

안드로이드 시스템은 프로세스에서 실행되는 구성 요소와 해당 구성 요소의 상태에 기초하여 각 프로세스에 "중요 계층"을 부여함. 중요도가 낮은 프로세스가 먼저 제거됨.


  • 중요 계층 5가지
    1. 포그라운드 프로세스 : 사용자가 현재 진행하는 작업에 필요한 프로세스 (Activity나 Foreground Service)
      • 사용자가 상호작용하는 Activity를 호스팅할 경우
      • 사용자와 상호작용하는 액티비티에 바인드된 Service를 호스팅할 경우
      • 포그라운드에서 실행되는 Service를 호스팅할 경우
      • 수명 주기 콜백 중 하나를 실행하는 Service를 호스팅할 경우 (onCreate(), onStart() 또는 onDestroy() )
    2. 가시적 프로세스 : 포그라운드 구성 요소는 없지만 사용자가 화면에서 보는 것에 영향을 미칠 수 있는 프로세스
      • 맨 앞에 있지는 않지만 사용자에게 보이는 Activity 를 호스팅할 경우
      • 눈에 보이는 액티비티에 바인딩된 Service 호스팅할 경우
    3. 서비스 프로세스 : startService() 메서드로 시작되었지만, 위 경우에 들어가지 않는 서비스를 실행 중인 프로세스일 경우
    4. 백그라운드 프로세스 : 현재 사용자에게 보이지 않는 액티비티를 보유한 프로세스. 이러한 프로세스는 LRU(최저 사용 빈도) 목록에 보관하여 사용자가 가장 최근에 본 액티비티가 있는 프로세스가 가장 마지막에 중단되도록 함.
    5. 빈 프로스세스 : 활성 앱 구성 요소를 보유하지 않은 프로세스.

프로세스의 등급은 다른 프로세스가 이에 의존할 경우 상승 가능.(의존 대상의 중요도가 의존할 대상과 같거나 높음)


스레드

앱이 시작되면 앱에 대한 실행의 스레드를 생성하며, 메인 스레드라 함. 이 스레드는 드로어블 이벤트를 포함한 UI 위젯에 이벤트를 발송하는 역할을 맡기도 하여 UI 스레드라고도 함.


시스템은 구성 요소의 각 인스턴스에 대해 별도의 스레드를 생성하지 않음. 같은 프로세스에서 실행되는 모든 구성 요소는 UI 스레드에서 시작되고, 구성 요소를 호출하는 시스템이 해당 스레드에서 발송됨.


시스템 콜백에 응답하는 메서드(onKeyDown()이나 생명주기 메서드 등)는 항상 프로세스의 UI 스레드에서 실행됨.


UI 스레드에서 DB 쿼리 등의 긴 작업을 수행하면 사용자가 보기에 앱이 중단된 것처럼 보이며, 약 5초이상 진행되면 ANR 대화상자가 표시됨.


안드로이드 UI 도구 킷은 스레드로부터 안전하지 않아 worker 스레드에서 처리하면 안됨.


Worker 스레드

단일 스레드 모델로 인해 앱 UI의 반응성을 위해서는 UI 스레드를 차단하지 않는 것이 중요함. 이를 위해 시간이 걸리는 작업들은 별도의 스레드에서 수행하며 이를 백그라운드 또는 워커 스레드라 함.


  • 안드로이드가 다른 스레드에서 UI 스레드에 액세스하기 위해 제공하는 방식
    • Activity.runOnUiThread(Runnable)
    • View.post(Runnable)
    • View.postDelayed(Runnable, long)

작업이 복잡해질수록 위 방식은 더 복잡해지므로, 더 복잡한 인터렉션을 처리하려면 worker 스레드에서 Handler를 사용하여 UI 스레드에 전달받은 메시지를 처리하는 방안을 고려해야 함. 최선의 해결책은 AsyncTask 클래스를 확장하여 사용하는 것.


AsyncTask

AsyncTask를 사용하면 UI에서 비동기식 작업을 수행할 수 있게 해줌. woker 스레드에서 작업을 수행하고 그 결과를 UI 스레드에 게시하므로 직접 스레드 및 핸들러를 처리할 필요가 없음.


AsyncTask를 서브 클래스로 지정한 다음, 백그라운드 스레드 풀에서 실행되는 doInBackground() 콜백 메서드를 구현하고, UI 업데이트를 위해 onPostExecute() 를 구현해야 함.


  • 매개 변수의 유형, 진행률 값과 최종 값을 제네릭을 사용하여 지정할 수 있음
  • doInBackground()는 worker 스레드에서 실행됨.
  • onPreExecute(), onPostExecute() 및 onProgressUpdate()는 UI 스레드에서 호출됨.
  • doInBackground()가 반환한 값이 onPostExecute() 로 전달됨.
  • publishProgress()를 doInBackground()에서 호출하여 UI 스레드에서 onProgressUpdate()를 실행함.
  • 모든 스레드에서 언제든 작업을 취소할 수 있음.

Thread-safe 메서드

어떤 경우에는 구현하는 메서드가 하나 이상의 스레드에서 호출되는 일이 있어 안전하게 작성해야 함.


주로 원격으로 호출할 수 있는 메서드이 있음. 바인드된 서비스 등.


IBinder에서 구현된 메서드가 IBinder가 실행되는 프로세스에서 호출될 경우, 해당 메서드는 호출자의 스레드에서 실행됨. 호출이 다른 프로세스에서 발생하면, 해당 메서드는 시스템이 IBinder 와 같은 프로세스에 유지하는 스레드 풀에서 선택된 스레드에서 실행됨.(프로세스의 UI 스레드에서 실행되지 않음)


위와 같은 이유로 스레드로부터 안전한 구현이 필요함.


프로세스 간 통신

안드로이드는 원격 프로시저 호출(RPC)을 사용한 프로세스 간 통신(IPC) 매커니즘을 제공함.


여기서 메서드는 액티비티나 다른 앱 구성 요소에 호출되지만 원격으로 실행되고, 결과는 모두 호출자에게 되돌려 보냄.


메서드 호출과 데이터는 운영 체제가 이해할 수 있는 수준으로 분해되고, 로컬 프로세스와 주소 공간에서 원격 프로세스와 주소 공간으로 전송되어 다시 결합되어 호출에 응답하게 됨. 그런 다음 반환 값이 반대 방향으로 전송됨.


안드로이드가 이와 같은 IPC 트랜잭션을 수행하는데 필요한 코드를 제공하므로, 개발자는 RPC 프로그래밍 인터페이스를 정의하고 구현하는 데만 집중하면 됨.


IPC 수행시 bindService()를 사용하여 앱이 서비스에 바인드 되어야 함.

+ Recent posts