안드로이드 개발력 향상하기


안드로이드 앱개발에 필요한 팁과 학습방법에 관한 글입니다. 아래 정보를 잘 활용하여 안드로이드 개발 학습에 도움이 되거나 실무에 적용하여 업무에 도움이 되기를 바랍니다.

안드로이드 스튜디오의 “라이브 템플릿”을 사용하여 개발 향상

아래 링크는 안드로이드 스튜디오 팁을 모아둔 곳입니다.
https://plus.google.com/u/0/collection/wtO0PB
여기서 가장 쉽고 빠르게 쓸 수 있는 기능 중 하나는 라이브 템플릿 기능으로, 반복되는 메서드를 임의로 지정한 약자를 입력하면 풀네임으로 자동변경되는 기능입니다.
ex) fbc + Enter -> findViewById로 변환

라이브 템플릿 기능에 대해서 아래 링크를 참고하면 좀 더 많은 정보를 확인할 수 있습니다.
https://www.bignerdranch.com/blog/android-studio-live-templates/

안드로이드에서 기본적으로 지원해주는 라이브 템플릿외에도 사용자가 커스텀하게 만들 수도 있습니다. 아래 링크는 많이 사용되는 메서드들에 대한 라이브 템플릿을 커스텀하여 공개하고 있습니다.
https://github.com/keyboardsurfer/idea-live-templates

라이브 템플릿은 반복되는 메서드 입력을 최대한 줄여 개발 시간을 단축시킬 수 있는 가장 좋은 방법입니다.
https://medium.com/@aditlal/must-have-tools-for-android-development-d76ae66f409f#.qhhck9bvk

앱을 디버깅하는 동안 안드로이드 스튜디오와 함께 사용할 수 있는 도구

Library methods count – 안드로이드의 DEX파일 포맷 구조상 65만 개의 메서드로 제한되어 있습니다. 해당 툴을 이용하면 안드로이드 라이브러리의 메서드 개수를 확인 할 수 있습니다.

Stetho – 페이스북에서 만든 안드로이드 앱을 쉽게 감시할 수 있는 라이브러리입니다. 네트워크 트래픽을 디버깅하는데 가장 좋습니다. 이뿐만 아니라 SQLite 데이터베이스, 쉐어드 프리퍼런스도 쉽게 감시할 수 있습니다. 크롬 브라우저의 인스팩트 기능을 이용하기 때문에 웹 개발경험이 있다면 훨씬 쉽게 사용할 수 있습니다.

LeakCanary – 메모리 릭을 감지해주는 라이브러리로 코드 한 줄만으로 쉽게 사용 가능합니다. 릭 탐지 시 별도의 UI화면으로 발생되는 위치와 경로를 알려줍니다.

Gradle, Please – Gradle기반의 라이브러리를 쉽게 찾아줍니다. 라이브러리 이름만 입력하면 최신 버전으로 라이브러리를 컴파일할 수 있는 구문을 찾아줍니다.

Android Arsenal – 안드로이드와 관련된 라이브러리를 한 번에 볼 수 있습니다. 항상 최신의 버전을 유지하며 카테고리 기반으로 빠르게 업데이트됩니다.

Android UI OpenSource – 안드로이드 UI와 관련된 오픈소스를 한 번에 볼 수 있습니다.

AndroidTool Mac – 맥 개발자들을 위한 안드로이드 툴입니다. UI 환경에서 빠르게 화면 캡처, 비디오 캡처, APK 끌어서 설치를 할 수 있습니다.

ButterKnifeZelezny – 안드로이드 스튜디오 플러그인으로 선택한 레이아웃 XML에서 버터나이프 인젝션으로 변환해줍니다.

Adb-idea – ADB명령을 쉘이 아닌 안드로이드 스튜디오의 액션 창에서 작동할 수 있는 플러그인입니다.

AndroidWeekly – 매주 최신의 안드로이드 관련 소식을 메일링으로 받아 볼 수 있습니다.

Android Developers Youtube Channel – 구글 안드로이드 공식 유튜브 채널이며, 가장 최신의 안드로이드 기술을 접할 수 있습니다.

완성된 앱으로 학습하기 

Plaid – 디자인 뉴스와 영감을 제공하기 위한 안드로이드 앱입니다. 안드로이드 UI처리에 대해 전반적인 학습을 할 수 있습니다.

