반응형

ndk-build를 이용한

Native Code 빌드 및 사용방법

 

다음과 같은 이유로 Android App에서 Native Code(C/C++등)로 코드를 작성하여 사용해야 할 경우가 존재합니다.

  • 처리 속도 및 성능향상
  • C/C++로 만들어진 코드 재사용
  • Native Code로만 개발 가능한 기능 구현등

 

본 포스팅에서는 Android Studio에서 Native Code를 작성하고 빌드하는 방법에 대해 "최대한 많은 스크린 샷"을 통해 설명하고자 합니다.

작성 예제는 Native코드를 포함한 라이브러리(ndklib)와 이 라이브러리를 사용하는 app(ndklibdemo)으로 모듈을를 구분하여 작성 하도록 하겠습니다.

 

프로젝트 및 라이브러리 생성

1. 프로젝트 생성

프로젝트 타입을 'Empty Activity'로 선택합니다.

 

적당한 프로젝트 이름을 지정합니다. "ndklibdemo"로 설정했습니다.

 

2.라이브러리 모듈생성

[File>New>New Module..] 메뉴를 통해 새로운 라이브러리 모듈 추가

(Native Code가 작성될 모듈입니다.)

"Android Library"를 선택합니다.
적당한 라이브러리 명을 지정합니다. "ndklib"로 지정했습니다.
프로젝트에 ndklib 라이브러리가 추가 되었습니다.

 

Native Code 빌드를 위한 툴킷 설치

1. NDK  설치

1) [Tool>SDK Manager] 메뉴를 통해 프로젝트 설정 창을 열고 [SDK Tools]탭을 선택후,

[NDK] 항목을 체크해서 설치합니다.

(1) Android NDK는 Native Development Kit의 약자로, C / C++과 같은 Native 언어를 사용하여 앱의 일부를 구현할 수있게 해주는 개발 툴킷입니다.

 

NDK 빌드환경 구성 및 코드 작성

1. C/C++ 소스 폴더 생성

1) 프로젝트 표시창의 뷰타입을 "Project"로 변경 후,

ndklib 라이브러리의 [ src/main ] 하위에 jni 폴더를 생성합니다.

2. NDK 빌드환경 구성

1) [File>Setting] 메뉴을 통해 설정 다이얼로그를 연후, [Tool>External Tools] 항목을 선택합니다.

본 스크린 샷에는 이미 설정된 3개의 구성이 이미 존재합니다. 이 3개의 구성을 아래의 설명대로 생성합니다.

2) NDK Group에 세가지 환경을 추가합니다. 각각의 역할은 아래와 같으며 각 설정방법은 아래에 기술합니다.

(1) ndklib-javah

- Java Class를 기반으로 Jni규격에 맞게 헤더파일을 생성해주는 역할

(2) ndklib-build

- Native(C/C++) 코드를 빌드하는 구성

(3) ndklib-clean

- Native(C/C++) 코드의 빌드 결과물을 지워주는 구성 

3) [ + ] 버튼을 눌러 각 구성을 추가합니다.

1) ndklib-javah

(1) Group: NDK

(2) Program: javah.exe의 경로를 적어 줍니다.

$JDKPath$\bin\javah.exe

(3) Arguments: javah.exe 실행시 전달되는 파라미터 입니다. 그냥 아래 내용대로 적어 줍니다.

-classpath "$Classpath$" -v -jni $FileClass$

(4) Working directory: Native Code 소스파일을 작성할 폴더 경로를 적어 줍니다.

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main\jni

- 위에서 생성한 jni폴더를 지정합니다.

- 지정한 경로에 헤더파일 생성됩니다.

2) ndklib-build

(1) Group: NDK

(2) Program: ndk-build.cmd의 경로를 적어 줍니다.

C:\Users\unodi\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

(3) Working directory: Library module의 src/main 폴더 경로를 적어 줍니다. 

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main

(3) ndklib-clean

(1) Group: NDK

(2) Program: ndk-build.cmd의 경로를 적어 줍니다.

C:\Users\unodi\AppData\Local\Android\Sdk\ndk-bundle\ndk-build.cmd

(3) Arguments:

clean

