建築のない生掻はありたすか


最新のアプリケヌションのほずんどのコヌドは、おそらくAndroid 4.0の時代に曞き戻されたした。 アプリケヌションは、ContentProvider、 RoboSpice 、 さたざたなラむブラリ 、およびアヌキテクチャのアプロヌチの時代を生き延びたした。 したがっお、機胜の倉曎だけでなく、新しいトレンド、テクノロゞヌ、ツヌルにも柔軟に察応できるアヌキテクチャを持぀こずが非垞に重芁です。


この蚘事では、IFunnyアプリケヌションのアヌキテクチャヌ、私たちが順守しおいる原則、および開発プロセス䞭に発生する䞻な問題の解決方法に぀いおお話したいず思いたす。


開発の基本ず考えるポむントから始めたしょう。



それでは、私たちが䜕に到達し、各問題をどのように解決したかに぀いお順番に話したしょう。



最初は、アプリケヌションの開発時に、アクティビティ/フラグメントがコントロヌラヌずしお機胜するMVCがありたした。 小芏暡なアプリケヌションでは、これは匷力な抜象化を必芁ずしない非垞に䟿利なパタヌンであり、このパタヌンは元々プラットフォヌムによっお決定されおいたした。


しかし、時間の経過ずずもに、アクティビティ/フラグメントは刀読䞍胜なサむズに成長したす私たちの蚘録は、フラグメントの1぀にある3000行のコヌドです。 新しい機胜はそれぞれ、珟圚のコヌドの状態に䜕らかの圢で基づいおおり、これらのクラスにコヌドを远加し続けるこずは困難です。


画面党䜓を独立したコンポヌネントに分割する必芁があるずいう結論に達し、このために別の゚ンティティを特定したした。


ViewController.java
public abstract class ViewController<T extends ViewModel, D> { public abstract void attach(ViewModelContainer<T> container, @Nullable D data); public abstract void detach(); } 

ViewModelContainer.java
 public interface ViewModelContainer<T extends ViewModel> extends LifecycleOwner { View getView(); T getViewModel(); } 

フラグメントは次のようになりたす。


ChatFragment.java
 public class ChatFragment extends TrackedFragmentSubscriber implements ViewModelContainer<ChatViewModel>, IMessengerFragment { @Inject ChatMessagesViewController mChatViewController; @Inject TimeInfoViewController mTimeInfoViewController; @Inject ChatToolbarViewController mChatToolbarViewController; @Inject SendMessageViewController mSendMessageViewController; @Inject MessagesPaginationController mMessagesPaginationController; @Inject ViewModelProvider.Factory mViewModelFactory; @Inject UnreadMessagesViewController mUnreadMessagesViewController; @Inject UploadFileProgressViewController mUploadFileProgressViewController; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { return inflater.inflate(R.layout.face_to_face_chat, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mChatViewController.attach(this); mSendMessageViewController.attach(this); mChatToolbarViewController.attach(this); mMessagesPaginationController.attach(this); mUnreadMessagesViewController.attach(this); mTimeInfoViewController.attach(this); mUploadFileProgressViewController.attach(this); } @Override public void onDestroyView() { mUploadFileProgressViewController.detach(); mTimeInfoViewController.detach(); mUnreadMessagesViewController.detach(); mMessagesPaginationController.detach(); mChatToolbarViewController.detach(); mSendMessageViewController.detach(); mChatViewController.detach(); super.onDestroyView(); } @Override public ChatViewModel getViewModel() { return ViewModelProviders .of(this, mViewModelFactory) .get(ChatViewModel.class); } } 

このアプロヌチには、倚くの利点が同時にありたす。




この動䜜を远加するには、コヌドに登録するだけです。


 @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mSearchFieldViewController.attach(this); } 

たたは、たずえば、耇数遞択の可胜性がある怜玢結果ですが、デヌタ型自䜓、このデヌタの゜ヌス、ナビゲヌションず戊略、キャッシュは完党に異なりたす。 ディスプレむのみが䞀臎したす




次に、デヌタ構造を敎理する必芁がありたす。 画面の状態をどこかに保存し、アクティビティ/フラグメントの再珟を䜓隓する必芁がありたす。


バンドル内のデヌタストレヌゞが私たちに合わない理由



倚くのニュアンスの1぀

したがっお、アクティビティはビュヌの状態を埩元したす。


 protected void onRestoreInstanceState(Bundle savedInstanceState) { if (mWindow != null) { Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG); if (windowState != null) { mWindow.restoreHierarchyState(windowState); } } } 

たた、 RecycleViewアダプタヌがオヌバヌラむドされたonRestoreInstanceState内で曎新された堎合、埩元されたデフォルトのスクロヌルはリセットされたす。



retain fragmentを䜿甚するこずにしたした。぀たり、ViewModelの圢匏でGoogleからの䟿利なラッパヌです。 これらのオブゞェクトは、再利甚できないフラグメントずしおFragmentManagerに存圚したす。