Kickstarter – 구글의 킥스타터 앱으로 예술, 디자인, 영화, 게임 음악 등으로 구성된 수천 개의 프로젝트를 탐색할 수 있습니다. 디자인 가이드라인부터 최신 기술을 한 번에 공부할 수 있습니다.

참고




CoordinatorLayout과 Behavior의 관계

머트리얼 디자인 가이드 라인중 스크롤시 다양한 반응을 위한 테크닉인 Behavior라는 개념이 도입 되었습니다. 기본적으로 액션바를 확장하여 스크롤시 액션바를 줄여들게 하도록 AppBarLayout의 ScrollingViewBehavior와 스크롤시 하단으로 숨기게 하기위해 BottomSheetBehavior를 서포트라이브러리에서 제공하고 있습니다.

  • android.support.design.widget.AppBarLayout$ScrollingViewBehavior
  • android.support.design.widget.BottomSheetBehavior

Behavior를 사용하기 위해서는 CoordinatorLayout을 통해서 사용되는데, CoordinatorLayout은 자식뷰의 스크롤의 변화 상태를 다른 자식뷰들에게 전달 해주는 역할을 합니다. 좀더 쉽게 말해 NestedScrollView나 RecyclerView등에 스크롤의 상태를 판단하여 정의된 반응을 하기위한 View에 Behavior를 등록하면 됩니다.

이해를 돕기위해 안드로이드 서포트 라이브러리에서 제공해주는 Behavior를 한번 보겠습니다. NestedScrollView에 layout_behavior에 AppBarLayout$ScrollingViewBehavior가 정의가 되어있습니다. NestedScrollView의 반응에 따라 AppBarLayout이 반응됩니다.

CoordinatorLayout는 NestedScrollView가 스크롤시 layout_behavior에 정의된 레이아웃으로 스크롤 정보를 전달 하는 역할을 합니다. 그럼 AppBarLayout의 ScrollingViewBehavior가 정보를 받아서 AppBarLayout 자신을 변형하도록 하는 구조입니다.

CoordinatorLayout이 스크롤되는 것은 Behavior에 구현된 NestedScrollingParent를 통해 전달 됩니다. 즉, CoordinatorLayout는 NestedScrollingParent가 구현되어 있으며 스크롤 되는 View들은 NestedScrollingChild가 구현되어 있어야 Behavior가 전달 됩니다. 그렇기 때문에 기존의 ScrollView나 ListView는 NestedScrollingChild가 구현되어 있지 않아 Behavior를 통해 스크롤 정보전달이 되지 않습니다.

이렇게 CoordinatorLayout의 역할과 Behavior의 관계를 알고 있다면 Behavior를 커스텀해서 구현하는데 전혀 문제 없을 것입니다.

안드로이드 APK 디컴파일 (소스보기)

안드로이드는 JAVA기반이기 때문에 런타임 후 코드가 실행되는 구조로 코드를 역으로 컴파일가능하다. 그중 가장 흔하게 사용하는 Dex2Jar이 있다. 이를 이용하면 dex파일을 jar로 변환이 가능하다. jar로 변환 후 JD GUI툴을 이용하여 코드를 볼 수있다.

아래 2가지 툴을 다운받아서 사용하면 되고 Windows와 Mac모두 지원이 된다.

dex2jar

http://code.google.com/p/dex2jar/downloads/list
dex2jar-0.0.9.12-a.zip

JD GUI

http://jd.benow.ca/
jd-gui-0.3.5.osx.i686.dmg jd-gui-0.3.6.windows.zip

 

apk파일을 jar로 변환하여 JDGUI로 어떻게 코드를 보는지 하나씩 설명 하겠다.

1) 소스를 볼 apk파일을 준비한다음 apk->zip파일로 확장자를 변환하여 압축을 푼다. 참고로 http://www.apkmirror.com 사이트를 이용하면 구글플레이에 올라온 앱을 모두 구할 수 있다.

 

2) 압축을 풀면 classs.dex파일이 보인다. 이파일을 dex2jar툴을 이용하여 jar로 변형한다. 위에서 받은 dex2jar압축을 풀면 dex2jar.bat(windows용)파일과 dex2jar.sh(mac용)을 볼 수 있다.

3) 명령어를 통해 dex파일을 jar로 변경 해보자.(mac)
$>./dex2jar.sh <apk압축을 푼 위치>/classes.de

이렇게 명령어를 실행 하고 나면 classes.dex의 같은 폴더내에 classes_dex2jar.jar파일이 생성된다.