(4) Working directory: Library의 main 폴더 경로를 적어 줍니다. 

D:\Git-Repos\Android\ndklibdemo\ndklib\src\main

 

Native Code 작성

1. Native Wrapping Java Class 작성

1) [라이브러리>java>패키지]에 Native API를 Wrapping하는 Java Class를 작성합니다.

Class 이름을 "NativeWrapper"로 지정했습니다.

2) 생성된 Wrapping class에 라이브러리를 로드하는 기본 코드를 구현합니다.

static {
   System.loadLibrary("ndklib");
}
"ndklib"부분은 생성된 라이브러리의 이름을 기술 합니다.
public native int nativeSum(int a, int b);
native로 구현할 함수의 원형을 기술 합니다.
이 선언을 기반으로 javah가 Jni규격에 맞는 함수명을 생성하게 됩니다.

2. Native 코드 작성

1) Header 파일 생성

위에서 생성한 클래스에서 오른쪽 마우스를 누른후, [NDK>ndklib-javah] 메뉴를 선택해서 헤더파일을 생성합니다.

아래와 같이 class파일을 찾지 못한다고 표시되는 경우, 프로젝트를 한번 빌드한후 다시 시도합니다.

정상적으로 수행되면 아래와 같이 *.h 파일이 생성됩니다.

생성된 header파일은 [jni] 폴더에서 확인 할 수 있습니다.(ndklib-javah 환경 구성에서 지정한 경로 입니다.)

Jni 규격에 맞게 함수명과 파라미터가 작성됩니다.

2) cpp 파일 구현

헤더파일이 위치한 [jni]폴더에 cpp파일을 생성하여 함수내용을 구현합니다.

#include "com_thirteenrain_ndklib_NativeWrapper.h"

JNIEXPORT jint JNICALL Java_com_thirteenrain_ndklib_NativeWrapper_nativeSum
(JNIEnv *env, jobject obj, jint a, jint b)
{
     return a+b;
}

 

Gradle연동

1. android.mk 생성

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ndklib

FILES := NativeSum.cpp

LOCAL_SRC_FILES := $(FILES)

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)
"ndklib" - 라이브러리 이름을 기술합니다.
NativeSum.cpp - 빌드할 CPP 파일을 기술합니다.

2. NDK build - gradle 연동

1) Java Wrapping Class를 오른쪽 클릭하여 NDK Build를 Gradle과 연동합니다.

(1) Build System: ndk-build

(2) project Path: 위에서 생성한 Android.mk를 선택합니다.

이 작업을 통해 ndklib 모듈의 gradle에 NativeBuild 구문이 추가되어, Gradle빌드시 ndkBuild가 함께 진행되도록 설정됩니다.

android {
     externalNativeBuild {
          ndkBuild {
               path file('src/main/jni/Android.mk')
           }
     }
}

이상으로 Native Code 라이브러리 생성을 위한 설정이 완료되었습니다.

 

Native Code Library Build

1. Java Wrapping Class 우클릭 > NDK > ndklib-build 메뉴를 통해 Native Code를 빌드 할 수 있습니다.

2. ndklib/src/main/libs 하위에 라이브러리 파일(*.so)이 생성됩니다.

 

Native Library 사용

1. 코드 작성

1) 테스트를 위해 MainActivity의 onCreate 메소드에 ndklib의 NativeWapper.nativeSum을 호출하는 코드를 삽입합니다.

NativeWrapper nativeApi = new NativeWrapper();
int result = nativeApi.nativeSum(5, 10);

2. dependency 추가

1) [Alt+Enter] 단축키로 ndklib에 대한 dependency를 추가해 줍니다.

2) ndklib에 대한 dependency가 추가되었습니다.

디버깅

1) 디버깅 Tool설치

(1) LLDB

: Native Debugging을 위해서는 LLDB를 설치해야 합니다.(LLDB는 Android Studio에서 사용하는 Native Debugger입니다.)

2) Debug type

(1) Debug type이 [java]로 설정되어 있으면 Native Code에 대한 디버깅이 되지 않습니다.

: Java - Java Code만 디버깅 합니다.

: Auto - 디버깅 대상을 자동으로 판단합니다.

