해당 포스팅에서 언급된 내용은 Ndroid 에서 제공합니다.

https://github.com/skaengus2012/Ndroid


안녕하세요. 연휴는 잘 보내고 계신가요?


저는 4일간 연휴 중 조금 마음을 refresh 하고자, 하루는!! 

기존 작성하였던 코드를 리뷰해보고, 리팩토링을 하는 시간을 가지게 되었어요. 


그래서 오늘 포스팅은 이에 대한 결과물로 조금 더 구체적인 안드로이드 개발의 패턴화에 대해 생각해보는 포스팅을 해보려 합니다. (그냥 일개 초보수준 프로그래머가 작성한 것입니다. ㅡㅡ^)


일단 리팩토링 한다고 해서 아무렇게나 작성을 해보기 보단 지난 포스팅 때 다뤘던 두 가지 사례를 적극적으로 사용해보는 시간을 가졌습니다. 아래의 두 포스팅을 말이죠. :-)



작업을 했던 내용은 다음과 같습니다.


1. Activity에 조금 더 최적화된 EasyWeakReference


코드를 작성하다보면 종종 어떤 일들은 안드로이드의 메인스레드에서 실행되어야할 경우가 많이 존재합니다. 특히 병렬처리를 하려 한다면, 멀티스레드 작업의 결과를 View 에 반영해야하는데 이 작업은 꼭 메인스레드에서 이루어 져야 하는 작업이죠!!


안드로이드에서 이에 접근할 수 있는 방법은 여러 종류가 있지만 저는 그중에서 runOnUiThread 를 주로 애용하는 편입니다. 


이 개념을 저는 일전에 제작한 NxWeakReference 에 추가하고 싶었으며(보다 더 쉽게 일하기 위해 ㅡㅡ^), 이를 위해 저는 해당 클래스의 확장체를 만들게 되었습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * Activity 전용 약한참조 객체
 *
 * Created by Doohyun on 2017. 1. 28..
 */
 
public class NxActivityWeakReference<extends Activity> extends NxWeakReference<T> {
    /**
     * 단순한 상속에 대한 생성자 제작
     * @param referent
     */
    public NxActivityWeakReference(T referent) {
        super(referent);
    }
 
    /**
     * 단순한 상속에 대한 생성자 제작
     *
     * @param referent
     * @param q
     */
    public NxActivityWeakReference(T referent, ReferenceQueue<T> q) {
        super(referent, q);
    }
 
    /**
     * Activity 에서 runOnUiThread 로 실행함
     *
     * @param weakReferenceConsumer
     */
    public void runOnUiThread(IWeakReferenceConsumer<T> weakReferenceConsumer) {
        final T activity = this.get();
 
        if (activity != null) {
            activity.runOnUiThread(() -> weakReferenceConsumer.accept(activity));
        }
    }
}
cs


간략하게 리뷰를 하자면, 생성자에서 Activity 를 상속받은 인스턴스를 강제로 입력하도록 제작하였으며, runOnUiThread 메소드를 통해 보다 더 쉽게 mainThread 에서 비지니스로직을 수행할 수 있습니다.



2. 안드로이드 MVP 규칙 정의


일전에 한번 JANDI 블로그(http://tosslab.github.io/android/2015/03/01/01.Android-mvc-mvvm-mvp.html)를 통해 protoType 화 했던 MVP 패턴에 대한 규칙을 조금 더 명세하였습니다. 규칙은 다음과 같습니다.


- View 에 해당하는 Activity는 오직 View 자체 변경 메소드만 작성할 것!


View 는 버튼 클릭 등에 의한 이벤트로 부터 해야할 비지니스 로직을 Presenter 로 위임합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LoginActivity extends MitActivity {
 
    // 하위 View
    private RelativeLayout ryDialogBg;
    private LoginLoadingDialog loginLoadingDialog;
    private Button btnLogin;
 
    // Presenter
    private LoginPresenter loginPresenter;
    
    .....
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        loginPresenter = new LoginPresenter(this);
       
        // 이벤트에 대한 비지니스 로직을 Presenter 로 위임! 
        btnLogin.setOnClickListener(loginPresenter::clickLoginButton);
    }
 
    /**     
     * View 자체의 변경 메소드
     *
     * 키보드 숨기기
     */    
    public void showLoginLoadingDialog() {
        loginLoadingDialog.show();
        ryDialogBg.setVisibility(View.VISIBLE);
    }
 
}
cs