JD-GUI를 통해서 디컴파일된 jar파일을 불러와서 코드를 보면된다.

이렇게 디컴파일이 쉽다 보니 코드를 난독화 하여 빌드되는 앱이 대부분일 것이다. 예를 들어 클래스명이나 메서드 명을 알아보지 못하게 바꿔버리는 해당클래스나 메서드가 어떤역할을 하는지 이해하지 못하도록 해버린다. 그렇기 때문에 이런 역컴파일 코드는 단순히 참고용으로 사용하길 바란다.

구글 Chromecast 에뮬레이터 만들기 For Mac

구글 크롬캐스트가 출시 되었지만 아직 국내에서 구입하기가 불가능하다.  크롬캐스트를 HDMI포트로 와이파이로 수신된 영상이나 음성을 출력해주는 기기이다. 얼마전 포스팅을 통해 크롬캐스트에 대한 작동법을 설명했다. 

아직 국내에 출시 되지 않아 사용해보거나 SDK를 통해 앱을 개발/테스트 할 수 없다. 하지만 에뮬레이터를 만들어 실제 기기에 크롬캐스트가 연결 된것 처럼 환경을 구성 할 수 있다.

Github를 통해 공개된 Leapcast를 이용하면된다. 파이썬을 이용해서 개발 되었으며, 작동 원리는 간단하게 말하자면 Leapcast는 서버를 실행해서 기기에서 request가 오면  크롬브라우저에 기본적으로 내장되어 있는 크롬캐스트를 호출 해주게되어 영상이나 음성이 나오게 된다.

준비사항

1. MAC OSX 10.8 이상   MAC OSX 10.7.5 에도 정상 작동 확인 

2. Chrome 브라우저 필수 설치

3. Python  2.7.x 및 기타 모듈

 

 

설치/환경 세팅 방법 (2013. 08. 05 수정)

 

 MAC OS X 10.8이상 일 경우 Python의 버그로 인해 실행안되니 터미널에서 다음과 같은 조취를 취한다.

$>sudo mkdir -p /usr/include/python2.7

$>sudo ln -s /System/Library/Frameworks/Python.framework/Versions/Current/in

 

– 앱을 이용한 방법

일단 아래 수동방법을 해보기전, 아래과정을 모두 자동으로 해주는 앱이 있다. 여기서 다운로드를 받는다.

다운받은 후 압축을 풀고 실행을 하면 모든 구성이 완료된 상태이다. 간혹 특정 Mac 에서는 환경설정 문제로 에러나는 경우가 있는데, 에러나면 수동방법을 사용하면된다.

앱이 실행되면 반응은 없지만, 유튜브앱을 실행하면 크롬캐스트 아이콘이 활성화 되어 있어 바로 캐스트가 가능하게된다.

 

– Python으로 수동으로 서버띄우는 방법

  1. Python이 설치 되어 있지 않으면 아래사이트를 통해 다운을 받고 설치한다. 버전은 2.7.x버전을 설치 해야한다.

http://www.python.org/getit/

  1. Github의 Leapcast에서 설치파일을 받은후 아래와 같이 설치를 한다. 

$> python setup.py develop

크롬캐스트 위치 기본적으로 가 Mac기반이 아닌 Unix기반으로 되어 있기 때문에 문제가 생길수 있어, 수정버전으로 재 업로드 했으니 여기서 받으시면 leapcast실행시 –chrome 위치를 적어주지 않아도 되고 설치 할 필요가 없다. 바로 3번으로 이동!

cfile2.uf.2609FC3651FFB5FB2FE427.zip

설치중 module이 없으면 에러가 나는데, 해당 모듈을 일일이 찾아서 설치하기가 힘들다. 하지만 easy-install을 이용하면 쉽게 설치 할 수있다.

예를 들어 setup module이 없다면 

$> python easy-install setup 를 하게되면 설치가 가능 하다.

$> python easy-install <모듈명> 

  1. Leapcast install이 끝나면 /leapcast 디렉토리에 실행에 필요한 파일이 생성되었을 것이며, leapcast를 실행 하면된다.

$>leapcast --name Chromecast --chrome 크롬이 설치된 위치

