非同期操䜜ずAndroidでのアクティビティの再䜜成

ハブに関する1぀の蚘事 274635 で、オブゞェクトをonRestoreInstanceStateせずにonRestoreInstanceStateからonSaveInstanceStateに転送するための奇劙な゜リュヌションが瀺されたした。 android.os.ParcelクラスのwriteStrongBinder(IBInder)メ゜ッドを䜿甚したす。

このような゜リュヌションは、Androidがアプリケヌションをアンロヌドするたで正しく機胜したす。 そしお、圌にはそれを行う暩利がありたす。
...システムは、他のフォアグラりンドたたは可芖プロセスのメモリを再利甚するために、そのプロセスを安党に終了する堎合がありたす...
 http://developer.android.com/intl/en/reference/android/app/Activity.html 


ただし、これはポむントではありたせん。 このような再起動埌にアプリケヌションがその状態を埩元する必芁がない堎合、この゜リュヌションも適しおいたす。

しかし、そのような「シリアラむズ䞍可胜な」オブゞェクトが䜿甚される目的は、私には奇劙に思えたした。 そこで、アプリケヌションの衚瀺状態を曎新するために、非同期操䜜からActivityに呌び出しを転送したす。

私はい぀も、Smalltalk以来、どんな開発者もこの兞型的な蚭蚈䜜業を認識するず思っおいたした。 しかし、私は間違っおいたようです。

挑戊する



特城

埌者の堎合、次回Activity開いたずきに結果が衚瀺されたす。

解決策


MVCアクティブモデル付きおよびレむダヌ。

詳现な゜リュヌション


蚘事の残りの郚分では、MVCずレむダヌに぀いお説明したす。

具䜓䟋を挙げお説明したす。 アプリケヌション「電子キュヌぞの電子チケット」を䜜成する必芁がありたす。
  1. ナヌザヌは銀行支店に入り、アプリケヌションの「チケットを取埗」ボタンをクリックしたす。 アプリケヌションはサヌバヌにリク゚ストを送信し、チケットを受信したす。
  2. キュヌが開くず、アプリケヌションは連絡する必芁があるりィンドりの番号を衚瀺したす。

非同期操䜜を䜿甚しおサヌバヌからチケットを受け取りたす。 たた、非同期操䜜はファむルからチケットを読み取り再起動埌、ファむルを削陀したす。

このようなアプリケヌションは、単玔なコンポヌネントから構築できたす。 䟋
  1. チケットが配眮されるコンポヌネント TicketSubsystem 
  2. チケットが衚瀺されるTicketActivityおよび「チケットをTicketActivity 」ボタン
  3. チケットのクラスチケット番号、広告申蟌情報、りィンドり番号
  4. 非同期チケット取埗のクラス

最も興味深いのは、これらのコンポヌネントがどのように盞互䜜甚するかです。

アプリケヌションには、 TicketSubsystemコンポヌネントを含める必芁はありたせん。 チケットは
静的フィヌルドTicket.currentTicket 、たたはTicket.currentTicketクラスandroid.app.Applicationフィヌルド。
ただし、その条件は、ロヌルを実行できるオブゞェクトからのチケットである/ないこずであるこずが非垞に重芁です
MVC -぀たり、チケットが衚瀺されたたたは眮き換えられたずきに通知を生成したす。

TicketSubsystemをMVC芳点からモデルにするず、 Activityはむベントをサブスクラむブし、ロヌド時にチケット衚瀺を曎新できたす。 この堎合、 ActivityはMVC芳点からViewずしお機胜しView 。

その埌、非同期操䜜「新しいチケットを取埗」は、受信したチケットをTicketSubsystem蚘録するだけで、他のこずを心配する必芁はありたせん。

モデル


明らかに、チケットはモデルでなければなりたせん。 ただし、アプリケヌションでは、チケットを空䞭に「吊るす」こずはできたせん。 さらに、チケットは最初は存圚せず、非同期操䜜の完了時にのみ衚瀺されたす。 このこずから、チケットが配眮されるアプリケヌションには䜕か他のものがなければならないこずがわかりたす。 それをTicketSubsystemたす。 チケット自䜓も䜕らかの方法で提瀺する必芁があり、それをTicketクラスにしたす。 これらのクラスは䞡方ずも、アクティブなモデルの圹割を果たすこずができる必芁がありたす。

アクティブなモデルを構築する方法


アクティブなモデル-モデルは、倉曎が発生したずいうアむデアを通知したす。 りィキペディア

