회사에서 C 코드를 안드로이드에서 사용해야할 일이 있어서 NDK와 JNI에 대해 알아보고 있었다. 그러다보니 자연스레 아키텍쳐까지 보게 되었다. 좋은 현상인 것 같다.




Linux Kernel


Android 플랫폼은 리눅스 커널을 기반으로 한다. 


오디오, 키패드, 카메라 등 각종 장치의 사용을 위한 드라이버와 프로세스 관리, 메모리 관리 등의 기능을 포함하고 있다.


리눅스 커널은 오픈 소스이면서 드라이버 모델의 검증된 안정성, 최신 장비의 지원, 하드웨어 입출력에 대한 보안모델 제공 등이 안드로이드가 리눅스 커널을 사용하는 이유일 것이다.


안드로이드서 사용하는 리눅스 커널은 알람, 공유메모리, Low memory-killer, kernel debugger, binder, power management, logger 등 기존에 없었거나 향상된 기능들이 있다.



Hardware Abstraction Layer(하드웨어 추상화 계층)


커널의 윗단에는 C와 C++로 짜여진 시스템 라이브러리가 존재한다. 기존에 많이 사용되어 신뢰성을 보장한다.


Java API 프레임워크에 하드웨어 기능을 노출하는 표준 인터페이스르 제공한다. 카메라 또는 블루투스 모듈과 같은 특정 유형의 하드웨어 구성 요소를 위한 인터페이스를 구현한다.


프레임워크 API가 기기 하드웨어에 액세스하기 위해 호출을 수행하면 안드로이드 시스템이 해당 하드웨어 구성 요소에 대한 라이브러리 모듈을 로드한다.


이러한 라이브러리에는 System C Library, Media Libraries, Surface Manager, 3D Libraries 등이 있다.



Android Runtime


안드로이드 버전 5.0(API 레벨 21) 이상부터는 각 앱이 자체 프로세스 내에서 자체 ART(Android 런타임) 인스턴스로 실행된다. 


ART의 주요 기능 중 몇 가지를 살펴보면 다음과 같다.


- AOT(Ahead of Time) 및 JIT(Just In Time) 컴파일

- 최적화된 가비지 컬렉터(GC)


이전에는 Dalvik VM이라는 것이 사용되었는데, 이 VM의 한계를 해결하기 위해 개발된 런타임이 ART이다. 


SUN의 자바는 명령들이 전부 8비트 길이를 가지지만 안드로이드는 4바이트이다. 기존의 SUN은 명령어가 스택에 들어가기 때문에 PUSH, POP명령어를 쓰고 Dalvik은 뱅크드레지스터를 이용한 복사명령을 이용하고 레지스터에서 바로 실행시키기 때문에 속도가 더 빠르다. 4바이트가 레지스터에 전부 들어가기 때문에 낮은사양에서도 느려지지 않는 효과도 있다. 



JNI


프로그램동작은 자바코드이고 드라이버들은 대부분 C/C++이지만 그 사이에 JNI가 있기때문에 동작이 가능하다. JNI는 자바코드에서 C나 C++  라이브러리를 호출할 수 있도록 만들어진 규약이다. 안드로이드에서 응용프로그램은 C/C++로도 만들 수 있다. 대신 UI를 가지기는 힘들다. 백그라운드 서비스를 제작할 경우 굳이 자바로 할 필요는 없다.



Native C/C++ Libraries


ART 및 HAL 등의 많은 핵심 Android 시스템 구성 요소와 서비스가 C 및 C++로 작성된 네이티브 라이브러리를 필요로 하는 네이티브 코드를 기반으로 빌드되었다. Android 플랫폼은 Java 프레임워크 API를 제공하여 이러한 일부 네이티브 라이브러리의 기능을 앱에 노출한다. 예를 들어, Android 프레임워크의 Java OpenGL API를 통해 OpenGL ES에 액세스하여 앱에서 2D 및 3D 그래픽을 그리고 조작할 수 있는 지원 기능을 추가할 수 있다.