: Native - Native Code만 디버깅 합니다.

: Dual - Java, Native 둘다 디버깅 합니다.

 

실행확인

1) cpp 소스에 중단점을 설정합니다.

2) 툴바의 디버그 버튼 혹은 [Shit+F9] 단축키를 눌러 디버그를 진행합니다.

3) 중단점에 적중되어 Native Code 디버깅이 가능합니다.

 

반응형
반응형

RecyclerView #3 - 컨텍스트 메뉴 처리


RecyclerView #1- 구조및 기본 사용법

RecyclerView #2 - 구분선 추가, 아이템간 간격 조절

RecyclerView #3 - 컨텍스트 메뉴 처리

RecyclerView #4 - 아이템 클릭 처리

RecyclerView #5 - 아이템 선택 처리하기

RecyclerView #6 - ItemView를 클래스화 하기

RecyclerView #7 - ViewType 동적변경


CreateContextMenu 리스너 설정


public static class StdViewHolder extends RecyclerView.ViewHolder
implements View.OnCreateContextMenuListener{
}
public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

itemView.setOnCreateContextMenuListener(this);
}


메뉴 구성

아래와 같이 동적으로 메뉴를 구성하거나, 리소스를 통해 메뉴를 inflate시키면 된다.
다만 메뉴를 Inflate 하기 위해 필요한 Context는 어딘가 에서 넘겨받거나 구해와야....

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuItem Edit = menu.add(Menu.NONE, R.id.add_menu, 1, "add");
MenuItem Delete = menu.add(Menu.NONE, R.id.delete_menu, 2, "delete");
Edit.setOnMenuItemClickListener(onMenuItemClickListener);
Delete.setOnMenuItemClickListener(onMenuItemClickListener);

}

메뉴 선택 처리

선택된 아이템에 대해 처리한다.
private final MenuItem.OnMenuItemClickListener onMenuItemClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_menu:
return true;

case R.id.delete_menu:
return true;
}
return false;
}
};

전체코드

public class StdRecyclerAdapter extends RecyclerView.Adapter<StdRecyclerAdapter.StdViewHolder> {


Context mContext;
List<String> mdata;
RecyclerView recyclerView;

public StdRecyclerAdapter(Context context) {
this.mContext = context;
}

public void setData(List<String> data) {
mdata = data;
notifyDataSetChanged();
}

@NonNull
@Override
public StdViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflate = LayoutInflater.from(mContext);
View view = inflate.inflate(R.layout.list_item, parent, false);

StdViewHolder vh = new StdViewHolder(view);
return vh;
}

@Override
public void onBindViewHolder(@NonNull StdViewHolder holder, int position) {
holder.textView.setText(mdata.get(position));
}

@Override
public int getItemCount() {
return mdata.size();
}


public static class StdViewHolder extends RecyclerView.ViewHolder
implements View.OnCreateContextMenuListener{
public TextView textView;

public StdViewHolder(@NonNull View itemView) {
super(itemView);
this.textView = itemView.findViewById(R.id.textView);

itemView.setOnCreateContextMenuListener(this);
}

@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
MenuItem Edit = menu.add(Menu.NONE, R.id.add_menu, 1, "add");
MenuItem Delete = menu.add(Menu.NONE, R.id.delete_menu, 2, "delete");
Edit.setOnMenuItemClickListener(onMenuItemClickListener);
Delete.setOnMenuItemClickListener(onMenuItemClickListener);
}

private final MenuItem.OnMenuItemClickListener onMenuItemClickListener = new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_menu:
return true;

case R.id.delete_menu:
return true;
}
return false;
}
};
}
}


반응형
반응형

복잡해 보이지만, 알고보면 간단한 Navigation Drawer Activity의 구조


안드로이드 모바일 앱에서 많이 보이는 Navigation Drawer Activity의 '레이아웃 구조'에 대해 정리 합니다.


뜯어보면 몇가지 레이아웃을 조합해서 만들어 놓은 UI 형태인데,

처음 접할때는 전체 구조가 머리속에 잘 안들어 오더라구요.



Navigation Drawer는 Android 프로젝트 생성시에도 기본으로 제공되는 Activity 타입니다.