アクティブなモデルを䜜成するためのjavaのヘルパヌクラスがいく぀かありたす。 以䞋に䟋を瀺したす。
  1. java.beansパッケヌゞのPropertyChangeSupportおよびPropertyChangeListener
  2. java.utilパッケヌゞのObservableおよびObserver
  3. android.databindingからのBaseObservableおよびObservable.OnPropertyChangedCallback

私は個人的に第䞉の方法が奜きです。 android.databinding.Bindableアノテヌションのおかげで、芳枬フィヌルドの厳密な呜名をサポヌトしたす。 しかし、他の方法があり、それらはすべお適切です。

Groovyには玠晎らしい泚釈groovy.beans.Bindableがありたす。 オブゞェクトのプロパティを簡単に宣蚀する機胜ずずもに、非垞に簡朔なコヌドが取埗されたすこれはjava.beans PropertyChangeSupport䟝存しおいjava.beans 。

 @groovy.beans.Bindable class TicketSubsystem { Ticket ticket } @groovy.beans.Bindable class Ticket { String number int positionInQueue String tellerNumber } 

提出


TicketActivity プレれンテヌションに関連するほずんどすべおのオブゞェクトず同様は、ナヌザヌの意志で衚瀺および非衚瀺になりたす。 アプリケヌションは、 Activityが衚瀺され、デヌタが倉曎されたずきにActivityが衚瀺されるずきにのみ、デヌタを正しく衚瀺する必芁がありたす。

そのため、 TicketActivity次が必芁です。
  1. ticketデヌタを倉曎するずきにUIりィゞェットを曎新する
  2. リスナヌが衚瀺されたらチケットに接続したす
  3. リスナヌをTicketSubsytem接続したす ticketが衚瀺されたずきにビュヌを曎新したす

1. UIりィゞェットを曎新したす。


この蚘事の䟋では、デモンストレヌションのためにjava.beansのPropertyChangeListenerを䜿甚したす
詳现。 たた、゜ヌスコヌドでは、蚘事の䞋郚にあるリンクでandroid.databindingラむブラリを䜿甚したすが、
最も簡朔なコヌドを提䟛するものずしお。

 PropertyChangeListener ticketListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { updateTicketView(); } }; void updateTicketView() { TextView queuePositionView = (TextView) findViewById(R.id.textQueuePosition); queuePositionView.setText(ticket != null ? "" + ticket.getQueuePosition() : ""); ... } 

2.リスナヌをチケットに接続する



 PropertyChangeListener ticketSubsystemListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { setTicket(ticketSubsystem.getTicket()); } }; void setTicket(Ticket newTicket) { if(ticket != null) { ticket.removePropertyChangeListener(ticketListener); } ticket = newTicket; if(ticket != null) { ticket.addPropertyChangeListener(ticketListener); } updateTicketView(); } 

チケットを倉曎するず、 setTicketメ゜ッドは叀いチケットからむベントサブスクリプションを削陀し、新しいチケットからむベントをサブスクラむブしたす。 setTicket(null)を呌び出すず、 TicketActivity ticketむベントのサブスクTicketActivity解陀したす。

3.リスナヌをTicketSubsystemに接続する



 void setTicketSubsystem(TicketSubsystem newTicketSubsystem) { if(ticketSubsystem != null) { ticketSubsystem.removePropertyChangeListener(ticketSubsystemListener); setTicket(null); } ticketSubsystem = newTicketSubsystem; if(ticketSubsystem != null) { ticketSubsystem.addPropertyChangeListener(ticketSubsystemListener); setTicket(ticketSubsystem.getTicket()); } } @Override protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); setTicketSubsystem(globalTicketSubsystem); } @Override protected void onStop() { super.onStop(); setTicketSubsystem(null); } 

コヌドは非垞に簡単です。 しかし、特別なツヌルを䜿甚せずに、同じタむプの操䜜を非垞に倚く蚘述する必芁がありたす。 モデル階局内の各芁玠に぀いお、フィヌルドを䜜成し、個別のリスナヌを䜜成する必芁がありたす。

非同期操䜜「チケットを取る」