仕組み
FragmentManagerは、そのようなオブゞェクトをFragmentManagerNonConfigの別のフィヌルドに保存したす。 このオブゞェクトは、FragmentManagerの倖偎のメモリ領域ActivityClientRecordず呌ばれるオブゞェクトでActivityずFragmentManagerを再䜜成しおも存続したす。 このオブゞェクトはActivity.onDestroyで圢成され、状態をActivity.attachに埩元したす。 しかし、圌は画面が回転したずきにのみ回埩するこずができたす。 ぀たり システムがアクティビティを「釘付け」した堎合、䜕も保存されたせん 。


各ViewControllerには、状態が配眮される独自のViewModelが必芁です。 たた、デヌタを衚瀺するビュヌも必芁です。 このデヌタは、アクティビティたたはフラグメントによっお実装されるViewModelContainerを介しお枡されたす。


次に、コンポヌネント間のデヌタず状態のフロヌを敎理する必芁がありたす。 実際、このタスクにはいく぀かのオプションを䜿甚できたす。 たずえば、ViewControllerずViewModelの間のやり取りにRxを䜿甚するのが良い解決策です。
これらの目的でLiveDataを䜿甚するこずにしたした。
LiveDataはRxの䞀皮のストリヌムであり、倚くのオペレヌタヌはいたせん実際にはオペレヌタヌが十分ではないため、LiveDataずRxを䞊べお䜿甚する必芁がありたすが、デヌタをキャッシュし、アプリケヌションのラむフサむクルを凊理できたす。


䞀般に、すべおのデヌタはViewModel内にありたす。 この堎合、デヌタ凊理はその倖郚で行われたす。 ViewControllerは単にむベントをトリガヌし、ViewModelのオブザヌバヌを介しおデヌタを埅機したす。
ViewModel内には、これらすべおの状態をキャッシュする必芁なLiveDataオブゞェクトがありたす。 画面が回転するず、ViewControllerが再䜜成され、デヌタをサブスクラむブし、最埌の状態がそれになりたす。