Navigation Drawer Activity타입으로 프로젝트를 생성하면


기본으로 네개의 레이아웃을 생성해 주는데요, 각 레이아웃에 대해 간단히 분석해 보겠습니다.


(* app_bar_main.xml에 생성되는 FloatingActionButton은 설명의 편의를 위해 삭제 했습니다.)


[activity_main.xml]


DrawerLayout안에 app_bar_main을 포함하고 NavigationView를 배치한 형태 입니다.
app_bar_main은 include 형태로 포함되어 있으며, 

<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />


nav_header_main은 NavigationView의 headerLayout 속성으로 설정되어 NavigationView가 그려질때 Header 영역에 그려지게 됩니다.
하위에 표시되는 메뉴는 activity_main의 app:menu="@menu/activity_main_drawer" 에 의해서 결정됩니다.

 

<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/activity_main_drawer" />


@drawer_layout ( DrawerLayout )

DrawerLayout은 윈도우의 한 쪽 혹은 양쪽 모서리에서 끌어낼 수있는 '서랍(Drawer)' 형태의 뷰와 

인터렉션을 위한 윈도우의 최상위 컨테이너 역할을 하는 레이아웃 입니다. ( ???? )

activity_main.xlm의 전체를 감싸고 있는 레이아웃이 DrawerLayout(@drawer_layout)위젯이며 

아래와 같은 코드를 통해 ActionBarDrawerToggle 인스턴스와 결합됩니다.




뿐만 아니라, Navigation View를 열거나 닫기 위해 다음과 같이 사용됩니다.




@nav_view ( Navigation View )

Navigation View는응용 프로그램의 표준 탐색(Navigation) 메뉴를 대표(?) 합니다. (메뉴 내용은 메뉴 리소스 파일로 채울 수 있습니다.)
NavigationView.OnNavigationItemSelectedListener 리스너를 설정하여, navigation menu 선택에 따른 동작을 처리 할 수 있습니다.




nav_header_main.xml

보통 홈버튼을 누르게 되면 펼쳐지는 Navigation뷰의 Header 부분을 표시하는 레이아웃 입니다.

이 레이아웃은 Navigation View의 header 영역을 표시하는 용도로 사용됩니다.


(* 포함된 ImageView나 TextView등의 위젯은 본 주제에서는 큰 의미가 없으므로 설명은 스킵합니다.)



app_bar_main.xml

Navigation View가 펼쳐지기 전에 전체 스크린으로 보게 되는 레이아웃 입니다.
여기부터는 일반적으로 구성되는 기본 레이아웃과 크게 다르지 않습니다. 
상단에 toolbar를 품은 AppBarLayout 을 배치하고, 
아래에 실제 내용을 보여줄 content_main을 include 하고 있습니다.

<include layout="@layout/content_main" /> 



content_main.xml

실제 보여주고자 하는 내용을 표현하게 될 content_main layout입니다.
ConstraintLayout이 content_frame을 감싸고 있습니다.


Navigation Drawer Layout은 일반적으로 Navigation 메뉴를 선택시 Fragment를 교체하는 형태로 사용되는데,

다름과 같은 코드로 content_frame을 교체하는 방식으로 사용된다.

case R.id.nav_tools {

FragmentTransaction ft = getFragmentManagerInstance().beginTransaction();

Fragment fragment = new ToolFragment();

ft.replace(R.id.content_frame, fragment, TOOL_FRAGMENT_TAG);
ft.commit();
break;

}


Navigation Drawer ...

영어를 그대로 읽고 쓰다 보니 생각해 본적이 없었는데

직역하면 '탐색 서랍' 인가요???



반응형

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

RecyclerView #1- 구조 및 기본 사용법  (0) 2019.03.13
코드 모음  (0) 2019.03.08
Android resource compilation failed  (3) 2019.02.23
Activity간 Object 공유- Parcelable  (0) 2019.02.21
EditText 포커스 문제  (0) 2019.02.13
반응형

Activity간의 Object 공유 - Parcelable

 

하나의 Activity에서 다른 Activity로 데이터를 전달해야 하는 경우에

데이터를 전달하는 Activity에서는 Intent Class의 putExtra() 메소드를 이용해서 데이터를 세팅하고,

