├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── mycommons │ │ └── androidarchitecturecomponents │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── mycommons │ │ │ └── androidarchitecturecomponents │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── cn │ └── mycommons │ └── androidarchitecturecomponents │ └── ExampleUnitTest.java ├── build.gradle ├── doc ├── final-architecture.png ├── lifecycle-states.png ├── room_architecture.png └── viewmodel-lifecycle.png ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 浅谈Android Architecture Components 2 | 3 | --- 4 | 5 | [TOC] 6 | 7 | ## 简介 8 | 9 | Google IO 2017发布Android Architecture Components,自己先研究了一下,蛮有意思的,特来记录一下。本文内容主要是参考[官方文档](https://developer.android.com/topic/libraries/architecture/index.html)以及自己的理解,如有错误之处,恳请指出。 10 | 11 | ## Android Architecture Components 12 | 13 | Android Architecture Components是Google发布的一套新的架构组件,使App的架构更加健壮,后面简称AAC。 14 | 15 | AAC主要提供了Lifecycle,ViewModel,LiveData,Room等功能,下面依次说明: 16 | 17 | * Lifecycle 18 | 19 | 生命周期管理,把原先Android生命周期的中的代码抽取出来,如将原先需要在onStart()等生命周期中执行的代码分离到Activity或者Fragment之外。 20 | 21 | * LiveData 22 | 23 | 一个数据持有类,持有数据并且这个数据可以被观察被监听,和其他Observer不同的是,它是和Lifecycle是绑定的,在生命周期内使用有效,减少内存泄露和引用问题。 24 | 25 | * ViewModel 26 | 27 | 用于实现架构中的ViewModel,同时是与Lifecycle绑定的,使用者无需担心生命周期。可以在多个Fragment之间共享数据,比如旋转屏幕后Activity会重新create,这时候使用ViewModel还是之前的数据,不需要再次请求网络数据。 28 | 29 | * Room 30 | 31 | 谷歌推出的一个Sqlite ORM库,不过使用起来还不错,使用注解,极大简化数据库的操作,有点类似Retrofit的风格。 32 | 33 | AAC的架构是这样的: 34 | 35 | ![新的架构](https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/master/doc/final-architecture.png) 36 | 37 | * Activity/Fragment 38 | 39 | UI层,通常是Activity/Fragment等,监听ViewModel,当VIewModel数据更新时刷新UI,监听用户事件反馈到ViewModel,主流的数据驱动界面。 40 | 41 | * ViewModel 42 | 43 | 持有或保存数据,向Repository中获取数据,响应UI层的事件,执行响应的操作,响应数据变化并通知到UI层。 44 | 45 | * Repository 46 | 47 | App的完全的数据模型,ViewModel交互的对象,提供简单的数据修改和获取的接口,配合好网络层数据的更新与本地持久化数据的更新,同步等 48 | 49 | * Data Source 50 | 51 | 包含本地的数据库等,网络api等,这些基本上和现有的一些MVVM,以及Clean架构的组合比较相似 52 | 53 | ### Gradle 集成 54 | 55 | 根目录gradle文件中添加Google Maven Repository 56 | 57 | ```gradle 58 | allprojects { 59 | repositories { 60 | jcenter() 61 | maven { url 'https://maven.google.com' } 62 | } 63 | } 64 | ``` 65 | 66 | 在模块中添加对应的依赖 67 | 68 | 如使用Lifecycle,LiveData、ViewModel,添加如下依赖。 69 | 70 | ```gradle 71 | compile "android.arch.lifecycle:runtime:1.0.0-alpha1" 72 | compile "android.arch.lifecycle:extensions:1.0.0-alpha1" 73 | annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha1" 74 | ``` 75 | 76 | 如使用Room功能,添加如下依赖。 77 | 78 | ```gradle 79 | compile "android.arch.persistence.room:runtime:1.0.0-alpha1" 80 | annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha1" 81 | 82 | // For testing Room migrations, add: 83 | testCompile "android.arch.persistence.room:testing:1.0.0-alpha1" 84 | 85 | // For Room RxJava support, add: 86 | compile "android.arch.persistence.room:rxjava2:1.0.0-alpha1" 87 | ``` 88 | 89 | ## LifeCycles 90 | 91 | Android开发中,经常需要管理生命周期。举个栗子,我们需要获取用户的地址位置,当这个Activity在显示的时候,我们开启定位功能,然后实时获取到定位信息,当页面被销毁的时候,需要关闭定位功能。 92 | 93 | 下面是简单的示例代码。 94 | 95 | ```java 96 | class MyLocationListener { 97 | public MyLocationListener(Context context, Callback callback) { 98 | // ... 99 | } 100 | 101 | void start() { 102 | // connect to system location service 103 | } 104 | 105 | void stop() { 106 | // disconnect from system location service 107 | } 108 | } 109 | 110 | class MyActivity extends AppCompatActivity { 111 | 112 | private MyLocationListener myLocationListener; 113 | 114 | public void onCreate(...) { 115 | myLocationListener = new MyLocationListener(this, (location) -> { 116 | // update UI 117 | }); 118 | } 119 | 120 | public void onStart() { 121 | super.onStart(); 122 | myLocationListener.start(); 123 | } 124 | 125 | public void onStop() { 126 | super.onStop(); 127 | myLocationListener.stop(); 128 | } 129 | } 130 | ``` 131 | 132 | 上面只是一个简单的场景,我们来个复杂一点的场景。当定位功能需要满足一些条件下才开启,那么会变得复杂多了。可能在执行Activity的stop方法时,定位的start方法才刚刚开始执行,比如如下代码,这样生命周期管理就变得很麻烦了。 133 | 134 | ```java 135 | class MyActivity extends AppCompatActivity { 136 | private MyLocationListener myLocationListener; 137 | 138 | public void onCreate(...) { 139 | myLocationListener = new MyLocationListener(this, location -> { 140 | // update UI 141 | }); 142 | } 143 | 144 | public void onStart() { 145 | super.onStart(); 146 | Util.checkUserStatus(result -> { 147 | // what if this callback is invoked AFTER activity is stopped? 148 | if (result) { 149 | myLocationListener.start(); 150 | } 151 | }); 152 | } 153 | 154 | public void onStop() { 155 | super.onStop(); 156 | myLocationListener.stop(); 157 | } 158 | } 159 | ``` 160 | 161 | AAC中提供了Lifecycle,用来帮助我们解决这样的问题。LifeCycle使用2个枚举类来解决生命周期管理问题。一个是事件,一个是状态。 162 | 163 | 事件: 164 | 165 | 生命周期事件由系统来分发,这些事件对于与Activity和Fragment的生命周期函数。 166 | 167 | 状态: 168 | 169 | Lifecycle的状态,用于追踪中Lifecycle对象,如下图所示。 170 | 171 | ![Lifecycle的状态](https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/master/doc/lifecycle-states.png) 172 | 173 | 上面的定位功能代码,使用LifeCycle实现以后是这样的,实现一个`LifecycleObserver`接口,然后用注解标注状态,最后在LifecycleOwner中添加监听。 174 | 175 | ```java 176 | public class MyObserver implements LifecycleObserver { 177 | 178 | @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) 179 | public void onResume() { 180 | } 181 | 182 | @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) 183 | public void onPause() { 184 | } 185 | } 186 | 187 | aLifecycleOwner.getLifecycle().addObserver(new MyObserver()); 188 | ``` 189 | 190 | 上面代码中用到了`aLifecycleOwner`是`LifecycleOwner`接口对象,`LifecycleOwner`是一个只有一个方法的接口`getLifecycle()`,需要由子类来实现。 191 | 192 | 在Lib中已经有实现好的子类,我们可以直接拿来使用。比如`LifecycleActivity`和`LifecycleFragment`,我们只需要继承此类就行了。 193 | 194 | 当然实际开发的时候我们会自己定义BaseActivity,Java是单继承模式,那么需要自己实现`LifecycleRegistryOwner`接口。 195 | 196 | 如下所示即可,代码很近简单 197 | 198 | ```java 199 | public class BaseFragment extends Fragment implements LifecycleRegistryOwner { 200 | 201 | LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this); 202 | 203 | @Override 204 | public LifecycleRegistry getLifecycle() { 205 | return lifecycleRegistry; 206 | } 207 | } 208 | ``` 209 | 210 | ## LiveData 211 | 212 | LiveData 是一个 Data Holder 类,可以持有数据,同时这个数据可以被监听的,当数据改变的时候,可以触发回调。但是又不像普通的`Observable`,LiveData绑定了App的组件,LiveData可以指定在LifeCycle的某个状态被触发。比如LiveData可以指定在LifeCycle的 STARTED 或 RESUMED状体被触发。 213 | 214 | ```java 215 | public class LocationLiveData extends LiveData { 216 | 217 | private LocationManager locationManager; 218 | 219 | private SimpleLocationListener listener = new SimpleLocationListener() { 220 | @Override 221 | public void onLocationChanged(Location location) { 222 | setValue(location); 223 | } 224 | }; 225 | 226 | public LocationLiveData(Context context) { 227 | locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 228 | } 229 | 230 | @Override 231 | protected void onActive() { 232 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener); 233 | } 234 | 235 | @Override 236 | protected void onInactive() { 237 | locationManager.removeUpdates(listener); 238 | } 239 | } 240 | ``` 241 | 242 | * onActive() 243 | 244 | 这个方法在LiveData在被激活的状态下执行,我们可以开始执行一些操作。 245 | 246 | * onActive() 247 | 248 | 这个方法在LiveData在的失去活性状态下执行,我们可以结束执行一些操作。 249 | 250 | * setValue() 251 | 252 | 执行这个方法的时候,LiveData可以触发它的回调。 253 | 254 | `LocationLiveData`可以这样使用。 255 | 256 | ```java 257 | public class MyFragment extends LifecycleFragment { 258 | 259 | public void onActivityCreated (Bundle savedInstanceState) { 260 | LiveData myLocationListener = ...; 261 | Util.checkUserStatus(result -> { 262 | if (result) { 263 | myLocationListener.addObserver(this, location -> { 264 | // update UI 265 | }); 266 | } 267 | }); 268 | } 269 | } 270 | ``` 271 | 272 | 注意,上面的`addObserver`方法,必须传`LifecycleOwner`对象,也就是说添加的对象必须是可以被LifeCycle管理的。 273 | 274 | 如果LifeCycle没有触发对对应的状态(STARTED or RESUMED),它的值被改变了,那么Observe就不会被执行, 275 | 276 | 如果LifeCycle被销毁了,那么Observe将自动被删除。 277 | 278 | 实际上LiveData就提供一种新的供数据共享方式。可以用它在多个Activity、Fragment等其他有生命周期管理的类中实现数据共享。 279 | 280 | 还是上面的定位例子。 281 | 282 | ```java 283 | public class LocationLiveData extends LiveData { 284 | 285 | private static LocationLiveData sInstance; 286 | 287 | private LocationManager locationManager; 288 | 289 | @MainThread 290 | public static LocationLiveData get(Context context) { 291 | if (sInstance == null) { 292 | sInstance = new LocationLiveData(context.getApplicationContext()); 293 | } 294 | return sInstance; 295 | } 296 | 297 | private SimpleLocationListener listener = new SimpleLocationListener() { 298 | @Override 299 | public void onLocationChanged(Location location) { 300 | setValue(location); 301 | } 302 | }; 303 | 304 | private LocationLiveData(Context context) { 305 | locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 306 | } 307 | 308 | @Override 309 | protected void onActive() { 310 | locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener); 311 | } 312 | 313 | @Override 314 | protected void onInactive() { 315 | locationManager.removeUpdates(listener); 316 | } 317 | } 318 | ``` 319 | 320 | 然后在Fragment中调用。 321 | 322 | ```java 323 | public class MyFragment extends LifecycleFragment { 324 | 325 | public void onActivityCreated (Bundle savedInstanceState) { 326 | 327 | Util.checkUserStatus(result -> { 328 | if (result) { 329 | LocationLiveData.get(getActivity()).observe(this, location -> { 330 | // update UI 331 | }); 332 | } 333 | }); 334 | } 335 | } 336 | ``` 337 | 338 | 从上面的示例,可以得到使用LiveData优点: 339 | 340 | * 没有内存泄露的风险,全部绑定到对应的生命周期,当LifeCycle被销毁的时候,它们也自动被移除 341 | 342 | * 降低Crash,当Activity被销毁的时候,LiveData的Observer自动被删除,然后UI就不会再接受到通知 343 | 344 | * 实时数据,因为LiveData是持有真正的数据的,所以当生命周期又重新开始的时候,又可以重新拿到数据 345 | 346 | * 正常配置改变,当Activity或者Fragment重新创建的时候,可以从LiveData中获取上一次有用的数据 347 | 348 | * 不再需要手动的管理生命周期 349 | 350 | 351 | ### Transformations 352 | 353 | 有时候需要对一个LiveData做Observer,但是这个LiveData是依赖另外一个LiveData,有点类似于RxJava中的操作符,我们可以这样做。 354 | 355 | * Transformations.map() 356 | 357 | 用于事件流的传递,用于触发下游数据。 358 | 359 | ```java 360 | LiveData userLiveData = ...; 361 | LiveData userName = Transformations.map(userLiveData, user -> { 362 | user.name + " " + user.lastName 363 | }); 364 | ``` 365 | 366 | * Transformations.switchMap() 367 | 368 | 这个和map类似,只不过这个是用来触发上游数据。 369 | 370 | ```java 371 | private LiveData getUser(String id) { 372 | // ... 373 | } 374 | 375 | LiveData userId = ...; 376 | LiveData user = Transformations.switchMap(userId, id -> getUser(id) ); 377 | ``` 378 | 379 | ## ViewModel 380 | 381 | ViewModel是用来存储UI层的数据,以及管理对应的数据,当数据修改的时候,可以马上刷新UI。 382 | 383 | Android系统提供控件,比如Activity和Fragment,这些控件都是具有生命周期方法,这些生命周期方法被系统调用。 384 | 385 | 当这些控件被销毁或者被重建的时候,如果数据保存在这些对象中,那么数据就会丢失。比如在一个界面,保存了一些用户信息,当界面重新创建的时候,就需要重新去获取数据。当然了也可以使用控件自动再带的方法,在`onSaveInstanceState`方法中保存数据,在onCreate中重新获得数据,但这仅仅在数据量比较小的情况下。如果数据量很大,这种方法就不能适用了。 386 | 387 | 另外一个问题就是,经常需要在Activity中加载数据,这些数据可能是异步的,因为获取数据需要花费很长的时间。那么Activity就需要管理这些数据调用,否则很有可能会产生内存泄露问题。最后需要做很多额外的操作,来保证程序的正常运行。 388 | 389 | 同时Activity不仅仅只是用来加载数据的,还要加载其他资源,做其他的操作,最后Activity类变大,就是我们常讲的上帝类。也有不少架构是把一些操作放到单独的类中,比如MVP就是这样,创建相同类似于生命周期的函数做代理,这样可以减少Activity的代码量,但是这样就会变得很复杂,同时也难以测试。 390 | 391 | AAC中提供ViewModel可以很方便的用来管理数据。我们可以利用它来管理UI组件与数据的绑定关系。ViewModel提供自动绑定的形式,当数据源有更新的时候,可以自动立即的更新UI。 392 | 393 | 下面是一个简单的代码示例。 394 | 395 | ```java 396 | public class MyViewModel extends ViewModel { 397 | 398 | private MutableLiveData> users; 399 | public LiveData> getUsers() { 400 | if (users == null) { 401 | users = new MutableLiveData>(); 402 | loadUsers(); 403 | } 404 | return users; 405 | } 406 | 407 | private void loadUsers() { 408 | // do async operation to fetch users 409 | } 410 | } 411 | ``` 412 | 413 | ```java 414 | public class MyActivity extends AppCompatActivity { 415 | 416 | public void onCreate(Bundle savedInstanceState) { 417 | MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class); 418 | model.getUsers().observe(this, users -> { 419 | // update UI 420 | }); 421 | } 422 | } 423 | ``` 424 | 425 | 当我们获取ViewModel实例的时候,ViewModel是通过ViewModelProvider保存在LifeCycle中,ViewModel会一直保存在LifeCycle中,直到Activity或者Fragment销毁了,触发LifeCycle被销毁,那么ViewModel也会被销毁的。下面是ViewModel的生命周期图。 426 | 427 | ![](https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/master/doc/viewmodel-lifecycle.png) 428 | 429 | ## Room 430 | 431 | Room是一个持久化工具,和ormlite、greenDao类似,都是ORM工具。在开发中我们可以利用Room来操作sqlite数据库。 432 | 433 | Room主要分为三个部分: 434 | 435 | * Database 436 | 437 | 使用注解申明一个类,注解中包含若干个Entity类,这个Database类主要负责创建数据库以及获取数据对象的。 438 | 439 | * Entity 440 | 441 | 表示每个数据库的总的一个表结构,同样也是使用注解表示,类中的每个字段都对应表中的一列。 442 | 443 | * DAO 444 | 445 | DAO是 Data Access Object的缩写,表示从从代码中直接访问数据库,屏蔽sql语句。 446 | 447 | 下面是官方给的结构图。 448 | 449 | ![](https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/master/doc/room_architecture.png) 450 | 451 | 代码示例: 452 | 453 | ```java 454 | // User.java 455 | @Entity 456 | public class User { 457 | @PrimaryKey 458 | private int uid; 459 | 460 | @ColumnInfo(name = "first_name") 461 | private String firstName; 462 | 463 | @ColumnInfo(name = "last_name") 464 | private String lastName; 465 | 466 | // Getters and setters are ignored for brevity, 467 | // but they're required for Room to work. 468 | } 469 | 470 | // UserDao.java 471 | @Dao 472 | public interface UserDao { 473 | @Query("SELECT * FROM user") 474 | List getAll(); 475 | 476 | @Query("SELECT * FROM user WHERE uid IN (:userIds)") 477 | List loadAllByIds(int[] userIds); 478 | 479 | @Query("SELECT * FROM user WHERE first_name LIKE :first AND " 480 | + "last_name LIKE :last LIMIT 1") 481 | User findByName(String first, String last); 482 | 483 | @Insert 484 | void insertAll(User... users); 485 | 486 | @Delete 487 | void delete(User user); 488 | } 489 | 490 | // AppDatabase.java 491 | @Database(entities = {User.class}, version = 1) 492 | public abstract class AppDatabase extends RoomDatabase { 493 | public abstract UserDao userDao(); 494 | } 495 | ``` 496 | 497 | 最后在代码中调用Database对象 498 | 499 | ```java 500 | AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build(); 501 | ``` 502 | 503 | **注意:** Database最好设计成单利模式,否则对象太多会有性能的影响。 504 | 505 | 506 | ### Entities 507 | 508 | ```java 509 | @Entity 510 | class User { 511 | @PrimaryKey 512 | public int id; 513 | 514 | @ColumnInfo(name = "first_name") 515 | public String firstName; 516 | 517 | @ColumnInfo(name = "last_name") 518 | public String lastName; 519 | 520 | @Ignore 521 | Bitmap picture; 522 | } 523 | ``` 524 | 525 | @Entity用来注解一个实体类,对应数据库一张表。默认情况下,Room为实体中定义的每个成员变量在数据中创建对应的字段,我们可能不想保存到数据库的字段,这时候就要用道@Ignore注解。 526 | 527 | **注意:** 为了保存每一个字段,这个字段需要有可以访问的gettter/setter方法或者是public的属性 528 | 529 | ### Entity的参数 primaryKeys 530 | 531 | 每个实体必须至少定义1个字段作为主键,即使只有一个成员变量,除了使用@PrimaryKey 将字段标记为主键的方式之外,还可以通过在@Entity注解中指定参数的形式 532 | 533 | ```java 534 | @Entity(primaryKeys = {"firstName", "lastName"}) 535 | class User { 536 | public String firstName; 537 | public String lastName; 538 | 539 | @Ignore 540 | Bitmap picture; 541 | } 542 | ``` 543 | 544 | ### Entity的参数 tableName 545 | 546 | 默认情况下,Room使用类名作为数据库表名。如果你想表都有一个不同的名称,就可以在@Entity中使用tableName参数指定 547 | 548 | ```java 549 | @Entity(tableName = "users") 550 | class User { 551 | ... 552 | } 553 | ``` 554 | 555 | 和tableName作用类似; @ColumnInfo注解是改变成员变量对应的数据库的字段名称。 556 | 557 | ### Entity的参数 indices 558 | 559 | indices的参数值是@Index的数组,在某些情况写为了加快查询速度我们可以需要加入索引 560 | 561 | ```java 562 | @Entity(indices = {@Index("name"), @Index("last_name", "address")}) 563 | class User { 564 | @PrimaryKey 565 | public int id; 566 | 567 | public String firstName; 568 | public String address; 569 | 570 | @ColumnInfo(name = "last_name") 571 | public String lastName; 572 | 573 | @Ignore 574 | Bitmap picture; 575 | } 576 | ``` 577 | 578 | 有时,数据库中某些字段或字段组必须是唯一的。通过将@Index的unique 设置为true,可以强制执行此唯一性属性。 579 | 580 | 下面的代码示例防止表有两行包含FirstName和LastName列值相同的一组: 581 | 582 | ```java 583 | @Entity(indices = {@Index(value = {"first_name", "last_name"}, unique = true)}) 584 | class User { 585 | @PrimaryKey 586 | public int id; 587 | 588 | @ColumnInfo(name = "first_name") 589 | public String firstName; 590 | 591 | @ColumnInfo(name = "last_name") 592 | public String lastName; 593 | 594 | @Ignore 595 | Bitmap picture; 596 | } 597 | ``` 598 | 599 | ### Entity的参数 foreignKeys 600 | 601 | 因为SQLite是一个关系型数据库,你可以指定对象之间的关系。尽管大多数ORM库允许实体对象相互引用,但Room明确禁止。实体之间没有对象引用。 602 | 603 | 不能使用直接关系,所以就要用到foreignKeys(外键)。 604 | 605 | ```java 606 | @Entity(foreignKeys = @ForeignKey(entity = User.class, 607 | parentColumns = "id", 608 | childColumns = "user_id")) 609 | class Book { 610 | @PrimaryKey 611 | public int bookId; 612 | 613 | public String title; 614 | 615 | @ColumnInfo(name = "user_id") 616 | public int userId; 617 | } 618 | ``` 619 | 620 | 外键是非常强大的,因为它允许指定引用实体更新时发生的操作。例如,级联删除,你可以告诉SQLite删除所有书籍的用户如果用户对应的实例是由包括OnDelete =CASCADE在@ForeignKey注释。ON_CONFLICT : @Insert(onConflict=REPLACE) REMOVE 或者 REPLACE 621 | 622 | 有时候可能还需要对象嵌套这时候可以用@Embedded注解 623 | 624 | ```java 625 | class Address { 626 | public String street; 627 | public String state; 628 | public String city; 629 | 630 | @ColumnInfo(name = "post_code") 631 | public int postCode; 632 | } 633 | 634 | @Entity 635 | class User { 636 | @PrimaryKey 637 | public int id; 638 | 639 | public String firstName; 640 | 641 | @Embedded 642 | public Address address; 643 | } 644 | ``` 645 | 646 | ### Dao 647 | 648 | ```java 649 | @Dao 650 | public interface UserDao { 651 | @Query("SELECT * FROM user") 652 | List getAll(); 653 | 654 | @Query("SELECT * FROM user WHERE uid IN (:userIds)") 655 | List loadAllByIds(int[] userIds); 656 | 657 | @Query("SELECT * FROM user WHERE first_name LIKE :first AND " 658 | + "last_name LIKE :last LIMIT 1") 659 | User findByName(String first, String last); 660 | 661 | @Insert 662 | void insertAll(User... users); 663 | 664 | @Delete 665 | void delete(User user); 666 | } 667 | ``` 668 | 669 | 数据访问对象Data Access Objects (DAOs)是一种抽象访问数据库的干净的方式。 670 | 671 | ### DAO的Insert 操作 672 | 673 | 当创建DAO方法并用@Insert注释它时,生成一个实现时会在一个事务中完成插入所有参数到数据库。 674 | 675 | ```java 676 | @Dao 677 | public interface MyDao { 678 | @Insert(onConflict = OnConflictStrategy.REPLACE) 679 | public void insertUsers(User... users); 680 | 681 | @Insert 682 | public void insertBothUsers(User user1, User user2); 683 | 684 | @Insert 685 | public void insertUsersAndFriends(User user, List friends); 686 | } 687 | ``` 688 | 689 | ### DAO的Update、Delete操作 690 | 691 | 与上面类似 692 | 693 | ```java 694 | @Dao 695 | public interface MyDao { 696 | @Update 697 | public void updateUsers(User... users); 698 | 699 | @Delete 700 | public void deleteUsers(User... users); 701 | } 702 | ``` 703 | 704 | ### DAO的Query 操作 705 | 706 | 一个简单查询示例 707 | 708 | ```java 709 | @Dao 710 | public interface MyDao { 711 | @Query("SELECT * FROM user") 712 | public User[] loadAllUsers(); 713 | } 714 | ``` 715 | 716 | 稍微复杂的,带参数的查询操作 717 | 718 | ``` 719 | @Dao 720 | public interface MyDao { 721 | @Query("SELECT * FROM user WHERE age > :minAge") 722 | public User[] loadAllUsersOlderThan(int minAge); 723 | } 724 | ``` 725 | 726 | 也可以带多个参数 727 | 728 | ```java 729 | @Dao 730 | public interface MyDao { 731 | @Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge") 732 | public User[] loadAllUsersBetweenAges(int minAge, int maxAge); 733 | 734 | @Query("SELECT * FROM user WHERE first_name LIKE :search OR last_name LIKE :search") 735 | public List findUserWithName(String search); 736 | } 737 | ``` 738 | 739 | ### 返回子集 740 | 741 | 上面示例都是查询一个表中的所有字段,结果用对应的Entity即可,但是如果我只要其中的几个字段,那么该怎么使用呢? 742 | 743 | 比如上面的User,我只需要firstName和lastName,首先定义一个子集,然后结果改成对应子集即可。 744 | 745 | ```java 746 | public class NameTuple { 747 | @ColumnInfo(name="first_name") 748 | public String firstName; 749 | 750 | @ColumnInfo(name="last_name") 751 | public String lastName; 752 | } 753 | 754 | @Dao 755 | public interface MyDao { 756 | @Query("SELECT first_name, last_name FROM user") 757 | public List loadFullName(); 758 | } 759 | ``` 760 | 761 | ### 支持集合参数 762 | 763 | 有个这样一个查询需求,比如要查询某两个地区的所有用户,直接用sql中的in即可,但是如果这个地区是程序指定的,个数不确定,那么改怎么办? 764 | 765 | ``` 766 | @Dao 767 | public interface MyDao { 768 | @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") 769 | public List loadUsersFromRegions(List regions); 770 | } 771 | ``` 772 | 773 | ### 支持Observable 774 | 775 | 前面提到了LiveData,可以异步的获取数据,那么我们的Room也是支持异步查询的。 776 | 777 | ``` 778 | @Dao 779 | public interface MyDao { 780 | @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)") 781 | public LiveData> loadUsersFromRegionsSync(List regions); 782 | } 783 | ``` 784 | 785 | ### 支持RxJava 786 | 787 | RxJava是另外一个异步操作库,同样也是支持的。 788 | 789 | ```java 790 | @Dao 791 | public interface MyDao { 792 | @Query("SELECT * from user where id = :id LIMIT 1") 793 | public Flowable loadUserById(int id); 794 | } 795 | ``` 796 | 797 | ### 支持Cursor 798 | 799 | 原始的Android系统查询结果是通过Cursor来获取的,同样也支持。 800 | 801 | ```java 802 | @Dao 803 | public interface MyDao { 804 | @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5") 805 | public Cursor loadRawUsersOlderThan(int minAge); 806 | } 807 | ``` 808 | 809 | ### 多表查询 810 | 811 | 有时候数据库存在范式相关,数据拆到了多个表中,那么就需要关联多个表进行查询,如果结果只是一个表的数据,那么很简单,直接用Entity定义的类型即可。 812 | 813 | ```java 814 | @Dao 815 | public interface MyDao { 816 | @Query("SELECT * FROM book " 817 | + "INNER JOIN loan ON loan.book_id = book.id " 818 | + "INNER JOIN user ON user.id = loan.user_id " 819 | + "WHERE user.name LIKE :userName") 820 | public List findBooksBorrowedByNameSync(String userName); 821 | } 822 | ``` 823 | 824 | 如果结果是部分字段,同上面一样,需要单独定义一个POJO,来接受数据。 825 | 826 | ```java 827 | public class UserPet { 828 | public String userName; 829 | public String petName; 830 | } 831 | 832 | @Dao 833 | public interface MyDao { 834 | @Query("SELECT user.name AS userName, pet.name AS petName " 835 | + "FROM user, pet " 836 | + "WHERE user.id = pet.user_id") 837 | public LiveData> loadUserAndPetNames(); 838 | } 839 | 840 | ``` 841 | 842 | ### 类型转换 843 | 844 | 有时候Java定义的数据类型和数据库中存储的数据类型是不一样的,Room提供类型转换,这样在操作数据库的时候,可以自动转换。 845 | 846 | 比如在Java中,时间用Date表示,但是在数据库中类型确实long,这样有利于存储。 847 | 848 | ```java 849 | public class Converters { 850 | @TypeConverter 851 | public static Date fromTimestamp(Long value) { 852 | return value == null ? null : new Date(value); 853 | } 854 | 855 | @TypeConverter 856 | public static Long dateToTimestamp(Date date) { 857 | return date == null ? null : date.getTime(); 858 | } 859 | } 860 | ``` 861 | 862 | 定义数据库时候需要指定类型转换,同时定义好Entity和Dao。 863 | 864 | ```java 865 | @Database(entities = {User.java}, version = 1) 866 | @TypeConverters({Converter.class}) 867 | public abstract class AppDatabase extends RoomDatabase { 868 | public abstract UserDao userDao(); 869 | } 870 | 871 | @Entity 872 | public class User { 873 | ... 874 | private Date birthday; 875 | } 876 | 877 | @Dao 878 | public interface UserDao { 879 | ... 880 | @Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to") 881 | List findUsersBornBetweenDates(Date from, Date to); 882 | } 883 | ``` 884 | 885 | ### 数据库升级 886 | 887 | 版本迭代中,我们不可避免的会遇到数据库升级问题,Room也为我们提供了数据库升级的处理接口。 888 | 889 | ``` 890 | Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name").addMigrations(MIGRATION_1_2, MIGRATION_2_3).build(); 891 | 892 | static final Migration MIGRATION_1_2 = new Migration(1, 2) { 893 | @Override 894 | public void migrate(SupportSQLiteDatabase database) { 895 | database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))"); 896 | } 897 | }; 898 | 899 | static final Migration MIGRATION_2_3 = new Migration(2, 3) { 900 | @Override 901 | public void migrate(SupportSQLiteDatabase database) { 902 | database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER"); 903 | } 904 | }; 905 | ``` 906 | 907 | 迁移过程结束后,Room将验证架构以确保迁移正确发生。如果Room发现问题,则抛出包含不匹配信息的异常。 908 | 909 | **警告:** 如果不提供必要的迁移,Room会重新构建数据库,这意味着将丢失数据库中的所有数据。 910 | 911 | 912 | ### 输出模式 913 | 914 | 可以在gradle中设置开启输出模式,便于我们调试,查看数据库表情况,以及做数据库迁移。 915 | 916 | ```java 917 | android { 918 | ... 919 | defaultConfig { 920 | ... 921 | javaCompileOptions { 922 | annotationProcessorOptions { 923 | arguments = ["room.schemaLocation": 924 | "$projectDir/schemas".toString()] 925 | } 926 | } 927 | } 928 | } 929 | ``` 930 | 931 | ## Sample 932 | 933 | 这里是官方示例,本人自己参考官方文档和示例[Android Architecture Components samples](https://github.com/googlesamples/android-architecture-components)后,也写出了一个类似的示例项目[XiaoxiaZhihu_AAC](https://github.com/LiushuiXiaoxia/XiaoxiaZhihu_AAC),还请多多指教。 934 | 935 | ## 总结 936 | 937 | 原先IO是在5月底已经结束,本来想尽快参照官方文档和示例,把API撸一遍,然后写个Demo和文章来介绍一下。代码示例早已经写出来了,但6月分工作太忙,然后又出差到北京,最后等到了6月底了,才把这篇文章给写出来了。中间可能有内容以及改变,如果有发现稳重有错误,请及时指出,不理赐教。同时以后做事一定要有始有终,确定的事一定要坚持。 938 | 939 | ## 相关资料 940 | 941 | [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/index.html) 942 | 943 | [简单聊聊Android Architecture Componets](http://www.cocoachina.com/android/20170519/19309.html) 944 | 945 | [Android Architecture Components samples](https://github.com/googlesamples/android-architecture-components) 946 | 947 | [XiaoxiaZhihu_AAC](https://github.com/LiushuiXiaoxia/XiaoxiaZhihu_AAC) 948 | 949 | [浅谈Android Architecture Components](https://github.com/LiushuiXiaoxia/AndroidArchitectureComponents) 950 | 951 | [Room ORM 数据库框架](https://juejin.im/entry/591d41c70ce463006923f937) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.3" 6 | defaultConfig { 7 | applicationId "cn.mycommons.androidarchitecturecomponents" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.3.1' 28 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 29 | testCompile 'junit:junit:4.12' 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/xiaqiulei/Documents/dev/android_sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/mycommons/androidarchitecturecomponents/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package cn.mycommons.androidarchitecturecomponents; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("cn.mycommons.androidarchitecturecomponents", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/cn/mycommons/androidarchitecturecomponents/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.mycommons.androidarchitecturecomponents; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | 6 | public class MainActivity extends AppCompatActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidArchitectureComponents 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/cn/mycommons/androidarchitecturecomponents/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package cn.mycommons.androidarchitecturecomponents; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /doc/final-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/doc/final-architecture.png -------------------------------------------------------------------------------- /doc/lifecycle-states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/doc/lifecycle-states.png -------------------------------------------------------------------------------- /doc/room_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/doc/room_architecture.png -------------------------------------------------------------------------------- /doc/viewmodel-lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/doc/viewmodel-lifecycle.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiushuiXiaoxia/AndroidArchitectureComponents/6bb8d22fafeb843b0de3cffcb076179e8912c8ab/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 27 13:41:31 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------