본내용은 https://github.com/android10/Android-CleanArchitecture 의 내용을 요약 정리 하였습니다.

"SHOULD NOT REINVENT THE WHEEL" 



Android Clean architecture principle



clean architecture란 다음과 같은 시스템을 생산하는 일련의 양식을 의미합니다.


  • Independent of Frameworks. 
  • Testable.
  • Independent of UI.
  • Independent of Database. 
  • Independent of any external agency. 


그림에 대한 설명 : 내부원의 경우에는 외부에 있는 원의 영역에 대해서는 생각할 필요가 없고 겹치는 영역도 존재하지 않는다. 

- Entities : 어플리케이션의 비지니스 오브젝트

- UseCases : 인터렉터라고 불리우며 엔티티간에 데이터흐름을 조정함

- Interface Adapters : 유즈케이스와 엔티티에 가장 편리한 형태로 데이터를 변환하는 역할을 한다. Presenter와 Controller가 여기에 속한다

- Frameworsk and Drivers : UI, Tools, fraemework 등을 포함




Android architecture


.친구 목록을 누르면 친구에 대한 상세 내용이 나오는 앱을 구성하는 시나리오로 Android architecture에 대해서 설명한다.

Android architecturing의 목적은  separation of concerns 이다. 비즈니스룰은 자신의 원 밖의 일에 대해서는 몰라도 된다. 따라서 비지니스룰은 외부적인 요인과는 

상관없이 테스트가 가능해야 한다.  이 목적을 이루기 위해서 프로젝트를  서로의 목적이 다르며 독립적으로 동작하는 3개의 다른 레이어로 구분했으면 한다. 


각각의 레이어는 독립성을 보장하기 위해서 독립된 데이터 모델을 가지고 있음을 언급할 필요가 있다. 



clean_architecture_android



Presentation Layer


이 레이어에서 뷰와 애니메이션과 관련된 동작들이 이루어 집니다.  MVC 나 MVVM 어떠한 패턴을 사용해도 상관은 없습니다. 그러나 fragments나 activity들은 view만 담당합니다.  UI로직을 제외하고는 어떠한 로직도 들어가서는 안되며 화면이 그려지는 모든 부분이 이곳에서 동작합니다.