C 또는 C++ 코드가 필요한 앱을 개발하는 경우에는 Android NDK를 사용하여 네이티브 코드에서 직접 이러한 몇몇 네이티브 플랫폼 라이브러리에 액세스할 수 있다.



Java API Framework


안드로이드 앱 개발을 하면서 가장 많이 접했던 계층이다. 뷰 시스템, resource manager, notification manager, activity manager, content provider 등을 호출하여 사용할 수 있다.


개발자는 안드로이드 시스템 앱이 사용하는 것과 동일한 프레임워크 API에 대한 전체 액세스 권한을 가진다.



System Apps


우리가 실행하고 사용하는 앱이다. 


시스템 앱은 사용자를 위한 앱으로도 작동하고 개발자가 자신의 앱에서 액세스할 수 있는 주요 기능을 제공하기 위한 용도로도 작동한다. 예를 들어, 앱이 SMS 메시지를 제공하고자 할 경우 해당 기능을 직접 빌드할 필요가 없다. 그 대신, 이미 설치된 SMS 앱을 호출하여 지정한 받는 사람에게 메시지를 제공할 수 있다.



JIT와 컴파일러, NDK 등에 대해서는 다른 글에서 다루도록 하겠다.



참고


플랫폼 아키텍쳐


안드로이드의 구조


안드로이드 가상머신 동작 구조


안드로이드 구조와 원리 정리

블로그 이미지

NCookie

,

java.lang.NoSuchMethodError: No static method getFont(Landroid/content/Context;ILandroid/util/TypedValue;ILandroid/widget/TextView;)


이런 오류가 떴는데 도대체 뭐가 문제인지 모르겠어서 검색해봤다.


dependency와 빌드 툴의 버전이 맞지 않아 발생할 수 있다고 한다.


나는 RecyclerView를 사용했는데, 버전을 확인해보니 혼자 27.0.1이었다. 그래서 빌드 버전인 26.1.0으로 바꾸어줬더니 깔끔하게 해결되었다.



참고


java.lang.NoSuchMethodError: No static method getFont(Landroid/content/Context;ILandroid/util/TypedValue;ILandroid/widget/TextView;)


Error get Font Landroid/content/Context;ILandroid/util/TypedValue

'안드로이드' 카테고리의 다른 글

안드로이드 이미지 여백  (0) 2017.12.05
안드로이드 플랫폼 아키텍쳐  (0) 2017.11.30
안드로이드 리소스 ID  (0) 2017.11.27
serializable, parcelable  (0) 2017.11.24
안드로이드 Bundle  (0) 2017.11.24
블로그 이미지

NCookie

,

xml에서 레이아웃을 작성할 때 사용하는 그 android:id="@android:id/" 가 지금 설명하려는 것이다.


이 ID는 xml에 정의된 고유 ID 뿐만 아니라 어플리케이션에서 결과 코드값으로도 사용할 수 있다.


선언 방법은 간단하다.


res/values/ids.xml 에서 아래와 같은 형식으로 적어주면 된다.


<?xml version="1.0" encoding="utf-8"?>
<resources>
   
<item type="id" name="button_ok" />
   
<item type="id" name="dialog_exit" />
</resources>

resource는 무조건 root에 있어야 하며, item 태그는 value를 가지지 않고 속성만을 가지고 있다. type은 항상 "id" 이어야하고 name은 ID에 붙이고 싶은 이름을 넣으면 된다.


<Button android:id="@id/button_ok"
   
style="@style/button_style" />


그리고 위와 같이 레이아웃 등에서 사용할 수 있다.


이런 것들은 굳이 ids.xml에 선언하지 않아도 원하는 레이아웃에서 직접 ID를 설정할 수 있다. 그렇다면 왜 굳이 이런 번거로운 방법을 사용하는걸까.


ids.xml에는 모든 ID가 선언되어 있기 때문에 컴파일러는 그것들을 recognize 할 수 있다.


<TextView
    android:id="@+id/text1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignBelow="@id/text2"
    android:text="...."/>
<TextView
    android:id="@+id/text2"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="...."/>


