태그 보관물: activity

버터나이프(ButterKnife)를 사용한 효율적인 추상화

대부분 프로젝트에서 소스의 양은 복잡도와 밀접한 관계가 있다. 그래서 OOP 언어에서는 공통 로직의 추상화와 모듈화로 소스의 복잡도를 줄이는 방향으로 가이드하고 있다는 것을 알 수 있다. 자바언어는 도메인에 특화된 여러 프레임웍이 제공하는 의존성 주입(DI)을 사용해서 코드의 복잡도를 줄이고 있다.

안드로이드 앱 프로젝트 또한 프레임웍 기반에서 개발한다. 그래서 안드로이드 프로젝트를 지원하는 각종 라이브러리가 존재하지만 프레임웍은 존재하지 않는 이유이기도 하다. 그래서 대부분의 의존성을 주입하는 라이브러리들은 어노테이션으로 컴파일 타임에 코드를 생성하는 형태이다. 이 형태는 런타임에 의존성 주입을 하는 형태보다 성능상의 이점이 있어서 표준처럼 사용하고 있다.

의존성 주입을 지원하는 대표 라이브러리로 버터나이프(ButterKnife)와 대거(Dagger)가 있다. 개인적으로도 버터나이프를 사용하고 있고, 안드로이드 프로젝트를 시작할 때에 처음 사용하는 라이브러리이다. 그래서 이 라이브러리를 사용해서, 비교적 좋은 형태의 추상화 예제를 살펴보겠다.

아래 예제는 안드로이드 프로젝트에서의 가장 많은 템플릿(Boilerplate) 코드로 뷰(View)에서 위젯(Widget)을 로드하는 예제이다. 아래 예제에서 액티비티(Activity)와 프레그먼트(Fragment)에서 어떻게 사용하고, 개선하는지 살펴보자.

1. 안드로이드 기본 예제

아래는 안드로이드 앱에서 많이 사용하는 액티비티와 프레그먼트의 예제이다.

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

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */
public class MainActivity extends AppCompatActivity {

	private TextView tv01;
	private TextView tv02;
	private TextView tv03;
	private TextView tv04;

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

		tv01 = (TextView) findViewById(R.id.activity_main_tv_01);
		tv02 = (TextView) findViewById(R.id.activity_main_tv_02);
		tv03 = (TextView) findViewById(R.id.activity_main_tv_03);
		tv04 = (TextView) findViewById(R.id.activity_main_tv_04);
	}
}

액티비티 예제

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class MainFragment extends Fragment {

	private TextView tv01;
	private TextView tv02;
	private TextView tv03;
	private TextView tv04;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.fragment_main, parent, false);

		tv01 = (TextView)view.findViewById(R.id.fragment_exview_tv_01);
		tv02 = (TextView)view.findViewById(R.id.fragment_exview_tv_02);
		tv03 = (TextView)view.findViewById(R.id.fragment_exview_tv_03);
		tv04 = (TextView)view.findViewById(R.id.fragment_exview_tv_04);

		return view;
	}

}

프레그먼트 예제

위 예제들은 findViewId() 메서드를 사용해서 화면에 배치한 위젯을 로드하는 예제이다. 이 형태로 화면에서 많은 위젯을 로드하면, 코드의 양도 그만큼 늘어난다.

2. 버터나이프(Butterknife) 예제

버터나이프를 사용해서 액티비티나 프레그먼트에서 화면을 로드하는 예제이다.

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

import net.sjava.example.abstraction.R;

import butterknife.BindView;
import butterknife.ButterKnife;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeActivity extends AppCompatActivity {

	@BindView(R.id.activity_main_tv_01) TextView tv01;
	@BindView(R.id.activity_main_tv_02) TextView tv02;
	@BindView(R.id.activity_main_tv_03) TextView tv03;
	@BindView(R.id.activity_main_tv_04) TextView tv04;

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

액티비티 예제

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import net.sjava.example.abstraction.R;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeFragment extends Fragment {
	@BindView(R.id.fragment_exview_tv_01) TextView tv01;
	@BindView(R.id.fragment_exview_tv_02) TextView tv02;
	@BindView(R.id.fragment_exview_tv_03) TextView tv03;
	@BindView(R.id.fragment_exview_tv_04) TextView tv04;

	private Unbinder unbinder;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.fragment_main, parent, false);
		unbinder = ButterKnife.bind(this, view);
		return view;
	}

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		unbinder.unbind();
	}
}

프레그먼트 예제

버터나이프 예제들은 @BindView 어노테이션을 사용해서 화면의 위젯을 로드한다. 이 예로 위의 안드로이드 기본 예제보다 약간의 수고를 덜 수 있다.

