출처 : http://markan82.tistory.com/127

 처: [선달이강좌2] TabActivity 내에서 ActivityGroup을 사용할 경우의 Navigation 처리

오늘의 강좌는 미리 공지한대로 TabActivity 내에서 ActivityGroup을 사용할 경우의 Navigation 처리에 대한 내용입니다.

1. TabActivity의 구성

먼저 TabActivity가 동작하는 간단한 원리를 정리해 보면 다음과 같습니다.

1) TabActivity에 생성되어 있는 TabHost를 얻는다.

1.getTabHost();

2) TabHost에 통해 TabSpec을 생성하면서 Indicator와 Intent를 지정한다.

1.Intent intent = new Intent().setClass(this,
2.com.mk.counsel.group.ViewCounselGroup.class);
3.spec = tabHost.newTabSpec("tab01").setIndicator(
4.new TabView(this, R.drawable.tab1_selector, "tab01")
5.).setContent(intent);

3) TabHost에 TabSpec을 등록한다.

1.tabHost.addTab(spec);

위의 과정에서 TabSpec에 적용되는 Intent가 하나의 Activity 만을 처리하는 형태와 여러 Activity가 하나의 Tab 내에서 관리되는 경우(그림 참조)로 구성될 수 있는데 후자의 경우에 문제가 발생할 수 있습니다.
단순히 Activity를 변경하기 위해서 startActivity() 함수를 사용할 경우 TabActivity가 새로운 Activity로 변경되어 버려서 기존의 Tab 화면을 유지할 수 없다는 문제가 생기는데 이런 문제를 해결해 주기 위해서 등장한 것이 ActivityGroup 입니다.


2. ActivityGroup

ActivityGroup은 철저하게 Activity수행에 대한 관리와 화면처리(View)를 분리하여 관리하기 위한 용도로 생각하시면 됩니다. Activity 수행에 대한 관리는 전적으로 LocalActivityManager에 위임하고 각종 Event 처리에 대해서는 Activity 자체에 맡기고 생성된 최종 화면(View)만 본인이 소유하고 있도록 구성되어 있습니다.(그림참조)
문제는 전형적인 ActivityGroup은 모든 Activity의 관리 권한을 LocalActivityManager에 위임한 상태이고 View만을 제공 받으므로 자체적으로 Navigation 처리를 할 수 없다는 것입니다.바로 여기서 Navigation을 관리할 대상이 정해집니다. 바로 ActivityGroup이 유일하게 소유할 수 있는 View가 바로 그것입니다. 이 View들을 ActivityGroup이 관리함으로 Navigation이 가능해 진다는 것입니다.


3. 실제 적용