받는 쪽에서는 getExtra() 메소드를 이용해서 받게 된다.

 

puExtra()을 이용해서 전달할수 있는 데이터 타입은 대략 다음과 같다.

 

 

ActivityA에서 Book Class의 인스턴스를 관리하고,

ActivityB에서 Book Class의 정보를 UI에 표시하는 상황을 가정해 보자.

 

Book Class는 아래와 같다.

public class Book {
    int serial;
    String isbn;
    String title;
    Bitmap image;
}

 

Book Class의 전체 프라퍼티를 ActivityA에서 ActivityB로 전달하려면 아래와 같이, 프라퍼티 별로 하나씩 put()하고 get()할 수 밖에 없다.

MainActivity

intent.putExtra("serial", book.getSerial());
intent.putExtra("isbn", book.getIsbn());
intent.putExtra("title", book.getTitle());
intent.putExtra("image", book.getImage());
BookActivity

int serial = intent.getIntExtra("serial", 0);
String isbn = intent.getStringExtra("isbn");
String title = intent.getStringExtra("title");
Bitmap image = intent.getParcelableExtra("image");

 

아래와 같이 사용하고 싶은 생각이 들것이다.

MainActivity

Book book = new Book(1, "1-1-1-1", "슬럼독 밀리어네어", bitmap);

Intent intent = new Intent(getApplicationContext(), BookActivity.class);
intent.putExtra("book", book);
BookActivity

Book book = intent.getExtra("book");

 

우선은 Intent  putExtra() 메소스 중, 아래의 두가지 함수원형을 눈여겨 보자!

putExtra( String name, Parcelable value );
putExtra( String name, Serializable value);

우리가 전달하고자 하는 Class가 Serializable 하거나 Parcelable 하게 만들 수 있다면 가능할것 같지 않은가?

 

구글링을 하다 보니 도착한 Goolgle의 가이드를 보면( 링크를 잃어 버렸다),

Serializable 보다는 Parcelable을 권장하는 듯 하니 Parcelable 을 사용하는 방법을 확인해 보기로 하자.

(Serializable의 Java의 표준 인터페이스이며, Parcelable의 안드로이드 SDK에 포함된 인터페이스 이다)

 

Parcelable은 IPC를 이용하여 데이터를 공유하며 Serialiable과는 달리 리플렉션을 사용하지 않으므로

성능면에서 유리하다고 한다.

반면 전달하는 데이터의 해석코드를 필수적으로 작성해야 하므로, 유지보수 측면에서는 상대적으로 귀찮고

번거로운 작업이 동반된다.

 

개략적으로 개념을 잡아 보자면

여러 Activity가 접근할 수 있는 영역(아마도 커널 메모리)에 

1. 전달하고자하는 데이터를 순차적으로 저장 한다.

2. 저장된 데이터를 순차적으로 읽어서 해석할 수 있는 방법을 제공한다.

* 다만 데이터를 쓰고, 읽는 방법은 Parcelable Interface가 지원하는 메소드를 통해서만 가능하다.

* 쓰여진 데이터를 해석하는데 있어 데이터가 쓰여진 순서대로 읽어야 복원이 가능하기 때문에

Write한 순서와 Read한 순서를 일치시키는 것은 반드시 지켜져야한다.

* 개념적으로 직렬화/마샬링과 유사한 개념으로 생각하면 이해가 편할것 같다.

직렬화(serialization)

마샬링(Marshalling)

 

구현 방법은 다음과 같다.

1. 공유하고자 하는 Class가 Parcelable Interface를 상속받도록 구현한다.

public class Book implements Parcelable {
    int serial;
    String isbn;
    String title;
    Bitmap image;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    }
}

 

2. 필수 추상 메소스를 구현한다.

* writeToParcel() 메소드가 전달할 데이터를 기록하는 메소드의 구현이다.

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(serial);
        dest.writeString(isbn);
        dest.writeString(title);
        dest.writeParcelable(image, flags);
    }

 

3. CREATOR를 구현한다.

public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        public Book[] newArray(int size) {
            return new Book[size];
        }
};

 

4. 생성자를 구현한다.