3. 버터나이프 추상화 예제

버터나이프를 사용해서 액티비티와 프레그먼트에서 추상화하는 예제와 추상화한 액티비티와 프레그먼트를 사용하는 예를 살펴보자.

3.1 추상화 예제

import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v7.app.AppCompatActivity;

import butterknife.ButterKnife;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */
public abstract class AbsBaseActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		super.setContentView(getLayoutId());
		ButterKnife.bind(this);
	}

	@LayoutRes
	public abstract int getLayoutId();
}

추상화 액티비티 예제

이 추상화한 클래스는 abstract 클래스로 이 클래스를 상속하는 클래스는 getLayoutId() 메서드를 구현하게 만든다. 이 메서드에서는 액티비티의 화면인 xml 파일의 id를 반환하도록 한다.


import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import butterknife.ButterKnife;
import butterknife.Unbinder;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public abstract class AbsBaseFragment extends Fragment {

	Unbinder mUnbinder;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		View view = inflater.inflate(getLayoutId(), container, false);
		mUnbinder = ButterKnife.bind(this, view);
		return view;
	}

	@LayoutRes
	public abstract int getLayoutId();

	@Override
	public void onDestroyView() {
		super.onDestroyView();
		mUnbinder.unbind();
	}
}

추상화 프레그먼트 예제

이 추상화한 프레그먼트는 추상화한 액티비티의 getLayoutId()와 더불어, onDestroyView() 메서드에서 버터나이프로 읽어들인 뷰를 제거하는 메서드를 추가했다.

3.2 사용 예제

import android.os.Bundle;
import android.widget.TextView;
import net.sjava.example.abstraction.R;

import butterknife.BindView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeMainActivity extends AbsBaseActivity {

	@BindView(R.id.activity_main_tv_01) TextView tv01;
	@BindView(R.id.activity_main_tv_02) TextView tv02;
	@BindView(R.id.activity_main_tv_03) TextView tv03;
	@BindView(R.id.activity_main_tv_04) TextView tv04;

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

	@Override
	public int getLayoutId() {
		return R.layout.activity_main;
	}
}

액티비티 예제

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import net.sjava.example.abstraction.R;

import butterknife.BindView;

/**
 * Created by mcsong@gmail.com on 2016-11-14.
 */

public class ButterknifeMainFragment extends Fragment {
	@BindView(R.id.fragment_exview_tv_01) TextView tv01;
	@BindView(R.id.fragment_exview_tv_02) TextView tv02;
	@BindView(R.id.fragment_exview_tv_03) TextView tv03;
	@BindView(R.id.fragment_exview_tv_04) TextView tv04;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
		return super.onCreateView(inflater, parent, savedInstanceState);
	}
}

프레그먼트 예제

* 예제 코드 : https://github.com/mcsong/AbstractionExample
이 글에서 버터나이프를 사용해서 비교적 효율적으로 추상화해서 사용하는 방법을 살펴봤다.

android.app.FragmentManagerImpl.execPendingActions에서 NPE가 떨어지는 경우..

android.app.FragmentManagerImpl.execPendingActions에서 java.lang.NullPointerException(NPE)가 떨어지는 제 경우를 살펴보니, Activity에서 많은 Fragment가 replace되고 있는 상황이다. 이 문제에 대해서, 아래를 확인해 보면 도움을 받을 수 있다.
– http://stackoverflow.com/questions/14810183/fragmentmanager-nullpointerexception-when-trying-to-commitallowingstateloss
– http://stackoverflow.com/questions/10456077/nullpointerexception-in-fragmentmanager

아래는 android.app.Activity를 상속받은 Activity 클래스에서 앱이 종료되면서, 프레임웍에서 발생시키는 크래시이다. 이것만으로는 디버깅하기 졸라 어렵다.

11-06 14:24:55.582: E/AndroidRuntime(6925): FATAL EXCEPTION: main
11-06 14:24:55.582: E/AndroidRuntime(6925): java.lang.NullPointerException
11-06 14:24:55.582: E/AndroidRuntime(6925): at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1357)
11-06 14:24:55.582: E/AndroidRuntime(6925): at android.app.FragmentManagerImpl$1.run(FragmentManager.java:426)
11-06 14:24:55.582: E/AndroidRuntime(6925): at android.os.Handler.handleCallback(Handler.java:605)
11-06 14:24:55.582: E/AndroidRuntime(6925): at android.os.Handler.dispatchMessage(Handler.java:92)
11-06 14:24:55.582: E/AndroidRuntime(6925): at android.os.Looper.loop(Looper.java:137)
11-06 14:24:55.582: E/AndroidRuntime(6925): at android.app.ActivityThread.main(ActivityThread.java:4424)
11-06 14:24:55.582: E/AndroidRuntime(6925): at java.lang.reflect.Method.invokeNative(Native Method)
11-06 14:24:55.582: E/AndroidRuntime(6925): at java.lang.reflect.Method.invoke(Method.java:511)
11-06 14:24:55.582: E/AndroidRuntime(6925): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
11-06 14:24:55.582: E/AndroidRuntime(6925): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
11-06 14:24:55.582: E/AndroidRuntime(6925): at dalvik.system.NativeStart.main(Native Method)