다음의 코드를 보시기 바랍니다.
01.public class NavigationGroupActivity extends ActivityGroup {
02.ArrayList<view> history; // View들을 관리하기 위한 List
03.NavigationGroupActivity group; // Activity들이 접근하기 위한 Group
04. 
05.@Override
06.protected void onCreate(Bundle savedInstanceState) {
07.// TODO Auto-generated method stub
08.super.onCreate(savedInstanceState);
09.history = new ArrayList<view>();
10.group = this;
11.}
12. 
13.public void changeView(View v)  { // 동일한 Level의 Activity를 다른 Activity로 변경하는 경우
14.history.remove(history.size()-1);
15.history.add(v);
16.setContentView(v);
17.}
18. 
19.public void replaceView(View v) {   // 새로운 Level의 Activity를 추가하는 경우
20.Log.d("MK","REPLACE VIEW...");
21.history.add(v);  
22.setContentView(v);
23.}  
24. 
25.public void back() { // Back Key가 눌려졌을 경우에 대한 처리
26.if(history.size() > 1) {  
27.history.remove(history.size()-1);  
28.setContentView(history.get(history.size()-1));
29.else {  
30.finish(); // 최상위 Level의 경우 TabActvity를 종료해야 한다.
31.}  
32.
33. 
34.@Override 
35.public void onBackPressed() { // Back Key에 대한 Event Handler 
36.group.back();  
37.return;
38.}
39.}</view></view>

위에서 언급한 내용대로 ActivityGroup이 관리할 수 있는 유일한 자원이 View 이므로 View를 저장할 List를 하나 생성합니다. 기존에는Activity가 변경될 때마다 LocalActivityManager가 넘겨주는 View를 ActivityGroup이 받아서 화면 처리만 하던 것을 조금 능동적으로 View를 직접 관리하는 것으로 바뀌었습니다. 즉 Back Key에 대한 Event가 발생하면 이를 처리할 Handler에서 ActivityGroup에게 View List에서 화면 전환 처리를 해 주라고 직접 요청하는 것입니다.
그러면 Back Key 처리는 알겠는데 새로운 Activity를 추가할 경우는 어디서 누가 호출을 해 주는지가 궁금할 것입니다. 다음의 코드를 보시기 바랍니다.

01.public class NavigationActivity extends Activity {
02.public void goNextHistory(String id,Intent intent)  { //앞으로 가기 처리
03.NavigationGroupActivity parent = ((NavigationGroupActivity)getParent());
04.View view = parent.group.getLocalActivityManager()
05..startActivity(id,intent)  
06..getDecorView();  
07.parent.group.replaceView(view);
08.}
09. 
10.@Override
11.public void onBackPressed() { //뒤로가기 처리
12.NavigationGroupActivity parent = ((NavigationGroupActivity)getParent());
13.parent.back();
14.}
15.}

위의 코드를 보시면 해당 Activity를 소유하고 있는 ActivityGroup을 getParent() 함수로 구한 다음에 실제 Activity의 실행을LocalActivityManager에 맡기고 있습니다.
LocalActivityManager에서 실행된(startActivity() 함수 호출) Activity의 View를 getDecoderView() 함수를 통해 얻은 다음 이 View를ActivityGroup의 view에 적용하는 모습을 볼 수 있습니다. 반대로 Back Key가 발생하면 Parent의 back 함수를 호출하여ActivityGroup내의View를 조정하여 Navigation처리를 하도록 요청합니다.
자 그럼 이제 두 클래스를 상속받은 실제 ActivityGroup 과 Activity가 어떻게 적용되는지를 살펴 보겠습니다. 
아래의 코드를 보시기 바랍니다.

01.public class ViewCounselGroup extends NavigationGroupActivity {
02.@Override
03.protected void onCreate(Bundle savedInstanceState) {
04.// TODO Auto-generated method stub
05.super.onCreate(savedInstanceState);
06.Intent intent = new Intent(this,ViewCounselMainActivity.class);
07.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_SINGLE_TOP);
08.View view = getLocalActivityManager().startActivity("CounselMainActivity",intent)
09..getDecorView();  
10.replaceView(view);  
11.}
12. 
13.@Override
14.public void onBackPressed() { // Back Key에 대한 처리 요청
15.super.onBackPressed();
16.}
17.}

ViewCounselGroup이라는 ActivityGroup에서는 LocalActivityManager를 통해 초기에 실행할 Activity인 ViewCounselMainActivity라는 클래스를 Intent로 실행합니다. 이때 Intent에 적절한 Flag를 설정해 주지 않으면 LocalActivityManager가 관리하는 Stack에 Activity가 View의 저장 구조와 다른 형태로 쌓이게 되므로 Navigation이 동기화 되지 못합니다.따라서 반드시 위의 Intent Flag를 설정해 주셔야 Activity와View의 순서가 동기화 됩니다. 또한 Back Key에 대한 처리는 모든 Activity에서 처리가 되는 것이 아니라 ActivityGroup에서 실행한 맨처음 Activity에서만 Event를 받을 수 있다는 점을 꼭~~~ 기억 하셔야 합니다.
참고로 Intent에 대한 Flag 설명은 다음 주소를 참고하시기 바랍니다.

http://chihun80.springnote.com/pages/6423199

Intent가 설정되면 LocalActivityManager를 통해 Activity를 실행하고 이때 생성된 View를 ActivityGroup에 적용해 주어야 하는데 그냥 적용하면 안되고 replace()라는 함수를 통해 View를 관리하는 List에 등록한 후 화면 적용을 해 주셔야 합니다.

01.public class ViewCounselMainActivity extends NavigationActivity
02.implements OnItemClickListener{
03. 
04.……중략……
05.@Override
06.protected void onCreate(Bundle savedInstanceState) {
07.// TODO Auto-generated method stub
08.super.onCreate(savedInstanceState);
09.setContentView(R.layout.view_counsel_list);
10.adapter = new CounselAdapter(this,items);
11.ListView view = (ListView)findViewById(R.id.counsel_list);
12.view.setAdapter(adapter);
13.view.setOnItemClickListener(this);
14.}
15. 
16.@Override
17.public void onItemClick(AdapterView<!--?--> adapter, View view, intposition, long id) {
18.Intent intent = new Intent(ViewCounselMainActivity.this,
19.com.mk.counsel.ViewCounselDetailActivity.class);
20.intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_SINGLE_TOP);
21.intent.putExtra("id", items.get(position).id);
22.goNextHistory("ViewCounselMainActivity",intent);
23.}
24.}