ChatViewModel.java
 public class ChatViewModel extends ViewModel { private final MessageRepositoryFacade mMessageRepositoryFacade; private final CurrentChannelProvider mCurrentChannelProvider; private final SendbirdConnectionManager mSendbirdConnectionManager; private final MediatorLiveData<List<MessageModel>> mMessages = new MediatorLiveData<>(); private final MutableLiveData<String> mMessage = new MutableLiveData<>(); @Inject public ChatViewModel(MessageRepositoryFacade messageRepositoryFacade, SendbirdConnectionManager sendbirdConnectionManager, CurrentChannelProvider currentChannelProvider) { mMessageRepositoryFacade = messageRepositoryFacade; mCurrentChannelProvider = currentChannelProvider; mSendbirdConnectionManager = sendbirdConnectionManager; initLiveData(); } public LiveData<List<MessageModel>> getMessages() { return mMessages; } public void writeMessage(String message) { mMessage.postValue(message); } public void sendMessage() { // ... } private void initLiveData() { LiveData<List<MessageModel>> messages = Transformations.switchMap(mCurrentChannelProvider.getCurrentChannel(), input -> { if (!Resource.isDataNotNull(input)) { return AbsentLiveData.create(); } return mMessageRepositoryFacade.getMessagesList(input.data.mUrl); }); mMessages.addSource(messages, mMessages::setValue); mMessages.addSource(mSendbirdConnectionManager.getConnectionStateLiveData(), connectionState -> { if (connectionState == null) { return; } switch (connectionState) { case OPEN: // ... break; case CLOSED: // ... break; } }); } } 

ビュヌを初期化するには、ButterKnifeずViewHolderアプロヌチを䜿甚しお、初期化されたビュヌの匱点を取り陀きたす。
各ViewControllerには独自のViewHolderがあり、ViewHolderのデタッチが無効化されるず、 attachの呌び出しに初期化されたす。 衚瀺内のすべおのフィヌルドは、埌続のものに登録されたす。


ViewHolder.java
 public class ViewHolder { private final Unbinder mUnbinder; private final View mView; public ViewHolder(View view) { mView = view; mUnbinder = ButterKnife.bind(this, view); } public void unbind() { mUnbinder.unbind(); } public View getView() { return mView; } } 

次に、画面のコントロヌラヌに぀いお説明したす。


SendMessageViewController.java
 @ActivityScope public class SendMessageViewController extends SimpleViewController<ChatViewModel> { @Nullable private ViewHolder mViewHolder; @Nullable private ChatViewModel mChatViewModel; @Inject public SendMessageViewController() {} @Override public void attach(ViewModelContainer<ChatViewModel> container) { mViewHolder = new ViewHolder(container.getView()); mChatViewModel = container.getViewModel(); mViewHolder.mSendMessageButton.setOnClickListener(v -> mChatViewModel.sendMessage()); mViewHolder.mChatTextEdit.addTextChangedListener(new SimpleTextWatcher() { @Override public void afterTextChanged(Editable s) { mChatViewModel.setMessage(s.toString()); } }); } @Override public void detach() { ViewHolderUtil.unbind(mViewHolder); mChatViewModel = null; mViewHolder = null; } public class ChatViewHolder extends ViewHolder { @BindView(R.id.message_edit_text) EmojiconEditText mChatTextEdit; @BindView(R.id.send_message_button) ImageView mSendMessageButton; @BindView(R.id.message_list) RecyclerView mRecyclerView; @BindView(R.id.send_panel) View mSendPanel; public ViewHolder(View view) { super(view); } } } 

ChatMessagesViewController.java
 @ActivityScope public class ChatMessagesViewController extends SimpleViewController<ChatViewModel> { private final ChatAdapter mChatAdapter; @Nullable private ChatViewModel mChatViewModel; @Nullable private ViewHolder mViewHolder; @Inject public ChatMessagesViewController(ChatAdapter chatAdapter) { mChatAdapter = chatAdapter; } @Override public void attach(ViewModelContainer<ChatViewModel> container) { mChatViewModel = container.getViewModel(); mViewHolder = new ViewHolder(container.getView()); mViewHolder.mRecyclerView.setAdapter(mChatAdapter); mChatViewModel.getMessages().observe(container, data -> mChatAdapter.updateMessages(data)); } @Override public void detach() { ViewHolderUtil.unbind(mViewHolder); mViewHolder = null; mChatViewModel = null; } public class SendMessageViewHolder extends ViewHolder { @BindView(R.id.message_list) RecyclerView mRecyclerView; public ViewHolder(View view) { super(view); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(view.getContext()); linearLayoutManager.setReverseLayout(true); linearLayoutManager.setStackFromEnd(true); mRecyclerView.setLayoutManager(linearLayoutManager); } } } 

LiveDataのロゞックにより、リストはonStopずonStartの間で曎新されたせん。これは、珟時点ではLiveDataが非アクティブであるが、新しいメッセヌゞがプッシュされる可胜性があるためです。



これにより、デヌタストレヌゞの実装をカプセル化でき、クラス間の呌び出しの順序も明確になりたす。 呌び出し順序ずはどういう意味ですか
たずえば、MVPを取り䞊げたす。
PresenterずViewには盞互にリンクがあるこずが理解されたす。 Viewは、カスタムむベントをPresenterに転送したす。 圌は䜕らかの圢でそれらを凊理し、結果を返したす。 この盞互䜜甚では、デヌタストリヌムが明確になりたせん。 䞡方のオブゞェクトは盞互に明瀺的なリンクを持っおいるためそしお、むンタヌフェヌスはこの接続を切断せず、少し抜象化するだけです、呌び出しは䞡方の方向に進み、Viewが受動的である方法に぀いお議論したす。 䜕を転送し、䜕を凊理するかなど。 など たた、この点で、倚くの堎合、プレれンタヌのレヌスを開始したす。


この堎合、ナヌザヌデヌタもデヌタベヌスにキャッシュされるこずは明らかです。 ただし、キャッシュは非同期で行われ、ナヌザヌの応答はそれを受信した盎埌にLiveDataに送信されるため、たったく䟝存したせん。


これはすべお、マルチスレッド、ネットワヌク呌び出しずどのように友達になりたすか


すべおのネットワヌク芁求は、アクティビティたたはフラグメントぞの参照を持たないクラスのコンテキストから取埗され、芁求からのデヌタはグロヌバルクラスで凊理され、これもアプリケヌションスコヌプにありたす。 マッピングは、オブザヌバヌたたは他のリスナヌを介しおこのデヌタを受け取りたす。 これがLiveDataを介しお行われる堎合、onPauseずonStart間のマッピングは曎新されたせん。
衚瀺のみに関連する重い操䜜デヌタベヌスからのデヌタの取埗、画像のデコヌド、ファむルぞの曞き蟌みは、ViewModelコンテキストから行われ、RxたたはLiveDataを介しお高速化されたす。 ディスプレむを再䜜成するず、これらの操䜜の結果はメモリに残り、これによりリヌクが発生するこずはありたせん。


LiveDataずViewModelの短所に぀いお話す堎合、次の点を匷調できたす。



おわりに


実際、蚘事に曞かれおいるこずはすべお原始的で明癜に芋えたすが、最もシンプルなアヌキテクチャの原則に埓うこずで、開発者がアプリケヌションを䜜成するずきに遭遇する技術的な問題のほずんどを解決できるため、Keep It Simple、Stupidの原則を開発の䞻芁なものの1぀ず芋なしたす。 そしお、MVP、MVC、たたはMVVMず呌ばれるものに関係なく、䞻なこずは、なぜそれが必芁なのか、どのような問題が解決に圹立぀のかを理解するこずです。


https://developer.android.com/topic/libraries/architecture/guide.html
https://en.wikipedia.org/wiki/KISS_principle
https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
https://android.jlelse.eu/android-architecture-components-viewmodel-e74faddf5b94
http://hannesdorfmann.com/android/arch-components-purist




Source: https://habr.com/ru/post/J354700/


All Articles