- Presenter 는 View 와 Model 의 연결고리, 즉 기존의 Controller 


Presenter 는 View 로 부터의 요청 시, Modeler 로 명령을 위임합니다. 


또한 Modeler 로부터  받은 정보를 View 로 반영하기 위해, View 에 정의된 메소드를 직접 실행합니다. 이를 위해 Presenter 는 View 에 대한 약한참조를 가지고 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LoginPresenter implements ILoginServiceObserver {
 
    private NxActivityWeakReference <LoginActivity> loginActivityEasyWeakReference;
 
    public LoginPresenter(LoginActivity loginActivity) {
  // Activity 와 약한참조 유지
        loginActivityEasyWeakReference = new NxActivityWeakReference <>(loginActivity);
    }
 
    /**
     * 로그인 시도
     * 
     * <pre>
     *  View 인 Activity에서 Click Event로 할 일을 위임한 메소드
     * </pre>
     * @param view
     */
    public void clickLoginButton(View view) {
       final UserInfo userInfo = getCurrentInputUserInfo();
 
       if (userInfo != null) {
          // View 변경 메소드 사용
          loginActivityMidasWeakReference.run(LoginActivity::showLoginLoadingDialog);
          // Modeler 로 실제 비지니스 로직 위임.
          LoginModeler.GetInstance().tryLogin(userInfo);  
       }
    }
}
cs


Presenter 는 다른 컴포넌트의 옵저버 역할을 수행합니다. 다른 컴포넌트의 알람의 결과(멀티스레드의 콜백결과 등..)로 View 가 변경되야 할 때, 이를 Presenter 로 전달을 하여 View 를 변경시킬 수 있도록 합니다.


- Model 을 담당하는 Modeler 개념 추가 


Modeler 는 스프링에 있던 Service 와 같은 역할을 수행합니다. (안드로이드에는 서비스가 있으니 서비스라는 이름은 차마 사용할 수 없었습니다. ㅜㅡㅜ)


Presenter 의 요청으로 내부DB 의 CRUD 를 수행하는 DAO 클래스를 사용하거나, network 작업을 수행합니다. 물론 요청이 콜백이었다면, 데이터를 전달하는 역할도 수행하겠죠?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class LoginModeler {
    /**
     * 로그인을 시도한다
     *
     * <pre>
     *     userInfo 가 넘어오는 것에 성공했다면 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     *
     * @param userInfo
     */
    public void tryLogin(@NonNull UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (네트워크 접근)
        BindServiceManager.GetInstance().runRMI(iMessengerBinder -> iMessengerBinder.loginProcess(userInfo.getUserId(), userInfo.getPwd()));
    }
 
    /**
     * 유저 정보를 저장한다.
     *
     * <pre>
     *     userInfo 는 null 이 아닌 한 정확한 정보를 보장함!
     *     유효성 검사 필요 없음
     * </pre>
     * @param userInfo (null 허용)
     * @return
     */
    public boolean saveUserInfo(@Nullable UserInfo userInfo) {
        // 실제 비지니스 로직 수행 (DB CRUD)
        final boolean resultYn;
 
        if (userInfo != null) {
            resultYn = TxManager.GetInstance().runTransaction(() -> {
                UserInfoServiceManager.GetInstance().insert(userInfo.getUserId(), userInfo.getPwd());
                SettingServiceManager.GetInstance().insert();
            });
        } else {
            resultYn = false;
        }
 
        return resultYn;
    }
}
cs



계속해서 이 패턴화 를 견고하게 작업해 볼 생각입니다. 


보다 유연하고 질 좋은 코드를 꿈꾸며 말이죠. :-)


이상 설연휴 중 삽질이었습니다. ㅡㅡ^ 



반응형
Posted by N'