여러가지 옵션이 있는데 chrome 설치위치는 필수이다. (위에서 수정버전을 받았다면 생략 가능) 크롬의 구글캐스트를 이용하여 재생하기때문이다.

  1. 여기까지 아무 문제가 없다면 이제 준비상태가 되었다. 같은 Wi-Fi 안드로이드or아이폰의 YouTube앱을 실행 하게 되면 아래와 같이 아이콘이 활성화 되어 cast를 할 수 있다.

  • Android YouTube App

  • Emulator

현재 Google MusicPlay, YouTube, Netflix 앱에서만 크롬캐스트가 지원된다. 아주 쉬운 SDK가 제공되기 때문에 앞으로 크롬캐스트를 사용하는 앱이 점차 늘어날 것이다. 아직 국내에서는 정식 출시가되지 않은 상황에서 이렇게 에뮬레이터를 통해서도 개발이 가능하니 재미있는 앱들이 만들어지면 좋겠다. 

구글 Chromecast DIAL프로토콜을 이용한 직접 재생방법

얼마전 구글에서 크롬캐스트가 발표 되었다.  

크롬캐스트는 HDMI포트에 꽂아 오디오나 비디오를 와이파이를 통해 수신해 기기에서 재생해줍니다.  

개발/작동 방식은 구글캐스트의 SDK를 사용하여 크롬캐스트에 스트리밍할 URL을 보내주면 크롬캐스트가 스트리밍 재생하는 방식입니다.

구글캐스트의  SDK를 이용하면 약간의 제약이 걸리게 됩니다. 예를 들어 로컬에 있는 파일은 재생할수 없으며, 등록된 안된 앱은 재생이 안되는등 제약이 걸리게 됩니다. 하지만 SDK를 안쓰게 되면 그만의 문제점이 있을수 있습니다.

크롬캐스트에 로컬재생과 스트리밍 URL을 이용하여 바로 재생되게 할 방법은 크롬캐스트가 사용하는 DIAL프로토콜을 직접 제어하면 됩니다. 제어는 HTTP및 웹소켓을 통해 수행하면 됩니다. 이렇게 직접 제어를 하게되면 구글캐스트의 SDK를 클라우드기반의 솔루션을 사용하지 않고 앱을 개발 할수 있습니다.

실제로 YouTube에 SDK를 이용하지 않고 크롬캐스트롤 영상을 재생하는 앱을만들었다는 동영상이 게시되어있습니다.

또한 Github에 관련된 코드도 공개 했습니다.

이것을 이용하면 로컬파일재생은 물론 현재 보고 있는 안드로이드 화면을 크롬캐스트로 보낼수도 있는 앱을 개발이 가능할 것 같습니다.

Fragment 파헤치기 – 2. Fragment Lifecycle(생명주기)

Fragment의 가장 기본인 Lifecycle을 알아보자. 모든 것이든 기본이 가장 중요한 만큼 하나하나 꼼꼼하게 분석해보자.

Fragment는 Activity와 비슷한 Lifecycle 구조를 가졌다.

이전 Fragment 개념에 대해 간단히 언급 했듯이, Fragment는 Activity에서 작동하는 구조라고 소개 되었다.

(2013/02/26 – [개발관련/Android] – Fragment 파헤치기 – 1. Fragment 개념)

 

하지만 Activity보다 좀더 복잡하지만, 개념만 이해 한다면 쉽게 사용 할것 이다.

 

FragmentTransaction으로 Fragment를 add, replace 한다. 이외 레이아웃에서 바로 add하는 경우도 있다.

이때 add, replace할때 부터 Lifecycle이 시작된다.

 

 

 

 

 

 

최초 생성 Lifecycle

1) onAttach()
Fragment가 Activity에 붙을때 호출 된다.

2) onCreate()
Activity에서의 onCreate()와 비슷하나, ui관련 작업은 할 수 없다.

3) onCreateView()
Layout을 inflater을하여 View작업을 하는곳이다.

4) onActivityCreated()
Activity에서 Fragment를 모두 생성하고 난다음 호출 된다. Activity의 onCreate()에서 setContentView()한 다음이라고 생각 하면 쉽게 이해 될것 같다. 여기서 부터는 ui변경작업이 가능하다.

5) onStart()
Fragment가 화면에 표시될때 호출된다. 사용자의 Action과 상호 작용 할 수 없다.

6) onResume()
Fragment가 화면에 완전히 그렸으며, 사용자의 Action과 상호 작용이 가능하다.

다른 Fragment가 add

1) onPause()
Fragment가 사용자의 Action과 상호 작용을 중지한다.