* 단순히 생성자라기 보다는 Parcelable data를 해석할 수 있는 방법의 구현이다.

public Book(Parcel src) {
        readFromParcel(src);
    }

public void readFromParcel(Parcel src) {
    serial = src.readInt();
    isbn = src.readString();
    title = src.readString();
    image = src.readParcelable(Bitmap.class.getClassLoader());
}

public Book(int serial, String isbn, String title, Bitmap image) {
    this.serial = serial;
    this.title = title;
    this.isbn = isbn;
    this.image = image;
}

 

전체 코드는 다음과 같다.

Book.java

( getter/setter의 구현은 필수 적이지 않다 )

package com.tistory.thepassion.pacelable;

import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    int serial;
    String isbn;
    String title;
    Bitmap image;

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(serial);
        dest.writeString(isbn);
        dest.writeString(title);
        dest.writeParcelable(image, flags);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    public Book(Parcel src) {
        readFromParcel(src);
    }

    public void readFromParcel(Parcel src) {
        serial = src.readInt();
        isbn = src.readString();
        title = src.readString();
        image = src.readParcelable(Bitmap.class.getClassLoader());
        // image = Bitmap.CREATOR.createFromParcel(src);
    }

    public Book(int serial, String isbn, String title, Bitmap image) {
        this.serial = serial;
        this.title = title;
        this.isbn = isbn;
        this.image = image;
    }

    public int getSerial() {
        return serial;
    }

    public void setSerial(int serial) {
        this.serial = serial;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(String isbn) {
        this.isbn = isbn;
    }

    public Bitmap getImage() {
        return image;
    }

    public void setImage(Bitmap image) {
        this.image = image;
    }
}

 

MainActivity.java

package com.tistory.thepassion.pacelable;

import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    Book book;

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

        Resources res = getResources(); Drawable d = res.getDrawable(R.drawable.book);
        Bitmap bitmap = ((BitmapDrawable) d).getBitmap();

        book = new Book(1, "1-1-1-1", "슬럼독 밀리어네어", bitmap);

        Intent intent = new Intent(getApplicationContext(), BookActivity.class);
        intent.putExtra("book", book);

        startActivity(intent);
    }
}

 

ActivityB.java

package com.tistory.thepassion.pacelable;

import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.TextView;

public class BookActivity extends AppCompatActivity {

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

        Intent intent = getIntent();

        Book book = intent.getParcelableExtra("book");
        ((TextView) findViewById(R.id.txtSerial)).setText(String.valueOf(book.getSerial()));
        ((TextView) findViewById(R.id.txtIsbn)).setText(book.getIsbn());
        ((TextView) findViewById(R.id.txtTitle)).setText(book.getTitle());
        ((ImageView) findViewById(R.id.imageView)).setImageBitmap(book.getImage());
    }
}

 

 

지금까지 읽어 내려오면서 궁금한 점이 있었는지 모르겠다.

Parcelable을 구현하기 전의 첫번째 예제에서도 Bitmap 객체를 넘겼었고,

마지막의 전체 구현 코드에서도 Bitmap 객체를 전달하고 있다.

 

예상한것처럼 Bitmap Class가 Parcelable을 implement하고 있기 때문이다.

public final class Bitmap implements Parcelable {
    private static final String TAG = "Bitmap";

    ....
    ....
}

 

Parcelable 을 통해 Object를 공유하고자 할때, 프라퍼티로 포함하고 있는 Class 또한

Parcelable 혹은 Serializable로 구현되어야 한다.

(binary data를 뽑아서 저장하고 복원할 수 있도록 한다면 불가능 하다고 할수는 없으니, 꼭 그런것은 아니다.)

 

사실 Bitmap을 포함한 Class를 다른 Activity로 넘기고 싶어 내용을 확인하고 정리하는 것이기는 한데...

Parcelable을 통해 전달할 수 있는 데이터의 사이즈에는 제한이 있다.

1MB 이상의 데이터를 전달하고자 할때 다음과 같은 에러가 발생할 수 있다.

Caused by: android.os.TransactionTooLargeException: data parcel size 1204788 bytes

 