text2라는 ID를 가진 뷰가 선언되기 전에 text2를 text1에서 참조하였기 때문에 에러가 발생한다. 이 때 ids.xml에 ID들을 미리 선언해둔다면 이러한 에러를 피해갈 수 있다.


이런 예시도 있다. 대형 프로젝트의 경우, id를 굉장히 많이 선언하게 되는데. 만약 중간에 View 하나를 삭제했다면 컴파일러는 no such field 같은 오류를 뿜을 것이다. 하지만 이런 오류 메시지 하나만으로는 그 많은 소스 코드에서 어떤 것이 문제인지 모를 수도 있다.


하지만 만약 ids.xml에 id들을 선언해놓는다면, 이 에러는 피해갈 수 있을 것이다.(이게 최고의 해결방법인지는 모르겠지만)



어쨌든 지금 분석하고 있는 nRF Toolbox 라는 어플리케이션의 코드에서는 ids.xml이라는 것을 사용한다. 난 그것과 관련된 오류 때문에 이 자료들을 찾아봤고.


...그냥 그렇다고.



참고


More Resource Types - ID(Google Developers)


what is ids.xml used for?

블로그 이미지

NCookie

,

액티비티에 객체를 전달하기 위해서는 이전에 다루었던 Intent와 Budnle을 사용해야 한다.


Bundle은 객체를 직렬화하여 저장하는데 serializble과 parcelable이라는 두 가지 방법이 있다.


이 둘은 뭐가 다른걸까. 둘 다 똑같이 객체를 직렬화한다. Serializable 또는 Parcelable 클래스를 implements 하여 클래스를 선언해야 하는 것도 같다. 다른 것은 성능과 사용 방법이다.



Serializable을 사용하는 방법은 간단하다. 클래스를 선언할 때 Serializable 클래스를 implements 하기만 하면 된다.


Serializable은 marker 인터페이스이기 때문에 implement 해야할 메소드도 없기 때문에 사용하기 엄청 편리하다. 대신 메커니즘이 많은 임시 객체를 생성하기 때문에 성능 저하를 일으킨다.



Parcelable은 이 반대다. 꽤나 많은 양의 boilerplate 코드가 있어 가독성이 떨어지고 유지보수가 어려워진다. 그 대가로 빠른 속도를 얻는다.



자세한 성능 비교는 Parcelable vs Serializable 에서 볼 수 있다. 


어쨌거나 결론은 Serializable을 많이 사용한다는 것이다. 성능이 떨어진다고 해도 Parcelable에 비해 딸린다는 것이지, 심각할 정도로 차이가 나는 것은 아니기 때문이다.



참고


14. Activity에 대해서 - 객체 직렬화편(Serializable)


Parcelable vs Serializable

'안드로이드' 카테고리의 다른 글

안드로이드 java.lang.NoSuchMethodError: No static method getFont  (0) 2017.11.28
안드로이드 리소스 ID  (0) 2017.11.27
안드로이드 Bundle  (0) 2017.11.24
안드로이드 Deprecated  (0) 2017.11.23
FLAG_ACTIVITY_NEW_TASK  (0) 2017.11.23
블로그 이미지

NCookie

,

onCreate 함수를 사용하면서 Bundle 이 뭘까 하는 생각은 많이 해보았지만 막상 찾아본적은 없었다. 그래서 찾아보았다.


Bundle은 Key-Value의 쌍으로 이루어진 일종의 해쉬맵 자료형이다.


이 Bundle은 여러 상황에서 유용하게 사용될 수 있다.


1. 액티비티 간의 데이터 전달


Intent를 사용하여 액티비티끼리 데이터를 교환할 수 있는데, 만약 넘길 데이터가 있다면 URI 형식이나 putExtras() 함수를 이용한다.


그러나 객체 같은 경우 위와는 다른 방법을 필요로 한다. 이 때 Bundle을 사용한다.


처음에는 일부 글만 보고 Bundle에서는 parcelable만 지원하는줄 알았는데 serializable도 지원한다. 자세한 함수에 대해서는 공식 문서를 참조하자.

(객체 직렬화에 대해서는 다른 글에서 자세히 다룰 것이다)