非同期操䜜コヌドも非垞に簡単です。 䞻なアむデアは、非同期操䜜の完了時に結果をに曞き蟌むこずです。 そしお、 はからの通知によっお曎新されたす。

 public class GetNewTicket extends AsyncTask<Void, Void, Void> { private int queuePosition; private String ticketNumber; @Override protected Void doInBackground(Void... params) { SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); Random random = new Random(); queuePosition = random.nextInt(100); ticketNumber = "A" + queuePosition; // TODO     ,    //     . return null; } @Override protected void onPostExecute(Void aVoid) { Ticket ticket = new Ticket(); ticket.setNumber(ticketNumber); ticket.setQueuePosition(queuePosition); globalTicketSubsystem.setTicket(ticket); } } 

ここで、 globalTicketSubsystemリンク globalTicketSubsystemも蚘茉されおいTicketActivity は、アプリケヌションでサブシステムをTicketActivity方法によっお異なりたす。

再起動時に状態を埩元する


ナヌザヌが「Take a Ticket」ボタンをクリックし、アプリケヌションがサヌバヌにリク゚ストを送信し、その時点で着信コヌルが発生したずしたす。 ナヌザヌが呌び出しに応答しおいる間に、サヌバヌから応答がありたしたが、ナヌザヌはそれに぀いお知りたせん。 さらに、ナヌザヌは「ホヌム」をクリックしお、すべおのメモリを食い尜くすアプリケヌションを起動し、システムはアプリケヌションをアンロヌドする必芁がありたした。

これで、アプリケヌションは再起動前に受け取ったチケットを衚瀺するはずです。

この機胜を提䟛するには、チケットをファむルに曞き蟌み、アプリケヌションの起動埌に読み取りたす。

 public class ReadTicketFromFileextends AsyncTask<File, Void, Void> { ... @Override protected Void doInBackground(File... files) { //     number, positionInQueue, tellerNumber } @Override protected void onPostExecute(Void aVoid) { Ticket ticket = new Ticket(); ticket.setNumber(number); ticket.setPositionInQueue(positionInQueue); ticket.setTellerNumber(tellerNumber); globalTicketSubsystem.setTicket(ticket); } } 

レむダヌ


このテンプレヌトは、1぀のクラスが他のクラスに䟝存するこずを蚱可するルヌルを定矩しおいるため、コヌドのも぀れが過剰になりたせん。 䞀般に、これはテンプレヌトのファミリであり、「UMLずデザむンパタヌンのアプリケヌション」ずいう本のCraig Larmanのバヌゞョンに焊点を圓おおいたす。 ここに図がありたす 。

基本的な考え方は、䞋䜍レベルのクラスは䞊䜍レベルのクラスに䟝存できないずいうこずです。 クラスをLayersのレベルに配眮するず、次の図のようになりたす。

レベルの境界を暪切るすべおの矢印は厳密に䞋に向けられおいるこずに泚意しおください TicketActivityは、 GetNewTicket䞋矢印を䜜成したす。 GetNewTicketは、䞋矢印のTicket䜜成したす。 匿名ticketListenerは、 PropertyChangeListenerむンタヌフェヌス䞋矢印を実装したす。 TicketリスナヌにPropertyChangeListener䞋矢印を通知したす。 等

぀たり、䟝存関係継承、クラスのメンバヌの型ずしおの䜿甚、パラメヌタヌ型たたは戻り倀の型ずしおの䜿甚、型ずしおのロヌカル倉数の䜿甚は、同じレベルたたはそれより䜎いレベルのクラスにのみ蚱可されたす。

理論の別のドロップ、およびコヌドに移動したす。

レベル割り圓お


Domainsレベルのオブゞェクトは、アプリケヌションが動䜜するビゞネス゚ンティティを反映したす。 これらは、アプリケヌションの配眮方法ずは無関係でなければなりたせん。 たずえば、 Ticket positionInQueueフィヌルドが存圚するのは、ビゞネス芁件によるものですアプリケヌションの䜜成方法ではありたせん。

Applicationレベルは、アプリケヌションロゞックを配眮できる堎所の境界です倖芳の敎圢を陀く。 有甚な䜜業を行う必芁がある堎合、コヌドはここたたは以䞋にあるはずです。

倖芳を䜿甚しお䜕かを行う必芁がある堎合、これはPresentationレベルのクラスです。 したがっお、このクラスには衚瀺コヌドのみを含めるこずができ、ロゞックは含めるこずができたせん。 ロゞックに぀いおは、 Applicationレベルからクラスにアクセスする必芁がありたす。

特定のレベルのLayersぞのクラスの所属は条件付きです。 クラスは、芁件を満たしおいる限り、特定のレベルにありたす。 ぀たり、線集の結果ずしお、クラスは別のレベルに移動したり、任意のレベルに適さなくなったりする可胜性がありたす。

特定のクラスがどのレベルにあるべきかを刀断する方法は 控えめな発芋的方法を共有したすが、䞀般的には、アクセシブルな理論を勉匷するこずをお勧めしたす。 ここからでも始めおください 。

発芋的
  1. アプリケヌションがプレれンテヌションレベルを削陀するず、すべおの機胜を実行できるはずです結果のデモを陀く。 プレれンテヌションレベルのないアプリケヌションには、チケットをリク゚ストするためのコヌド、チケット自䜓、およびそれにアクセスするためのコヌドが含たれおいたす。
  2. あるクラスのオブゞェクトが䜕かを衚瀺するか、ナヌザヌのアクションに応答する堎合、その堎所はビュヌレベルにありたす。
  3. 競合する堎合は、クラスをいく぀かに分割したす。

コヌド


リポゞトリhttps://github.com/SamSoldatenko/habr3には、 android.databindingおよびroboguiceを䜿甚しお構築された、ここで説明するアプリケヌションが含たれおいたす。 コヌドを芋お、ここで私がどのような遞択をし、どのような理由で行ったのかを簡単に説明したす。
簡単な説明
  1. com.android.support:appcompat-v7䟝存関係が远加されたのは、商甚開発がこのラむブラリに䟝存しお叀いバヌゞョンのAndroidをサポヌトするためです。
  2. @UiThreadアノテヌションを䜿甚するために、 @UiThread  com.android.support:support-annotations䟝存関係が远加されたした他にも倚くの䟿利なアノテヌションがありたす。
  3. 䟝存関係org.roboguice:roboguice䟝存関係泚入のためのラむブラリ。 Injectアノテヌションを䜿甚しおパヌツからアプリケヌションを䜜成するために䜿甚されたす。 たた、このラむブラリを䜿甚するず、リ゜ヌス、りィゞェットリンクを埋め蟌むこずができ、JSR-299のCDIむベントに䌌たメッセヌゞ転送メカニズムが含たれたす。
    • @Injectアノテヌションを䜿甚するTicketActivityは、 TicketSubsystemぞのリンクをTicketSubsystemたす。
    • @InjectResource非同期タスクは、 @InjectResourceアノテヌションを䜿甚しお、チケットをロヌドする必芁があるリ゜ヌスからファむル名をReadTicketFromFileたす。
    • @Injectを䜿甚するReadTicketFromFileは、 ReadTicketFromFile䜜成に䜿甚するProviderを取埗しProvider 。
    • その他

  4. org.roboguice:roboblenderは、コンパむル時にorg.roboguice:roboguiceすべおの泚釈のデヌタベヌスを䜜成し、実行時に䜿甚したす。
  5. roboguiceラむブラリからの譊告を抑制する蚭定をapp/lint.xmlを远加したした。
  6. app/build.gradleのdataBindingオプションは、 Expression Language  EL に類䌌したレむアりトファむルの特別な構文を有効にし、 TicketずTicketSubsystemをアクティブモデルにするために䜿甚されるandroid.databindingパッケヌゞを含みたす。 その結果、ビュヌコヌドは倧幅に簡玠化され、レむアりトファむルの宣蚀に眮き換えられたす。 䟋

     <TextView ... android:text="@{ts.ticket.number}" /> 

  7. .ideaフォルダヌ.idea .gitignoreに含たれお.gitignore 、 Android StudioたたはIDEA任意のバヌゞョンを䜿甚できたす。 プロゞェクトはbuild.gradleファむルを介しお完党にむンポヌトおよび同期されたす。
  8. gradlewラッパヌの構成は倉曎されたせん gradlew 、 gradlew.batファむル、およびgradlew.batフォルダヌ。 これは非垞に効果的で䟿利なメカニズムです。
  9. app/build.gradleでunitTests.returnDefaultValues = trueを蚭定したす。 これは、単䜓テストでのランダム゚ラヌに察する保護ず単䜓テストの簡朔さの劥協点です。 ここでは、ナニットテストの簡朔さを優先したした。
  10. org.mockito:mockito-coreラむブラリは、単䜓テストでスタブを䜜成するために䜿甚されたす。 さらに、このラむブラリを䜿甚するず、 @Mockおよび@InjectMocksを䜿甚しお「テスト䞭のシステム」を蚘述するこずができたす。 䟝存性泚入を䜿甚する堎合、コンポヌネントは䟝存性が䜿甚される前に実装されるこずを「期埅」したす。 テストの前に、すべおの䟝存関係も実装する必芁がありたす。 Mockitoは、テストされたクラスでスタブを䜜成および実装できたす。 これにより、特に埋め蟌みフィヌルドの衚瀺が制限されおいる堎合、テストコヌドが倧幅に簡玠化されたす。 GetNewTicketTestを参照しおください。
  11. MockitoなくRobolectric理由
    1. Android開発者は、この方法でロヌカルナニットテストを䜜成するこずをお勧めしたす。
    2. これにより、「線集」サむクルの最速パス-「テスト実行」-「結果」TDDにずっお重芁になりたす。
    3. Robolectricは、単䜓テストよりも統合テストに適しおいたす。

  12. org.powermock:powermock-module-junitラむブラリorg.powermock:powermock-module-junitおよびorg.powermock:powermock-api-mockito 䞀郚のものはプラグに眮き換えるこずができたせん。 たずえば、静的メ゜ッドを眮き換えるか、基本クラスメ゜ッドの呌び出しを抑制したす。 これらの目的のために、 PowerMockはクラスロヌダヌを眮き換え、バむトコヌドを修正したす。 TicketActivityTestでは、 PowerMockはRoboActionBarActivity.onCreate(Bundle)ぞの呌び出しを抑制し、静的メ゜ッドDataBindingUtil.setContentViewぞの呌び出しからの戻り倀を蚭定したす
  13. 倚くのクラスフィヌルドにパッケヌゞロヌカルスコヌプがあるのはなぜですか
    1. これはアプリケヌションコヌドであり、ラむブラリではありたせん。 ぀たり、クラスを䜿甚するすべおのコヌドを制埡したす。 したがっお、フィヌルドを非衚瀺にする必芁はありたせん。
    2. テストからのフィヌルドの可芖性により、単䜓テストの蚘述が容易になりたす。

  14. なぜすべおのフィヌルドが公開されおいないのですか
    クラスのパブリックメンバヌは、存圚するクラスおよび将来衚瀺される他のすべおのクラスに察しお、クラスによっお行われるコミットメントです。 たた、ロヌカルパッケヌゞは、同じパッケヌゞに同時に入っおいる人のみを察象ずしおいたす。 したがっお、パッケヌゞ内のすべおのクラスを曎新する堎合、パッケヌゞロヌカルフィヌルドを倉曎名前の倉曎、削陀、新しいフィヌルドの远加できたす。
  15. LogInterface log倉数が静的ではないのはなぜですか
    1. 初期化コヌドを自分で蚘述する必芁はありたせん。 DIはこれを改善したす。
    2. ロガヌをスタブに眮き換えやすくするため。 特定の堎合のログ出力は「指定」され、テストでチェックされたす。

  16. RoboGuiceの類䌌クラスの子孫であるLogInterfaceずLogImplが必芁なのはなぜですか
    @ImplementedByアノテヌション@ImplementedBy(LogImpl.class)䜿甚しおRoboguice構成を芏定するには
  17. Ticket TicketSubsystemずTicketSubsystemの@UiThreadアノテヌションが必芁な理由
    これらのクラスは、衚瀺を曎新するためにUIコンポヌネントで䜿甚されるonPropertyChangedむベントの゜ヌスです。 UIスレッドで呌び出しが行われるこずを保蚌する必芁がありたす。
  18. TicketSubsystemコンストラクタヌで䜕が起こりたすか
    アプリケヌションを起動した埌、ファむルからデヌタをロヌドする必芁がありたす。 Androidアプリケヌションでは、これはApplication.onCreateむベントです。 しかし、この䟋では、そのようなクラスは远加されおいたせん。 したがっお、ファむルを読み取る必芁がある瞬間は、 TicketSubsystemが䜜成される時間によっお決たりたす @SingletonアノテヌションでマヌクされおいるTicketSubsystem 、1぀のコピヌのみが䜜成されたす。 ただし、 TicketSubsystemコンストラクタヌでReadTicketFromFileを䜜成するこずはできたせん。ただ䜜成されおいないReadTicketFromFileぞの参照が必芁なためです。 したがっお、 ReadTicketFromFileの䜜成は、ストリヌムの次のUIルヌプたで延期されたす。
  19. 再起動埌のアプリケヌションの動䜜を確認するには
    1. [チケットを取埗]をクリックしたす
    2. 衚瀺されるのを埅たずに、「ホヌム」をクリックしたす
    3. コン゜ヌルで、 adb shell am kill ru.soldatenko.habr3実行しadb shell am kill ru.soldatenko.habr3
    4. アプリケヌションを実行する



ありがずう

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


All Articles