이 레이어의 Interactors(유즈케이스)로 구성된 Presenters는  Android의 메인 UI thread의 밖에서 새로운 thread를 생성해서 동작하고 Callback 을 통해서 화면을 그려주게됩니다.  (https://github.com/pedrovgs/EffectiveAndroidUI/)


clean_architecture_mvp





Domain Layer


비즈니스 룰과 관련되 내용은 모두 이 레이어에서 일어나게 됩니다.  안드로이드프로젝트라면 모든 use case의 구현을 이 레이어에서 볼 수 있습니다.

이 레이어는 순수한 자바 모듈이고 안드로이드와 어떠한 디팬던시 없이 구현됩니다. 모든 비지니스 오브젝트와 외부 컴포넌트는 인터페이스로만 통신하게 됩니다. 



clean_architecture_domain




Data Layer


어플리케이션의 모든 데이터는 UserRepository구현체(Domain Layer 인터페이스)를 통해서만 가지고 올수 있으며, 팩토리를 통해 특정 조건에 따라 데이터소스를 선택하는 전략과 함께 레파지토리패턴을 사용한다. 

예를 들어 사용자 id 를 가지고 오는 경우 캐시에 사용자 id가 있으면 Disk 소스에서 가지고 오고, 그렇지 않으면 클라우드에서 조회한후 디스크 캐쉬에 저장하는 식으로 동작한다.

이때 클라이언트는 데이터가 어떻게 처리되는지(메모리에서 오든지, 클라우드에서오든지) 신경쓰지 않고 데이터를 가져다 쓰는 부분에만 집중하면 된다. 


clean_architecture_data





Error Handling


위에서는  콜백메소드를 사용했다. 데이터레파지토리의 경우에는 onResponse()와 onError() 같이 두가지 콜백 메소드를 사용했다. OnError() 는 ErrorBundle이라는 래퍼클래스를 캡슐화합니다. 이런방식은 렌터링할 프레젠테이션 레이어까지 콜백체인을 형성하기 때문에 여러가지 어려움이 생길수 있습니다. 또한 코드 가독성이 손상될 수 있습니다. 

반면에 무엇인가 잘못되면 이벤트를 던지는 이벤트 버스 시스템을 구현할 수 있습니다. 여러이벤트를 subscribe하는 가운데 세밀하게 다루지 않으면 에러를 놓치는 경우가 발생 할 수 있다.(?)



Testing


Testing과 관련하여서는 레이어 별로 다른 솔루션들을 선택했습니다.

.Presentation Layer : Android instrumentation 과 espresso를 통합 및 기능테스트를 위해서 사용했습니다.

.Domain Layer : 단위테스스트를 위해 JUnit 과 mockito를 사용했습니다.

.Data Layer : Android 와의 의존성때문에 Robolectric , Junit, mockito를 사용했습니다.





본 소스코드 : https://github.com/android10/Android-CleanArchitecture

  • presentation: 프레젠테이션 레이어를 구현한 안드로이드 모듈
  • domain: 안드로이드 디팬던시가 없는 자바 모듈
  • data: 모든 데이터 조회가 가능한 안드로이드모듈
  • data-test: 데이터 레이어를 위한 테스트 모듈.




Conculution


아키텍처가 모든 능사는 아니다. 그러나 위와 같은 방법을 사용한다면 다음과 같은 장점은 얻을수 있다.

  • 쉬운 유지보스
  • 쉬운 테스트
  • 높은 모듈 응집도
  • 낮은 결합성











본 내용은 Android Devloper site 의 내용을 발췌한 내용입니다.


인텐트를 활용한 방식이 어플리케이션간의 데이터 공유방식중 가장 일반적인 방식이다.
Android에서 다른 앱으로 데이터를 전달하기 위해서는 ACTION_SEND 엑션을 활용한다. 

데이터를 공유 하는 방법중에 가장 좋은 방법은  ActionBar 에서 ShareActionProvider 를 추가 하여 공유하는 방법이다.(https://developer.android.com/training/sharing/shareaction.html)


TEXT 컨텐츠 보내기

  • ACTION_SEND 엑션을 통해서 바로 다른 앱으로 데이터를 전달하는 방법이다. 예를 들어서 어떤 글이나 URL을 E-mail 또는 소셜네트워킹 앱으로 공유하는 경우를 생각해 보자
          데이터를 보내는 앱에서는 아래와 같이 전달한다.
Intent sendIntent = new Intent();
sendIntent
.setAction(Intent.ACTION_SEND);
sendIntent
.putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
sendIntent
.setType("text/plain");
startActivity
(sendIntent);

  • 위와같이 인텐트를 실행했을때 설치되어 있는 어플리케이션중에 ACTION_SEND필터와 “text/plain”과 같은 MimeType을 셋팅한 어플리케이션이 있을 경우 안드로이드는 해당 어플리케이션을 실행하게 된다. 한개 이상의 어플리케이션이 매칭될경우 아래와 같이 앱을 선택하는 다이얼 로그가 나타난다.
            



  • 아래와 같이 위 소스를 수정하여 Intent.createChooser()를 명시적으로 호출하는경우 몇가지 장점을 얻을 수 있다.
  • Intent sendIntent = new Intent();
    sendIntent
    .setAction(Intent.ACTION_SEND);
    sendIntent
    .putExtra(Intent.EXTRA_TEXT, "This is my text to send.");
    sendIntent
    .setType("text/plain");
    startActivity
    (Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));

    • 이전에 선택한 앱이 있더라도 앱을 선택하는 창이 나타나게 된다.
    • 매칭되는 앱이 없는 경우에 안드로이드 시스템 메세지가 보이게 된다.
    • Chooser 다이얼로그의 제목을 지정 할 수 있다.



Binary 컨텐츠 보내기

바이너리 데이터를  ACTION_SEND 을 활용하여 전송하기 위해서는 적절한 MIME타입 지정과  EXTRA_STREAM에 uri 를 지정해야 한다. 일반적으로 이미지를 공유하는 방식이지만 기본적으로 모든 바이너리 파일에 대해서 적용 가능하다.

Intent shareIntent = new Intent();
shareIntent
.setAction(Intent.ACTION_SEND);
shareIntent
.putExtra(Intent.EXTRA_STREAM, uriToImage);
shareIntent
.setType("image/jpeg");
startActivity
(Intent.createChooser(shareIntent, getResources().getText(R.string.send_to)));
  •  "*/*” MIME을 활용 할 수 있다. 그러나 이경우에는 일반적인 데이타 스트림을 다루는 엑티비티에게만 매칭된다.

  • 데이터를 받는 어플리케이션에서는 Uri가 가르키는 데이터에 접속하기 위해서 권한이 필요하다. 추천하는 방법은 다음과 같다. 
    • 데이터를  ContentProvider 에 저장하고 ContentsProvider를 통해서 데이터 접속 하도록 한다.
    • 시스템의  MediaStore 를 활용한다. 미디어 스토어는 비디오와 오디오를 위해서 만들어 졌지만 안드로이드 3.0 이상에서는 non-media 타입에 대해서도 지원을 한다. MediaStore에 저장을 하면 content:// 와 같은 형태로 접근이 가능하다(단, 미디어스캔이 끝난 이후에). MediaStore에 저장되면 모든 앱에서 접근이 가능하다.




Multiple 컨텐츠 보내기

multiple 컨텐츠를 공유하기 위해서는  ACTION_SEND_MULTIPLE  액션을 사용해야 한다. MIME 타입은 공유하는 컨텐츠 종류에 따라 달라진다. 예를 들어  3개의  JPEG  이미지를 공유하는 경우 타입은 여전희  "image/jpeg” 이다.  또는 여러개의 이미지 종류가 섞여있는 경우 "image/*” 로 설정하면 어떠한 타입의 이미지가 와도 핸들링이 가능하다.   "*/*” 를 사용한다면 모든 타입에 대해서 공유를 할 수 있음을 의미한다.이러한 경우에는 받는쪽에서 데이터 타입에 따른 핸들링 하는 부분이 보다 추가 되어야 한다.(타입이 명확하지 않은 부분에 대한 verify 필요함)

ArrayList<Uri> imageUris = new ArrayList<Uri>();
imageUris
.add(imageUri1); // Add your image URIs here
imageUris
.add(imageUri2);

Intent shareIntent = new Intent();
shareIntent
.setAction(Intent.ACTION_SEND_MULTIPLE);
shareIntent
.putParcelableArrayListExtra(Intent.EXTRA_STREAM, imageUris);
shareIntent
.setType("image/*");
startActivity
(Intent.createChooser(shareIntent, "Share images to.."));














참고 : 다른앱으로 데이터 전달하기


본 내용은 Android Devloper site 의 내용을 발췌한 내용입니다.


1. 다른 App으로부터 ACTION_SEND 인텐트를 받기 위해서 App의 Manifest 파일에 <intent-filter> 엘리먼트를 추가 한다.
<intent-filter>를 추가 함으로 인해서 앱이 다른 앱으로부터 데이터를 받을 수 있음을 나타낸다. 다른 앱이 쉐어 기능을 활용할 경우 , 동일한 mimeType 으로 지정되어 있으면 공유 리스트에 나타나게 된다.
<activity android:name=".ui.MyActivity" >

   
<intent-filter>

       
<action android:name="android.intent.action.SEND" />

       
<category android:name="android.intent.category.DEFAULT" />

       
<data android:mimeType="image/*" />

   
</intent-filter>

   
<intent-filter>

       
<action android:name="android.intent.action.SEND" />

       
<category android:name="android.intent.category.DEFAULT" />

       
<data android:mimeType="text/plain" />

   
</intent-filter>

   
<intent-filter>

       
<action android:name="android.intent.action.SEND_MULTIPLE" />

       
<category android:name="android.intent.category.DEFAULT" />

       
<data android:mimeType="image/*" />

   
</intent-filter>
</activity>

2. 다른 앱으로부터  전달받은 데이터가 실행될 때는 getIntent() 로  ACTION_SEND Intent로 받은 내용에 따라서 핸들링하는 부분을 다음소스와 같이 추가 해야한다.
홈에서 앱을 띄우는 경우와 다른앱에서 ACTION_SEND 로 전달되는 경우 각각에 따른 핸들링이 필요하다.
void onCreate (Bundle savedInstanceState) {

   
...

   
// Get intent, action and MIME type

   
Intent intent = getIntent();

   
String action = intent.getAction();

   
String type = intent.getType();


   
if (Intent.ACTION_SEND.equals(action) && type != null) {

       
if ("text/plain".equals(type)) {

            handleSendText
(intent); // Handle text being sent

       
} else if (type.startsWith("image/")) {

            handleSendImage
(intent); // Handle single image being sent

       
}

   
} else if (Intent.ACTION_SEND_MULTIPLE.equals(action) && type != null) {

       
if (type.startsWith("image/")) {

            handleSendMultipleImages
(intent); // Handle multiple images being sent

       
}

   
} else {

       
// Handle other intents, such as being started from the home screen

   
}

   
...
}


void handleSendText(Intent intent) {

   
String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);

   
if (sharedText != null) {

       
// Update UI to reflect text being shared

   
}
}


void handleSendImage(Intent intent) {

   
Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);

   
if (imageUri != null) {

       
// Update UI to reflect image being shared

   
}
}


void handleSendMultipleImages(Intent intent) {

   
ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);

   
if (imageUris != null) {

       
// Update UI to reflect multiple images being shared

   
}
}




+ Recent posts