어쨌든 Bundle이라는 자료형을 Intent에 넣어 데이터를 다른 액티비티에게 전달할 수 있다.


2. 어플리케이션의 이전 상태 저장


onCreate 함수를 선언할 때 볼 수 있는 Bundle savedInstance라는 매개변수가 바로 이것을 위해 존재하는 것이다.


보통 메모리가 부족하거나 화면이 가로, 세로 전환되었을 때 ShutDown 된다. 


이런 경우 앱을 다시 실행했을 때 기존의 데이터는 다 날라가게 되는데, 만약 중요한 데이터를 입력 또는 저장 중이었다면 큰 낭패일 것이다. 그래서 Bundle을 이용해 값을 저장할 수 있다.


onSaveInstanceState 와 onRestoreInstanceState 함수를 오버라이딩해서 값을 저장하고 보관한 데이터를 불러올 수 있다.



참고


Android Bundle Class


[안드로이드] Activity,Fragment 에서 Bundle 로 Object, ArrayList 넘기기


Bundle(번들)이란?


Parcelable을 사용한 오브젝트 전달 (Object serialization using Parcelable)


[ANDROID] 안드로이드 BUNDLE

'안드로이드' 카테고리의 다른 글

안드로이드 리소스 ID  (0) 2017.11.27
serializable, parcelable  (0) 2017.11.24
안드로이드 Deprecated  (0) 2017.11.23
FLAG_ACTIVITY_NEW_TASK  (0) 2017.11.23
안드로이드 Intent, Task  (0) 2017.11.22
블로그 이미지

NCookie

,


Nordic의 DFU 라이브러리에서 설정값을 가져오는데, 'SETTINGS_NUMBER_OF_PACKETS' is deprecated 라면서 해당 상수에 가로줄이 그어졌다. 


찾아보니 이 deprecated 는 안드로이드 버전이 업그레이드 되면서 다른 것으로 대체된 경우, 지원이 끊긴 경우 등에서 발생한다.


deprecated가 되었어도 계속 지원을 해주는 경우도 있다고는 하지만, 가장 좋은 방법은 현재 디바이스의 버전을 확인하여 그에 따라 호환성을 제공하는 것이다.


지금 문제가 되는 DFU 라이브러리에서 상수가 정의된 부분을 찾아보았더니, 


This constant is now deprecated. Please, use {@link DfuServiceInitiator#setPacketsReceiptNotificationsValue(int)} to set it.

라고 한다. 


Nordic의 github에 있는 nRF-Toolbox의 코드(공식 어플이다)를 보면, 다음과 같이 설정한다. 

final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final SharedPreferences.Editor editor = preferences.edit();
final boolean keepBond = preferences.getBoolean(SettingsFragment.SETTINGS_KEEP_BOND, false);
final boolean forceDfu = preferences.getBoolean(SettingsFragment.SETTINGS_ASSUME_DFU_NODE, false);
final boolean enablePRNs = preferences.getBoolean(SettingsFragment.SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED,
                                                Build.VERSION.SDK_INT < Build.VERSION_CODES.M);
String value = preferences.getString(SettingsFragment.SETTINGS_NUMBER_OF_PACKETS,
                                            String.valueOf(DfuServiceInitiator.DEFAULT_PRN_VALUE));

int numberOfPackets;
try {
    numberOfPackets = Integer.parseInt(value);
} catch (final NumberFormatException e) {
    numberOfPackets = DfuServiceInitiator.DEFAULT_PRN_VALUE;
}

final DfuServiceInitiator starter = new DfuServiceInitiator(mSelectedDevice.getAddress())
        .setDeviceName(mSelectedDevice.getName())
        .setKeepBond(keepBond)
        .setForceDfu(forceDfu)
        .setPacketsReceiptNotificationsEnabled(enablePRNs)
        .setPacketsReceiptNotificationsValue(numberOfPackets)
        .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true);
 


SETTINGS_PACKET_RECEIPT_NOTIFICATION_ENABLED 상수의 경우, DfuSettingsConstants.java 파일에 "settings_packet_receipt_notification_enabled" 라는 문자열로 저장되어 있다. 그래서 자동으로 defValue인 false가 enablePRNs라는 변수에 들어가게 되는 것이고.


