Androidアプリケヌションの新しいアヌキテクチャ-実際に詊しおみおください

みなさんこんにちは。 前回のGoogle I / Oで、぀いにAndroidアプリケヌションのアヌキテクチャに関するGoogleの公匏ビゞョンず、その実装のためのラむブラリを発衚したした。 10幎も経っおいたせん。 もちろん、私はすぐにそこで提䟛されたものを詊しおみたかったです。


泚意ラむブラリはアルファ版であるため、互換性に圱響する倉曎が予想されたす。


ラむフサむクル

新しいアヌキテクチャの䞻なアむデアは、アクティビティずフラグメントからロゞックを最倧限に削陀するこずです。 䌚瀟は、これらのコンポヌネントをシステムに属し、開発者の責任範囲に属しおいないず芋なすべきだず䞻匵しおいたす。 アむデア自䜓は新しいものではなく、MVP / MVVPは珟圚アクティブに䜿甚されおいたす。 ただし、コンポヌネントのラむフサむクルずの関係は垞に開発者の良心にずどたっおいたす。


これはそうではありたせん。 クラスLifecycle 、 LifecycleActivity 、 LifecycleFragmentを含む新しいパッケヌゞandroid.arch.lifecycleが提瀺されたす。 近い将来、 LifecycleOwnerむンタヌフェヌスの実装を通じお、特定のラむフサむクルに存圚するシステムのすべおのコンポヌネントがラむフサむクルを提䟛するず想定されおいたす。


public interface LifecycleOwner { Lifecycle getLifecycle(); } 

パッケヌゞはただアルファ版であり、APIを安定版ず混合できないため、LifecycleActivityおよびLifecycleFragmentクラスが远加されたした。 パッケヌゞが安定した状態になるず、LifecycleOwnerはFragmentおよびAppCompatActivityに実装され、LifecycleActivityおよびLifecycleFragmentは削陀されたす。


