안드로이드 외부 라이브러리 뷰 스타일 변경하기

안드로이드는 뷰의 크기, 색 등의 각종 스타일과 관련된 설정은 외부 파일(dimens.xml, colors.xml 등)에 저장해서, 한번에 많은 뷰의 스타일을 변경할 수 있도록 한다. 이 방법을 사용해서 테마나 스타일을 쉽게 변경할 수 있게 한다.

만약 앱과 라이브러리에서 동일한 속성 값을 사용한다면, 앱에서 정의한 속성을 사용한다. 그래서, 이 방법을 사용해서 외부 라이브러리 뷰의 스타일을 변경하는 방법을 살펴보겠다. 대부분의 라이브러리는 뷰의 속성을 외부 파일에 정의해서 릴리즈를 하지만, 속성값을 하드코딩한 경우에는 스타일 변경이 쉽지 않다.

라이브러리의 스타일을 변경하는 예제로, AboutLibraries(https://github.com/mikepenz/AboutLibraries)를 사용해 보자. 이 라이브러리가 사용하는 각종 스타일 값들을 살펴보고, 레이아웃에서 정의한 값들을 사용하는 부분과 이 정의 값을 변경해서 UI를 변경하는 예제를 살펴보자.

1. 스타일(styles.xml)

이 파일은 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/values/styles.xml 에서 확인 할 수 있다. 이 파일에서 앱이나 라이브러리의 스타일을 확인 할 수 있다. 아래는 스타일 파일에서 기본 스타일로 정의한 부분이다. 다른 앱들과 거의 비슷하다.

   


그러나, “AboutLibraries specific values” 주석아래를 살펴보면, item name=”about_libraries_window_background” 와 같이 커스텀 값이라는 것을 알 수 있다. 커스텀 값은 속성 파일(attrs.xml)에 정의해서 사용 할 수 있다. 속성 파일을 살펴보자.

2. 속성(attr.xml)

이 파일에서는 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/values/attrs.xml 에서 확인 할 수 있다. 이 파일은 안드로이드 앱이나 라이브러리에서 커스텀으로 사용하는 요소나 속성을 정의하는 용도이다. 이 내용은 XML 스키마를 정의해 봤다면 쉽게 이해가 갈 것이다. 하지만, 안드로이드에서 읽어들이는 값들이 정의되어 있기에, 이름과 포맷만 기술하게 되어 있긴 하다. 아래는 라이브러리에서 커스텀으로 정의한 속성의 일부이다.

 

 	
 	
 	
 	
 	
 	
 	
 	
 	
 	

위 속성에서 “about_libraries_window_background”는 색이고 참조(reference)라는 것을 알 수 있다. 다음으로 색을 살펴보자.

3. 색(colors.xml)

이 파일은 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/values/colors.xml 에서 확인 할 수 있다. 아래는 라이브러리가 사용하려고 정의한 색들 중에 일부이다.

 

#ECECEC
#FAFAFA
#212121
#727272
#212121
#AAA
#DADADA

색의 이름이 속성 파일에 정의한 이름으로 요소는 color로 시작하는 것을 알 수 있다.

4. 크기 정의(dimens.xml)

이 파일은 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/values/dimen.xml 에서 확인 할 수 있다. 이 파일은 안드로이드에서 뷰의 마진, 여백, 폰트 크기 등의 값을 정의한다. 이 라이브러리의 크기 값들은 아래와 같다.

 


12dp
72dp
20sp
14sp

16dp
16dp

이제 위 속성을 변경하지 않은 화면은 아래와 같다.

다음으로 속성을 변경하면, 어떤 UI가 변경되는지 살펴보기 위해서 간단하게 레이아웃 파일을 살펴보자.

5. 레이아웃 확인

이제 레이아웃 파일에서 개별 뷰의 스타일이 위에서 사용하는 속성을 사용하는지, 그리고 이 속성을 변경해서 뷰를 수정해 보자.

– 목록 컨테이너
목록 컨테이너는 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/layout/fragment_opensource.xml 에 정의되어 있다. 목록을 보여주는 컨테이너로 리사이클러뷰(RecyclerView)를 사용하고, 패딩 값으로 dimens.xml 파일에 정의한 속성 값을 사용하는 것을 알 수 있다. 이 속성 값을 변경해서 카드 레이아웃이 조금 좁거나 넓게 보이게 할 수 있다.

 

– 헤더 부분
목록의 헤더 부분(앱 아이콘, 이름, 버전 정보 등)은 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/layout/listheader_opensource.xml 에 정의되어 있다. 아래는 이 파일에서 앱 아이콘, 이름, 버전을 보여주는 일부 뷰이다. 여기에서도 이미지 크기는 dimens.xml 파일에 정의되어 있고, 앱 이름과 버전도 정의한 것을 알 수 있다.

 



– 항목(Item) 부분
목록의 항목은 https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/res/layout/listitem_opensource.xml 에 정의되어 있다. 아래는 라이브러리 이름과 개발자 이름을 보여주는 부분이다. 여기도 목록의 헤더와 같이 textSize를 dimen.xml 에서 정의한 값으로 사용한다.

 


6, UI 변경 예제

이제 앱에서 텍스트 크기를 변경해서, 이 라이브러리 뷰가 사용하는 텍스트뷰의 스타일을 변경해 보자. 개발중인 앱의 앱 모듈에서 크기 정의 파일(/app/src/main/res/values/dimens.xml)을 열어서 아래의 값을 추가해 보자.

 
16sp
16sp

위 속성을 추가한 뒤의 결과는 아래와 같다.

다음으로 위에서 정의한 속성 파일의 사용자 정의 색을 변경해 보자. 개발중인 앱의 앱 모듈에서 크기 정의 파일(/app/src/main/res/values/color.xml)을 열어서 아래의 값을 추가해 보자.

 
#0368A1
#FBBB00

위에서 변경한 색이 어떻게 적용되는지 확인하려면, 라이브러리 코드를 보면 쉽게 확인할 수 있다. 예로, 헤더의 앱 이름 색을 변경하는 코드는  https://github.com/mikepenz/AboutLibraries/blob/develop/library/src/main/java/com/mikepenz/aboutlibraries/ui/item/HeaderItem.java 에서 알 수 있다. 이 클래스에서 아래의 코드로 앱 이름 텍스트뷰의 텍스트 색을 변경하는 것을 알 수 있다.

aboutAppName = (TextView) headerView.findViewById(R.id.aboutName);
aboutAppName.setTextColor(UIUtils.getThemeColorFromAttrOrRes(headerView.getContext(), R.attr.about_libraries_title_description, R.color.about_libraries_title_description));

뷰를 가지는 대부분의 안드로이드 라이브러리는 위와 거의 비슷한 구조이다. 그래서 외부 라이브러리를 개발하는 앱의 스타일에 맞춰서 사용하려면, 이 글의 과정으로 쉽게 스타일을 변경할 수 있다.

액션바(ActionBar) 제목 및 부제목 스타일 변경

이제 아주 많은 앱들이 v7의 툴바(Toolbar)를 사용해서 액션바(ActionBar)로 사용할 것이다. 그리고, 액티비티(Activity)에서 보여주는 정보에 따라서 액티비티의 제목(Title)과 부제목(SubTitle)을 사용할 수도 있다.

프로젝트를 만들고, 액션바에 제목과 부제목을 추가해보자.

위 화면에서 보듯이, 글자색이 보색이 아니라서 가독성이 떨어진다. 그리고, 글자의 크기로 인해서 액션바의 제목을 보여주는 영역이 꽉 찬 느낌을 준다.

그래서, 가독성을 좋게하고 글자의 크기나 색을 변경할 수 있는 방법을 살펴보자. 여기에서는 툴바를 사용해서 액션바의 제목과 부제목의 스타일을 변경하는 방법을 살펴보자.

1. 액션바의 제목과 부제목에 해당하는 스타일을 만든다.
/res/values/styles.xml 파일을 열어서 아래의 스타일을 추가한다.

    


 


이 코드는 2개의 스타일을 정의한다. 제목 텍스트 스타일로 ToolbarTitleText에서 18sp의 크기와 md_grey_100의 색을 사용한다. 그리고, 부제목으로 ToolbarSubTitleText에서 볼 수 있듯이 14sp의 크기와 md_grey_200의 색을 사용한다.

2. Toolbar에서 제목 텍스트와 부제목 텍스트 스타일 지정
이제 /res/layout/xxx.xml 파일을 열어서 툴바의 스타일을 아래와 같이 지정한다.

    
    
        
        

이제 위에서 정의한 스타일이 잘 적용되는지 확인해보면 아래와 같은 결과를 확인 할 수 있다.

TextView android:maxLines과 android:ellipsize 사용시 주의할 점

안드로이드 텍스트뷰는 가장 많이 사용하는 컴포넌트 중의 하나이고, 또 이 컴포넌트의 텍스트는 많은 경우 줄임(ellipse…)을 사용한다. 내 경우에는 가운데 줄임을 많이 사용하는 편이고, 아래와 같이 사용한 경우가 있었다.

<TextView
 android:id="@+id/action_detail_path"
 style="@style/SmallText"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:layout_margin="4dp"
 android:ellipsize="middle"
 android:maxLines="2"
 android:text="@string/lbl_empty"
 />

위 XML 코드는 문제가 없어 보인다. 대부분의 경우에는 문제가 없이 잘 동작해 보였다.

그러나, 간혹 이 텍스트뷰로 인해서 크래시가 발생할 수도 있다. 발생할 수도 있다는 경우는 자주는 아니지만 가끔 발생한다는 것이다. 그래서 위 android:ellipse에 대응하는 TextView API는 setEllipsize 메서드이고, 아래와 같이 설명하고 있다.

void setEllipsize (TextUtils.TruncateAt where)

Causes words in the text that are longer than the view’s width to be ellipsized instead of broken in the middle. You may also want to setSingleLine() or setHorizontallyScrolling(boolean) to constrain the text to a single line. Use null to turn off ellipsizing. If setMaxLines(int) has been used to set two or more lines, only END and MARQUEE are supported (other ellipsizing types will not do anything).
Related XML Attributes:

– android:ellipsize

위 설명을 보면, setMaxLines() 메서드 인자가 2보다 크면, ellipse로 END, MARQUEE만 지원한다는 것이다. 결국 위 xml의 android:ellipsize=”middle”이 크래시를 유발할 수 있다.

앱에서 이 버그가 많이 발생하지 않아서 발견하기 어려(?)웠는데… 이게 다 API를 잘 읽어보지 않고 막 쓰는 제 불찰이었네요.. 괜히 애꿎은 제조사 탓만 했네요..ㅋㅋ

Robo 3T (Robomongo) 설치 on Ubuntu 16.04

Robo 3T는 Robomongo의 새로운 이름으로 아주 유용하게 사용할 수 있는 몽고디비 클라이언트 툴이다. 간단하게 설치하는 방법을 살펴보면 아래와 같다.

1. 다운로드
https://www.robomongo.org/download 에서 플랫폼 버전에 맞게 다운로드한다. 여기에서는 Linux를 선택한다.

2. 다운받은 파일을 필요한 위치에 이동시키고, 압축을 푼다.
mcsong@mcsong-ubuntu:~/Downloads$ mv robo3t-1.1.1-linux-x86_64-c93c6b0.tar.gz ../dev/
mcsong@mcsong-ubuntu:~/dev$ tar xvfz robo3t-1.1.1-linux-x86_64-c93c6b0.tar.gz

3. 링크파일을 패스가 잡혀있는 폴더에 생성한다.
mcsong@mcsong-ubuntu:~/dev/robo3t-1.1.1-linux-x86_64-c93c6b0/bin$ sudo ln -s /home/mcsong/dev/robo3t-1.1.1-linux-x86_64-c93c6b0/bin/robo3t /usr/local/bin/robo3t

4. 이제 실행해서 아래와 같이 연결을 추가하고 사용하면 된다. 

4.1 만약 아래와 같이 에러가 발생한다면..
mcsong@mcsong-ubuntu:~/dev/robo3t-1.1.1-linux-x86_64-c93c6b0/bin$ robo3t
This application failed to start because it could not find or load the Qt platform plugin "xcb"
in "".

Available platform plugins are: xcb.

Reinstalling the application may fix this problem.
Aborted (core dumped)

설치한 폴더의 lib 폴더에서 아래의 파일들을 삭제하면 된다.

libstdc++.so.6
libstdc++.so.6.0.22

안드로이드에서 비교적 안전하게 키 사용하기

많은 앱이 상품을 팔거나 각종 서비스에 연동하는 경우에 필요한 아이디나 키를 저장해서 사용한다. 이 키들을 안전하게 사용하려면 추가적인 처리가 필요하다. 짧은 지식으로 안전함의 순위를 나열하면 아래와 같다.

1. 안전한 방법 : 암호화해서 저장
2. 비교적 안전한 방법 : 네이티브 영역에 저장
3. 안전하지 않은 방법 : 리소스 파일에 저장

우선, 위 기준에 따른 사용방법을 살펴보자.

1. 안전한 방법
1.1 대칭키를 사용해서 키를 암호화한다.
1.2 안드로이드 리소스 파일에 암호화된 키를 저장한다.
1.3 암호화된 키를 사용하기 전에 서버에서 대칭키를 받아서 복호화한다.

2. 비교적 안전한 방법
2.1 네이티브 코드에 키를 리턴하는 메서드를 구현한다.
2.2 네이티브 메서드를 호출해서 키를 받는다.

3. 안전하지 않은 방법
3.1 키를 리소스 파일에 저장한다.
3.2 저장된 키를 사용한다.

이 글에서는 현실적으로 서버를 사용할 수 없는 경우에, 비교적 안전한 방법을 채택하는 형태를 살펴본다.

기존 프로젝트의 main 폴더에 jni 폴더를 만들고 아래와 같은 파일을 만든다.

위 파일의 내용은 아래와 같다.

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := keys
LOCAL_SRC_FILES := keys.c

include $(BUILD_SHARED_LIBRARY)

Application.mk : 아래 코드로 모든 플랫폼에 해당하는 .so 를 만든다.

APP_ABI := all

Keys.c : 자바에서 호출할 메서드와 키를 정의한다.

#include 

JNIEXPORT jstring JNICALL
Java_net_sjava_keytest_MainActivity_getNativeKey1(JNIEnv *env, jobject instance) {
    return (*env)->  NewStringUTF(env, "First Key");
}

JNIEXPORT jstring JNICALL
Java_net_sjava_keytest_MainActivity_getNativeKey2(JNIEnv *env, jobject instance) {
    return (*env)->NewStringUTF(env, "Second Key");
}

이제 build.gradle에서 ndk를 포함시키는 코드를 추가한다.

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"
    defaultConfig {
        applicationId "net.sjava.keytest"
        minSdkVersion 19
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        ndkBuild {
            path 'src/main/jni/Android.mk'
        }
    }
}

그리고, MainActivity.java 에서 아래와 같이 키를 가져오는지 확인한다.

package net.sjava.keytest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("keys");
    }

    public native String getNativeKey1();
    public native String getNativeKey2();

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

        String key1 = getNativeKey1();
        String key2 = getNativeKey2();

        ((TextView)findViewById(R.id.textview)).setText("Key1-->"+key1+"\nKey2-->"+key2);
    }
}

이제, 위 코드를 실행하면 아래의 실행 결과를 볼 수 있다.

현실적으로 앱 크래킹을 완벽하게 막기는 불가능한 것 같다. 그래서 프로가드(Proguard)로 난독화 처리와, 앱에서 사용하는 키를 비교적 안전하게 관리해서 크래킹을 어렵게 만드는 게 현실적인 방법인 것 같다.

* 레퍼런스 : https://medium.com/@abhi007tyagi/storing-api-keys-using-android-ndk-6abb0adcadad