1MB 제한은 구현한 Parcelable객체 하나당이 아닌 Activity가 속한 프로세스 전역에 걸친 한계 용량으로,

Parcelable을 통해 큰 사이즈의 데이터를 전달하는것은 적합하지 않다.

대략 50kb 미만의 데이터 사이즈를 권장하고 있다.

https://developer.android.com/guide/components/activities/parcelables-and-bundles

 

다른 측면에서 생각해보면

android.provider.MediaStore.ACTION_IMAGE_CAPTURE 를 이용해 촬영한 이미지를 얻을때,

onActivityResult 를 통해 섬네일이미지는 획득이 가능하지만, 

풀이미지를 얻기 위해서는 별도의 Uri를 통해야하는 이유를 이해할 수 있다. 

 

결국 bitmap을 전달해야 할 경우, 

Bitmap 객체가 아닌 파일 경로 혹은 대상 bitmap파일을 불러올수 있는 key를 전달하는 방식으로 우회해야 할것같다.

반응형

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

Navigation Drawer 의 Layout구조  (2) 2019.02.27
Android resource compilation failed  (3) 2019.02.23
EditText 포커스 문제  (0) 2019.02.13
Android Studio 단축키  (0) 2018.12.21
Android Studio 화면구성  (0) 2018.05.13
반응형

안드로이드 애플리케이션 UI디자인&프로그래밍

도카시키 마모루 지음

이윤혜 옮김

정보문화사





저자가 개발한 안드로이드 앱을 실제 사례로, 아이템 구상부터 화면디자인, 개발, 운영까지의 개발프로세스 전반을 예제로 설명한 책.


아이템 구상부터 이미지 제작, 프로그래밍 까지... 따라하긴 쉽지 않다.


그래도 실제 앱을 구상하고 개발 출시하는 과정을 순차적으로 예시하고 있어 읽어보면 도움이 된다.


목차

1. 아이디어 구상

2. 실현가능성 확인

3. 아이콘 디자인

4. 리소스 제작

5. UI 설계

6. 레이이웃 XML 작성

7. 아키텍처 설계

8. 프로그래밍

9. 애플리케이션 제작 마무리

10. 구글플레이를 통한 배포

11. 피드백 분석



반응형
반응형

Activity 생성 시, EditText에 포커스가 가는 문제




의도치 않은 동작

Toolbar메뉴를 누르면

EditText 위젯이 포함된 Activity를 하나 생성해서 띄우도록 구현했는데

Activity가 생성 -> EditText에 포커스 -> 키패드 팝업

의 순서로 의도치 않은 동작이 수행되는 군요.



그렇다고 입력을 받아야 하는 EditTxt가 포커스를 갖지 못하게 할수도 없구요.

Activity 생성시 다른 위젯에 포커스를 강제로 가게 하는 것도 다른 문제가 있습니다.


어쩌면 꼼수

다음과 같이 처리하면 이러한 문제는 해결(?) 됩니다.


결국은 다른 위젯에 포커스를 가게 하는 방식입니다만,

화면상에 보이는 다른 위젯에 포커스를 주는게 아니라, 전체 화면을 감싸는 LinearLayout에 다음 속성을 통해 포커스를 주면 된다는 거죠


테스트를 해보니 android:focusableInTouchMode="true" 설정만으로도 현상이 사라지기는 하는데...

LinearLayout의 focusable 기본값이 true라는 얘긴가?

안전하게 둘다 쓰는것으로 하겠습니다.



*Update - 2019.04

- 테스트 해보지 않음

<activity android:windowSoftInputMode="stateHidden ">

stateHidden : activity 실행시 키보드가 자동으로 올라오는 것을 방지


default: adjustUnspecified | stateUnspecified

adjustUnspecified  : system이 적절한 키보드 상태를 설정, 혹은 테마에 따라 설정

stateUnspecified : system이 알아서 상황에 맞게 설정

반응형

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

Android resource compilation failed  (3) 2019.02.23
Activity간 Object 공유- Parcelable  (0) 2019.02.21
Android Studio 단축키  (0) 2018.12.21
Android Studio 화면구성  (0) 2018.05.13
자주쓰는 코드  (0) 2018.05.13

+ Recent posts