동작에 대한 Event 처리 등은 실행된 Activity 내에서 관리가 된다고 말씀 드렸듯이 GroupActivity에서 처음 실행된 ViewCounselMainActivity내에서 특정 아이템이 선택된 경우에 ItemClick 핸들러를 처리하고 다음 Level의 Activity로 이동을 하려고 한다면goNextHistory()를 통해 이동하고자 하는 Intent를 넘겨주면 GroupActivity 내부에서 LocalActivityManager를 통해 Intent를 수행하고 View를 GroupACtivity의 ViewList에 추가하고 화면을 전환하는 작업을 해 줍니다.


4. 마무리

TabSpec에 Intent를 설정할 때 GroupActivity를 먼저 등록하고 GroupActivity 내에서 처음 호출할 Activity를 LocalActivityManager를 통해 실행하고 View를 얻는 과정이 핵심입니다. 이걸로 TabActivity와 ActivityGroup간의 관계 정리와 ActivityGroup에서 LocalActivityManager를 통해 Activity와 View를 관리하는 메커니즘을 살펴 보았습니다. 먼저 그림을 한번 보시고 전체 흐름을 이해하신 후에 해당 소스를 보시면 훨씬 이해가 쉬우실 것으로 생각됩니다.
다음 글에는 TabActivity 위에 TitleBar를 각 탭별로 Custom하게 만들어서 TitleBar의 메뉴와 TabActivity의 각 Tab이 상호 연동되어 구동되는 예제를 가지고 설명을 드리겠습니다. 완벽하게 돌아가는 예제를 만들어 전체 소스를 올리고 싶지만 예제를 따로 만들만한 개인적인 시간이 조금 부족한 관계로 핵심 코드만을 보여 드렸습니다. 다음 번 강좌를 기대해 주세요. ^^


5. Q&A

1. 왜 뷰를 따로 리스트 관리를 하나요?

LocalActivityManager에 보면 현재 Activity를 종료하는... (finish와 유사동작) 방법이 있는것으로 알고 있습니다. 현재 Activity를 종료하고 현재 Activity를 얻어서 윈도우의 DecorView를 얻어서 현재 ContentView를 갱신하는 방식으로 하면 뒤로가기가 따로 뷰를 관리 안해도 되지 않나요? (실테스트는 완벽하게 하지 않아서... 혹시 이게 안되서 그리하신건가요?) 그리고 Flag를 쓸때 문제점이 해결될수 있습니다. 현재는 자유롭게 쓸수 없죠...

현재 Activity를 LocalActivityManager를 통해서 종료 시키고 이전 Activity를 활성화 할 경우 화면상으로는 이전 Activity가 나타나지만 기존에 처리되던 상태나 화면을 그대로 다시 복원하는데 문제가 발생합니다. Intent Flag를 통해서 기존 Activity가 재사용 되도록 처리를 하더라도 화면에 대한 복원을 해 주지는 않기 때문에 인스턴스 상태를 저장하거나 복원하기 위한 처리를 따로 해 주어야 하는데 그 방법이 복잡하기 때문에 View 관리를 통해서 이동하는 것이 훨씬 간단하다고 생각되었습니다. 그리고 DecodeView를 하게 되면 근본적으로 LocalActivityManager가 Activity를 재시작하고 View를 새로 얻는 것이므로 상태 유지 자체가 안됩니다.

2. 직접 ViewCounselMainActivity Class를 형변환 하여 사용하는 방식은 안좋다고 생각합니다.

결국 저런 구조의 소스들은 재사용 측면에서 통째로 들고 다녀야 하는 나쁜점이 있습니다. 클래스이름에 대한 리펙토링 문제도 있구요.. (물론 이클립스의 리펙토링이 좋긴해서 다 되긴합니다.) Broadcast를 이용하는게 의존성이 낮아지기 때문에 구성이 더 깔끔해 보일거라 생각합니다.

사용해 보았지만 의존성이라는 측면은 공감을 하지만 아무래도 Message전송을 통한 Receiver를 통해 Activity의 View를 변경하는 방식보다 직접 View를 관리하는 것이 성능면에서 조금 나은 면을 보였습니다. 그리고 LocalActivityManager에서도 Stack에 Activity를 들고 다니면서 관리하고 있기 때문에 Activity View를 추가로 들고 다니는 것이 같은 원리에서 보면 큰 문제는 아니지 않을까 생각됩니다. 마지막으로 ActivityGroup을 상속받는대신 NavigationGroupActivity를 상속받고 Activity 대신 NavigationActivity 클래스를 상속 받는거 뿐이기 때문에 재사용성 측면에서도 문제가 될 부분이 없어 보이는데요...ㅠㅠ

+ Recent posts