참고로 SharedPreference란 UI에 대한 간단한 정보들을 저장할 수 있는 임시 저장소 같은 공간이다. 여기에 저장된 값들은 화면이 전환되었을 때 이전 값을 다시 세팅해주는 등을 하는데에 사용될 수 있다.



참고


[안드로이드] Deprecated 되는건 기준이 뭐죠?


안드로이드 deprecated

'안드로이드' 카테고리의 다른 글

serializable, parcelable  (0) 2017.11.24
안드로이드 Bundle  (0) 2017.11.24
FLAG_ACTIVITY_NEW_TASK  (0) 2017.11.23
안드로이드 Intent, Task  (0) 2017.11.22
마시멜로우 권한 요청 시 [화면 오버레이 감지됨] 발생  (0) 2017.11.20
블로그 이미지

NCookie

,

이전 글에서 설명했던 Intent Flag와 관련된 내용이다.



Activity Affinity


먼저 Activity Affinity라는 개념이 있는데, 액티비티와 태스크를 관리하기 위해 사용한다.


기본적으로 AndroidManifest의 package 명으로 설정되어 있다.


보통 같은 어플리케이션의 액티비티들은 같은 affinity를 가지고 있고 하나의 태스크에서 관리하게 된다.



FLAG_ACTIVITY_NEW_TASK


새 액티비티는 기본적으로 자신을 호출한 액티비티와 같은 태스크에서 관리가 된다.


이 때 FLAG_ACTIVITY_NEW_TASK라는 플래그를 사용하면 새로운 태스크 또는 같은 Affinity를 가지는 태스크로 가게 된다.


만약 FLAG_ACTIVITY_MULTIPLE_TASK 플래그와 함께 사용하면 무조건 새로운 태스크를 생성한다.



예시


앱 A에 액티비티 A1, 앱 B에 액티비티 B1, B2가 존재한다고 하자.


Case 1) 앱 A 실행 후 A1에서 B1 호출


B1은 A1과 같은 태스크에서 관리가 된다.


따라서 B1에서 Back을 하면 A1이 나타난다.


TASK1 : A1 - B1



Case 2) 앱 A 실행 후 A1에서 B1 호출 + FLAG_ACTIVITY_NEW_TASK


B1과 같은 affinity를 가지고 있는 태스크가 현재 존재하지 않기 때문에 태스크를 새로 생성하고 B1은 그 태스크의 Root Activity가 된다.


TASK1 : A1

TASK2 : B1



Case 3) 앱 A, B 실행 후 A1에서 B2 호출 + FLAG_ACTIVITY_NEW_TASK


(이 때 앱 B를 실행하였기 때문에 B1은 TASK3 이라는 태스크에 존재한다)


B2와 같은 Affinity를 가진 태스크가 존재한다.


B2는 B1과 같은 태스크에서 관리되고, B2에서 Back을 했을 때 B1이 보여지게 된다.


TASK1 : A1

TASK3 : B1 - B2



참고


[Android] Activity 생성시에 사용되는 Intent Flag 정리


Activity Affinity, FLAG_ACTIVITY_NEW_TASK

블로그 이미지

NCookie

,

안드로이드 시스템에서 컴포넌트 간의 호출과 메시지 전달에 사용된다.


인텐트를 통해 화면을 전환하거나 값을 전달할 수 있다.


호출될 대상을 명시하냐 아니냐에 따라 명시적 인텐트(Explicit Intent)와 암시적 인텐트(Implicit Intent)로 나누어진다.


명시적 인텐트(Explicit Intent)


말 그대로 호출될 컴포넌트를 명시한다.


만약 호출한 컴포넌트가 반환된 뒤 값을 받아오고 싶다면, startActivity() 대신에 startActivityForResult()를 사용한다. 그 후, 콜백 함수인 onActivityResult()를 오버라이드하여 반환된 값을 사용할 수 있다.


암시적 인텐트(Implicit Intent)