그래서, Activity를 최신버전(4.4)의 support 라이브러리에 있는 FragmentActivity로 바꿔서 돌려보니, 디버깅할 수 있는 콜 스택을 보여준다.. 헐.. 그래서, 내 경우에는 Fragment 클래스에서 replace하는 메서드에 아래의 코드를 추가해서 해결했다.

if(activity() == null || activity().isFinishing())
return;

Android에서 앱 Activity 관리 및 앱 종료 detect하기..

안드로이드(Android)에서 앱의 Activity나 앱 종료를 체크하기 위해서, Activity Stack을 설정한 개수만큼만 유지시켜주는 클래스를 만들었다.

ActivityManager.java

import java.util.concurrent.LinkedBlockingQueue;
import android.app.Activity;
/**
 * Activity Manager Class
 * @author mcsong@gmail.com
 *
 */
public class ActivityManager {
    private static final int max = 13;
    private static final int size =12;
    private static LinkedBlockingQueue queue = new LinkedBlockingQueue(max);
    private static final String CNAME = ActivityManager.class.getSimpleName();

    public static void createActivity(Activity activity) {
        if(activity == null)
            return;

        queue.add(activity);
        if(queue.size() > size) {
            try {
                queue.take().finish();
            } catch (Exception e) {
                LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e));               
            }
        }
    }

    public static boolean isRootActivity(Activity activity) {
        if(activity == null)
            return false;

        try {
            if(activity == queue.peek())
                return true;
        } catch (Exception e) {
            LogUtil.e(CNAME, LogUtil.TAG, ExceptionUtil.getException(e));               
        }

        return false;
    }

    public static void clear() throws InterruptedException {
        while(queue.size() > 0) {
            queue.take().finish();
        }
    }
}

사용방법 및 효과
– Activity 생성(onCreate 메소드에서)시에 createActivity()를 호출한다. 자연스럽게 size만큼 Activity Stack이 관리가 된다.
– 앱 종료 detect는 isRootActivity()로 확인을 하면 된다.. ture인 경우 각 Activity 클래스에서.. onBackPressed()를 Override해서 확인하면 된다.. 참고로, AbstractActivity 클래스를 만들어서 상속해서 AbstractActivity 클래스에서 이 코드를 삽입하면 각각의 Activity 클래스에서 이 코드를 삽입할 필요가 없다.
– 앱을 종료하면서 clear()를 호출해서 Activity stack에 있는 Activity들을 다 날려주셔야 Activity 관리가 명확해 집니다.. ^^

안드로이드 activity의 동작형태는 android:launchMode=”standard” 에서 잘 동작한다.

관계를 좀 더 쉽게 풀어내는 방법 : Activity Stream ????

눈 앞으로 다가온 물리적 웹의 시대에서 Physical Web, Object, Activity 등의 새로운 개념을 읽고, 포스팅 하신분께 요청해서 정보도 받고 해서, 좀 더 자세히 Activity Stream이라는 것에 대해서 살펴 보았습니다.. 포스팅 하신분께 감사드립니다..

포스팅 하신 분의 도움을 받아서, 제가 파악한 내용은, http://activitystrea.ms/ 이 Object의 Activity에 대한 정의, 그 정의를 바탕으로 한 관계에 대한 포맷에 대한 표준(?)을 제시하고 있더군요.. ^^.

결국, 데이타를 synd하는 포맷을 정의하고, 포맷된 데이타를 통해서 Object간의 관계를 알 수 있을 거 같습니다. 물론, 위치 정보 까지도요… 지금 하는 프로젝트(소셜 합니다..ㅋㅋ)에 써먹기에 참 좋은데 말이죠…

여튼, 기존의 관계가 들어있지 않았던 많은 데이타들이, 관계, 위치등을 기반으로하는 포맷으로 변환이 되서 유저들간의 관계를 좀 더 쉽게 풀어 내기를 기대해 봅니다..