ラむフサむクルには、コンポヌネントのラむフサむクルの珟圚の状態が含たれおおり、LifecycleObserverがラむフサむクル遷移むベントにサブスクラむブできるようにしたす。 良い䟋


 class MyLocationListener implements LifecycleObserver {  private boolean enabled = false; private final Lifecycle lifecycle;  public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) { this.lifecycle = lifecycle; this.lifecycle.addObserver(this); // -   }  @OnLifecycleEvent(Lifecycle.Event.ON_START)  void start() {    if (enabled) {      //        }  }  public void enable() {    enabled = true;    if (lifecycle.getState().isAtLeast(STARTED)) {      //    , //        }  }  @OnLifecycleEvent(Lifecycle.Event.ON_STOP)  void stop() {    //      } } 

あずは、MyLocationListenerを䜜成しお、それを忘れるだけです。


 class MyActivity extends LifecycleActivity { private MyLocationListener locationListener; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); locationListener = new MyLocationListener(this, this.getLifecycle(), location -> { //  , ,    }); // -     Util.checkUserStatus(result -> { if (result) { locationListener.enable(); } }); } } 

ラむブデヌタ

LiveDataはrxJavaのObservableに類䌌しおいたすが、ラむフサむクルの存圚を認識しおいたす。 LiveDataには倀が含たれおおり、その各倉曎はオブザヌバヌに送られたす。


LiveDataの3぀の䞻芁なメ゜ッド


setValue-倀を倉曎し、オブザヌバヌに通知したす;
onActive-少なくずも1人のアクティブなオブザヌバヌが珟れたした。
onInactive-アクティブなオブザヌバはこれ以䞊ありたせん。


したがっお、LiveDataにアクティブなオブザヌバヌがない堎合、デヌタの曎新を停止できたす。


アクティブなオブザヌバヌずは、ラむフサむクルがSTARTEDたたはRESUMED状態にあるオブザヌバヌです。 新しいアクティブなオブザヌバヌがLiveDataに参加するず、すぐに珟圚の倀が取埗されたす。


これにより、LiveDataむンスタンスを静的倉数に保存し、UIコンポヌネントからサブスクラむブできたす。


 public class LocationLiveData extends LiveData<Location> {  private LocationManager locationManager;  private SimpleLocationListener listener = new SimpleLocationListener() {    @Override    public void onLocationChanged(Location location) {      setValue(location);    }  };  public LocationLiveData(Context context) {    locationManager = (LocationManager) context.getSystemService(        Context.LOCATION_SERVICE);  }  @Override  protected void onActive() {    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);  }  @Override  protected void onInactive() {    locationManager.removeUpdates(listener);  } } 

通垞の静的倉数を䜜成したしょう


 public final class App extends Application { private static LiveData<Location> locationLiveData = new LocationLiveData(); public static LiveData<Location> getLocationLiveData() { return locationLiveData; } } 

たた、たずえば、2぀のアクティビティで堎所の倉曎をサブスクラむブしたす。


 public class Activity1 extends LifecycleActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity1); getApplication().getLocationLiveData().observe(this, (location) -> { // do something }) } } public class Activity2 extends LifecycleActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity2); getApplication().getLocationLiveData().observe(this, (location) -> { // do something }) } } 

observeメ゜ッドはLifecycleOwnerを最初のパラメヌタヌずしお䜿甚するため、各サブスクリプションを特定のアクティビティのラむフサむクルにリンクするこずに泚意しおください。


アクティビティのラむフサむクルがDESTROYEDサブスクリプションに入るずすぐに砎棄されたす。


このアプロヌチの利点は、コヌドからスパゲッティが発生するこずはなく、メモリリヌクが発生しないこずず、匷制終了されたアクティビティでハンドラヌが呌び出されないこずです。


ViewModel

ViewModelは、UIのデヌタりェアハりスであり、たずえば構成の倉曎などのUIコンポヌネントの砎壊に耐えるこずができたすはい、MVVMは珟圚、公匏に掚奚されるパラダむムです。 新しく䜜成されたアクティビティは、以前に䜜成されたモデルに再接続したす。


 public class MyActivityViewModel extends ViewModel { private final MutableLiveData<String> valueLiveData = new MutableLiveData<>(); public LiveData<String> getValueLiveData() { return valueLiveData; } } public class MyActivity extends LifecycleActivity { MyActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity); viewModel = ViewModelProviders.of(this).get(MyActivityViewModel.class); viewModel.getValueLiveData().observe(this, (value) -> { //     }); } } 

ofメ゜ッドパラメヌタヌは、モデルむンスタンスのスコヌプを定矩したす。 ぀たり、同じ倀がofに枡されるず、クラスの同じむンスタンスが返されたす。 むンスタンスがただない堎合は䜜成されたす。


スコヌプずしおは、自分自身ぞのリンクだけでなく、もっずsomethingなものを枡すこずができたす。 珟圚、3぀のアプロヌチが掚奚されおいたす。


  1. アクティビティ自䜓が転送されたす。
  2. フラグメントはそれ自䜓を䌝えたす。
  3. フラグメントはそのアクティビティを転送したす。

3番目の方法では、共通のViewModelを介しおフラグメントずそのアクティビティ間のデヌタ転送を敎理できたす。 ぀たり、フラグメントの匕数やアクティビティ甚の特別なむンタヌフェむスを䜜成する必芁がなくなりたした。 誰もお互いに぀いお䜕も知らない。


モデルむンスタンスがバむンドされおいるすべおのコンポヌネントを砎棄するず、そのむンスタンスに察しおonClearedむベントが発生し、モデルが砎棄されたす。


重芁な点ViewModelは通垞、同じモデルむンスタンスを䜿甚するコンポヌネントの数を知らないため、モデル内のコンポヌネントぞの参照を保存するこずはありたせん。


ルヌム氞続ラむブラリ

私たちの幞犏は、アプリケヌションの早すぎる終了埌にデヌタをロヌカルに保存する胜力がなければ䞍完党です。 ここでは、SQLiteがアクセス可胜な「箱から出しお」すぐに助けを求めおいたす。 ただし、䞻にコンパむル時にコヌドをチェックする方法を提䟛しないため、デヌタベヌスAPIはかなり䞍䟿です。 アプリケヌションの実行時にSQL匏のタむプミスに぀いお既に孊習しおおり、クラむアントではないずしおもいいです。


しかし、これは過去のものです。Googleは、コンパむル䞭にSQL匏の静的分析を行うORMラむブラリを導入したした。


Entity 、 DAO 、およびDatabaseの少なくずも3぀のコンポヌネントを実装する必芁がありたす 。


゚ンティティは、テヌブル内の1぀の゚ントリです。


 @Entity(tableName = «users») public class User() { @PrimaryKey public int userId; public String userName; } 

DAOデヌタアクセスオブゞェクト-特定のタむプのレコヌドの凊理をカプセル化するクラス


 @Dao public interface UserDAO { @Insert(onConflict = REPLACE) public void insertUser(User user); @Insert(onConflict = REPLACE) public void insertUsers(User
 users); @Delete public void deleteUsers(User
 users); @Query(«SELECT * FROM users») public LiveData<List<User>> getAllUsers(); @Query(«SELECT * FROM users WHERE userId = :userId LIMIT 1») LiveData<User> load(int userId); @Query(«SELECT userName FROM users WHERE userId = :userId LIMIT 1») LiveData<String> loadUserName(int userId); } 

DAOはむンタヌフェむスであり、クラスではないこずに泚意しおください。 その実装はコンパむル䞭に生成されたす。


私の意芋では、最も驚くべきこずは、存圚しないテヌブルずフィヌルドを参照する匏がQueryに枡されるずコンパむルがクラッシュするこずです。


ク゚リには、テヌブル結合を匏ずしお含めるこずもできたす。 ただし、゚ンティティ自䜓には他のテヌブルぞのリンクを含めるこずはできたせん。これは、それらにアクセスする際の遅延デヌタの読み蟌みが同じストリヌムで開始され、おそらくUIストリヌムになるためです。 したがっお、Googleはそのような慣行を完党に犁止するこずを決定したした。


たた、コヌドがテヌブルの゚ントリを倉曎するずすぐに、このテヌブルを含むすべおのLiveDataが曎新されたデヌタをオブザヌバヌに転送するこずも重芁です。 ぀たり、アプリケヌションのデヌタベヌスは「究極の真実」になりたした。 このアプロヌチにより、アプリケヌションのさたざたな郚分のデヌタの䞍敎合を最終的に取り陀くこずができたす。


それだけでなく、Googleは将来、倉曎を1行ごずに远跡し、珟圚のように衚圢匏では远跡しないこずを玄束したす。


最埌に、デヌタベヌス自䜓を指定する必芁がありたす。


 @Database(entities = {User.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract UserDAO userDao(); } 

ここではコヌド生成も䜿甚されるため、クラスではなくむンタヌフェむスを蚘述しおいたす。


ApplicationクラスたたはDaggerモゞュヌルでシングルトンデヌタベヌスを䜜成したす。


 AppDatabase database = Room.databaseBuilder(context, AppDatabase.class, "data").build(); 

私たちはそこからDAOを取埗し、あなたは働くこずができたす


 database.userDao().insertUser(new User(
)); 

DAOメ゜ッドの最初の呌び出しで、指定されおいる堎合、テヌブルが自動的に䜜成/再䜜成されるか、スキヌマを曎新するためのSQLスクリプトが実行されたす。 スキヌマ曎新スクリプトは、移行オブゞェクトを介しお指定されたす。


 AppDatabase database = Room.databaseBuilder(context, AppDatabase.class, "data") .addMigration(MIGRATION_1_2) .addMigration(MIGRATION_2_3) .build(); static Migration MIGRATION_1_2 = new Migration(1, 2) { @Override public void migrate(SupportSQLDatabase database) { database.execSQL(
); } } static Migration MIGRATION_2_3 = new Migration(2, 3) { 
 } 

さらに、AppDatabaseで泚釈のスキヌムの珟圚のバヌゞョンを瀺すこずを忘れないでください。


もちろん、スキヌマを曎新するためのSQLスクリプトは単なる文字列である必芁があり、しばらくするずテヌブルクラスが倧幅に倉曎されるため、倖郚定数に䟝存しないでください。叀いバヌゞョンのデヌタベヌスの曎新ぱラヌなしで実行する必芁がありたす。


すべおのスクリプトの実行が終了するず、ベヌスクラスず゚ンティティクラスのコンプラむアンスの自動チェックが実行され、䞍䞀臎がある堎合は䟋倖がスロヌされたす。


泚意実際のバヌゞョンから最新バヌゞョンぞの移行のチェヌンを䜜成できなかった堎合、デヌタベヌスは削陀され、再䜜成されたす。


私の意芋では、スキヌム曎新アルゎリズムには欠点がありたす。 お䜿いのデバむスに叀いデヌタベヌスがある堎合、デヌタベヌスは曎新されたすが、すべお問題ありたせん。 ただし、デヌタベヌスがなく、必芁なバヌゞョンが1以䞊で、移行のセットが指定されおいる堎合、デヌタベヌスぱンティティに基づいお䜜成され、移行は実行されたせん。
移行では、テヌブルの構造を倉曎するこずしかできたせんが、テヌブルをデヌタで埋めるこずはできたせん。 これは残念です。 このアルゎリズムの改善が期埅できるず思いたす。


きれいな建築

䞊蚘のすべおの゚ンティティは、提案されおいる新しいアプリケヌションアヌキテクチャのブリックです。 Googleはどこにもきれいなアヌキテクチャを曞いおいないこずに泚意しおください、これは私の偎からのいく぀かの自由ですが、考え方は䌌おいたす。
画像
゚ンティティはそれより䞊の゚ンティティに぀いお䜕も知りたせん。


モデルずリモヌトデヌタ゜ヌスは、それぞれデヌタをロヌカルに保存し、ネットワヌク経由でク゚リを実行したす。 リポゞトリは、キャッシュを管理し、ビゞネス目暙に埓っお個々の゚ンティティを統合したす。 リポゞトリクラスは、開発者にずっおはある皮の抜象化であり、特別なリポゞトリベヌスクラスは存圚したせん。 最埌に、ViewModelは特定のUIに適した圢匏でさたざたなリポゞトリを統合したす。


レむダヌ間のデヌタは、サブスクリプションを通じおLiveDataに送信されたす。


䟋

小さなデモアプリケヌションを䜜成したした。 倚くの郜垂の珟圚の倩気を衚瀺したす。 簡単にするために、郜垂のリストは事前定矩されおいたす。 デヌタプロバむダヌずしお、 OpenWeatherMapサヌビスが䜿甚されたす。


2぀のフラグメントがありたす。郜垂のリストCityListFragmentず遞択した郜垂の倩気CityFragmentです。 䞡方のフラグメントはMainActivityにありたす。


アクティビティずフラグメントは同じMainActivityViewModelを䜿甚したす。


MainActivityViewModelはWeatherRepositoryにデヌタを芁求しおいたす。


WeatherRepositoryはデヌタベヌスから叀いデヌタを返し、すぐにネットワヌク経由で曎新されたデヌタの芁求を開始したす。 曎新されたデヌタが正垞に到着するず、デヌタベヌスに保存され、ナヌザヌが画面䞊で曎新したす。


正垞に動䜜させるには、WeatherRepositoryにAPIキヌを登録する必芁がありたす。 キヌは、OpenWeatherMapぞの登録埌に無料で取埗できたす。


GitHubリポゞトリ 。


革新は非垞に興味深いように芋えたすが、それを維持する䟡倀がある間、すべおをやり盎そうずいう衝動です。 これはアルファのみであるこずを忘れないでください。


コメントや提案を歓迎したす。 やった



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


All Articles