2) onStop()
Fragment가 화면에서 더이상 보여지지 않게 되며, Fragment기능이 중지 되었을때 호출 된다.

3) onDestoryView()
View 리소스를 해제 할수 있도록 호출된다. backstack을 사용 했다면 Fragment를 다시 돌아 갈때 onCreateView()가 호출 된다.

Replace or backward로 removed되는 경우

4) onDestory()
Fragment상태를 완전히 종료 할 수 있도록 호출 한다.

5) onDetach()
Fragment가 Activity와 연결이 완전히 끊기기 직전에 호출 된다.

그외 Callbacks Method

onSaveInstanceState()

Activity와 동일하게 Fragment가 사라질떄 호출되며 상태를 Bundle로 저장할수 있도록 호출 된다.

그럼 간단한 테스트 코드를 통해 Lifecycle에 맞게 작동 되는지 확인 해보도록 하자.

 

Activity

package com.test;

import com.example.fragmenttest.R;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.AttributeSet;
import android.view.View;

public class FragmentTestActivity extends FragmentActivity {
    

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        
        TestFragment newtf = TestFragment.newInstance(1);
        
        ft.replace(R.id.embedded, newtf);
        ft.commit();
        
    }

    @Override
    public void onAttachFragment(Fragment fragment) {
        super.onAttachFragment(fragment);
    }
    
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return super.onCreateView(name, context, attrs);
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }
    
}

Fragment

package com.test;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.fragmenttest.R;

public class TestFragment extends Fragment {
    
    int mIdx;
     
    public static TestFragment newInstance(int index) {
        
        TestFragment fragment = new TestFragment();
        
        Bundle args = new Bundle();
        args.putInt("index", index);
        fragment.setArguments(args);

        return fragment;
    }
    
    
    @Override
    public void onAttach(Activity activity) {
        Log.d(this.getClass().getSimpleName(), "onAttach()");
        
        super.onAttach(activity);
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onCreate()");
        
        super.onCreate(savedInstanceState);
        
        Bundle args = getArguments();
        if (args != null) {
            mIdx = args.getInt("index", 0);
        }
        
    }
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onCreateView()");
        return inflater.inflate(R.layout.layout_contents, null);
        
    }
    
    @Override
    public void onInflate(Activity activity, AttributeSet attrs,
            Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onInflate()");
        super.onInflate(activity, attrs, savedInstanceState);
    }
    
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onViewCreated()");
        super.onViewCreated(view, savedInstanceState);
    }
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onActivityCreated()");
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onStart() {
        Log.d(this.getClass().getSimpleName(), "onStart()");
        super.onStart();
    }

    @Override
    public void onResume() {
        Log.d(this.getClass().getSimpleName(), "onResume()");
        super.onResume();
    }
    
    @Override
    public void onPause() {
        Log.d(this.getClass().getSimpleName(), "onPause()");
        super.onPause();
    }
    
    
    
    
    @Override
    public void onStop() {
        Log.d(this.getClass().getSimpleName(), "onStop()");
        super.onStop();
    }
    
    @Override
    public void onDestroyView() {
        Log.d(this.getClass().getSimpleName(), "onDestroyView()");
        super.onDestroyView();
    }
    
    @Override
    public void onDestroy() {
        Log.d(this.getClass().getSimpleName(), "onDestroy()");
        super.onDestroy();
    }

    @Override
    public void onDetach() {
        Log.d(this.getClass().getSimpleName(), "onDetach()");
        super.onDetach();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d(this.getClass().getSimpleName(), "onSaveInstanceState()");
        super.onSaveInstanceState(outState);
    }

    
    
}


Fragment실행시 Log를 찍어 보았다. Lifecycle과 동일하게 작동 되는 것을 확인 할 수 있다.

 

Fragment종료시 Log를 찍어 보았다. Lifecycle과 동일하게 작동 되는 것을 확인 할 수 있다.

 

Activity에 비해 많은 Callbacks메소드로 인해 복잡한건 사실이다. 하지만 자세히 하나씩 보면 Activity와 별반 다르지 않다는 것을 알 것이다.

아래 그림을 첨부 했으니 Activity와 비교 해보면 좋을것 같다.

 

 여기 까지 Fragment를 살펴 보았다.

다음 시간에는 Fragment를 관리 하는 FragmentManager와 FragmentTransaction에 대해서 상세히 알아 보도록 하자.