호출할 컴포넌트를 명시하지 않는 대신, 호출 대상 컴포넌트의 속성을 정의한다.


여기에는 Action, Category, Tpye 등이 있다.


Action은 말 그대로 호출된 컴포넌트가 수행할 동작이고, Category는 이 Action을 좀 더 보충해주는 설명이라고 보면 된다.


컴포넌트가 처리하기를 원하는 데이터들은 주소록, 사진, 텍스트 등 여러 종류가 있는데, 이는 Type에서 정의된다. 이 때 전달되는 데이터는 URI 형식으로 정의되어 있으며, 주소를 전달한다.


이 외에 직접 전달할 데이터가 있다면 putExtra() 등을 사용하면 된다.



Intent Filter


암시적 인텐트를 사용할 때 적용되며, AndroidManifest의 intent-filter라는 태그 내에서 정의할 수 있다. 


사용되는 필터로는 action, category, data, type 등이 있다. 



Task(Activity Stack)


인텐트를 사용하여 다른 액티비티들을 호출하다가, 다시 Back 키를 누르면 이전에 사용했던 액티비티들이 보인다. 이는 Task 라는 곳에 스택 형식으로 액티비티가 쌓이기 때문이다.


이러한 Task는 메모리 상에 여러 개가 존재할 수 있으며 서로 독립적이다.


가장 아래에 있는 액티비티를 Root Activity라고 하며, 가장 상단에 있는 액티비티가 현재 사용자와 상호작용하고 있는 액티비티이다.


이렇게 순차적으로 쌓이는(stack) Task에서, 순서를 바꾸거나 제어 등을 하고 싶다면 Intent 객체에서 Flag를 설정하면 된다.



Flag


이 Flag를 사용하여 Task 내 액티비티의 흐름을 제어할 수 있다. AndroidManifest나 소스 코드에서 직접 설정할 수 있다.


Flag에는 여러 종류가 있으며, 자세한 내용은 알아서 찾아보자. 엄청 많다.



참고


- 인텐트(Intent)란 무엇인가?

- 어플리케이션 컴포넌트의 실행 : 인텐트(Intent)


- 내가 누군지 말해줘! - 인텐트 필터(Intent Filter)


- 액티비티와 태스크(Task)


[Android] Activity 생성시에 사용되는 Intent Flag 정리

안드로이드 Intent Flag 정리(Activity 관리)

블로그 이미지

NCookie

,

안드로이드에서 사용자에게 권한 요청을 거부당한 후, 다시 요청했을 때 [화면 오버레이가 감지됨] 이라는 메시지가 뜨면서, 권한 설정도 되지 않았다.


이는 권한을 허가하지 않았을 때 앱의 기능을 사용하지 못한다는 Toast와 권한 요청 다이얼로그가 동시에 띄어졌기 때문에 발생하는 문제였다. 


이 둘의 호출  시점을 조정해주니 문제가 해결되었다.



참고

https://stackoverflow.com/questions/35453759/android-screen-overlay-detected-message-if-user-is-trying-to-grant-a-permissio

'안드로이드' 카테고리의 다른 글

FLAG_ACTIVITY_NEW_TASK  (0) 2017.11.23
안드로이드 Intent, Task  (0) 2017.11.22
마시멜로우 블루투스 스캔  (0) 2017.11.09
[안드로이드 스튜디오] 유용한 단축키 모음  (0) 2016.09.23
안드로이드 LinearLayout  (0) 2016.06.29
블로그 이미지

NCookie

,

안드로이드 버전 6.0 마시멜로우(API 23)부터 와이파이나 블루투스 스캔으로 근처의 기기를 탐색하기 위해서는 앱에 ACCESS_FINE_LOCATION 또는 ACCESS_COARSE_LOCATION 권한이 있어야한다.


안드로이드 공식


https://developer.android.com/about/versions/marshmallow/android-6.0-changes.html#behavior-hardware-id



사용자의 버전에 따라 권한 부여


https://stackoverflow.com/questions/33052811/since-marshmallow-update-bluetooth-discovery-using-bluetoothadapter-getdefaultad

블로그 이미지

NCookie

,