谷歌官方Android應用架構庫——Room 持久化庫
架構庫版本:1.0.0 Alpha 2 - June 2, 2017
Room提供了一個SQLite之上的抽象層,使得在充分利用SQLite功能的前提下順暢的訪問資料庫。
對於需要處理大量結構化資料的App來說,把這些資料做本地持久化會帶來很大的好處。常見的用例是快取重要資料塊。這樣當裝置無法連網的時候,使用者仍然可以瀏覽內容。而使用者對內容做出的任何改動都在網路恢復的時候同步到服務端。
核心framework內建了對SQL的支援。雖然這些API很強大,但是都很低階,使用起來很花時間和精力:
沒有編譯時的SQL查詢檢查機制。當資料表發生改變的時候,需要手動更新受影響的SQL查詢。這個過程既耗時又容易出錯。
需要寫很多公式化的程式碼在SQL查詢與Java物件之間轉換。
Room處理了這些相關的事情,同時提供了SQLite之上的抽象層。
Room中有三個主要的元件:
- Database:你可以用這個元件來建立一個database holder。註解定義實體的列表,類的內容定義從資料庫中獲取資料的物件(DAO)。它也是底層連線的主要入口。
這個被註解的類是一個繼承RoomDatabase的抽象類。在執行時,可以通過呼叫Room.databaseBuilder()
或者 Room.inMemoryDatabaseBuilder()
來得到它的例項。
- Entity:這個元件代表一個持有資料庫的一個表的類。對每一個entity,都會建立一個表來持有這些item。你必須在Database類中的entities陣列中引用這些entity類。entity中的每一個field都將被持久化到資料庫,除非使用了
@Ignore
注:實體可以有一個空建構函式(如果DAO類可以訪問每個持久化欄位),或者一個建構函式的引數包含與實體中的欄位匹配的型別和名稱。Romm還可以使用全部或部分建構函式,例如只接收一些欄位的建構函式。
- DAO:這個元件代表一個作為Data Access Objec的類或者介面。DAO是Room的主要元件,負責定義查詢(新增或者刪除等)資料庫的方法。使用
@Database
註解的類必須包含一個0引數的,返回型別為@Dao
註解過的類的抽象方法。Room會在編譯時生成這個類的實現。
注:通過DAO而不是query builders或者直接的query語句來處理資料庫,可以把資料庫的各個部分分離開來。而且DAO還可以讓你輕鬆的使用假的database來測試app。
這些元件以及它們與app其餘部分之間的關係如圖1:
下面是一個只有一個entity和一個DAO的資料庫配置的簡單例子:
User.java
@Entity
public class User {
@PrimaryKey
private int uid;
@ColumnInfo(name = "first_name")
private String firstName;
@ColumnInfo(name = "last_name")
private String lastName;
// Getters and setters are ignored for brevity,
// but they're required for Room to work.
}
UserDao.java
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List<User> getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND "
+ "last_name LIKE :last LIMIT 1")
User findByName(String first, String last);
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
}
AppDatabase.java
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
建立了上面的檔案之後,可以使用下面的程式碼來得到database的例項了:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
在例項化AppDatabase物件的時候應該遵循單例模式,因為每個Database例項都是相當耗費的,而且也很少需要多個例項。
Entities
當一個類用@Entity註解並且被@Database註解中的entities屬性所引用,Room就會在資料庫中為那個entity建立一張表。
預設Room會為entity中定義的每一個field都建立一個column。如果一個entity中有你不想持久化的field,那麼你可以使用@Ignore來註釋它們,如下面的程式碼所示:
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
要持久化一個field,Room必須有獲取它的渠道。你可以把field寫成public,也可以為它提供一個setter和getter。如果你使用setter和getter的方式,記住它們要基於Room的Java Bean規範。
Primary key
每個entity必須至少定義一個field作為主鍵(primary key)。即使只有一個field,你也必須用@PrimaryKey註釋這個field。如果你想讓Room為entity設定自增ID,你可以設定@PrimaryKey的autoGenerate屬性。如果你的entity有一個組合主鍵,你可以使用@Entity註解的primaryKeys屬性,具體用法如下:
@Entity(primaryKeys = {"firstName", "lastName"})
class User {
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
Room預設把類名作為資料庫的表名。如果你想用其它的名稱,使用@Entity註解的tableName屬性,如下:
@Entity(tableName = "users")
class User {
...
}
注:SQLite中的表名是大小寫敏感的。
和tableName屬性類似,Room預設把field名稱作為資料庫表的column名。如果你想讓column有不一樣的名稱,為field新增@ColumnInfo屬性,如下:
@Entity(tableName = "users")
class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
Indices 和 uniqueness
為了提高查詢的效率,你可能想為特定的欄位建立索引。要為一個entity新增索引,在@Entity註解中新增indices屬性,列出你想放在索引或者組合索引中的欄位。下面的程式碼片段演示了這個註解的過程:
@Entity(indices = {@Index("name"), @Index("last_name", "address")})
class User {
@PrimaryKey
public int id;
public String firstName;
public String address;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
有時候,某個欄位或者幾個欄位必須是唯一的。你可以通過把@Index註解的unique屬性設定為true來實現唯一性。下面的程式碼防止了一個表中的兩行資料出現firstName和lastName欄位的值相同的情況:
@Entity(indices = {@Index(value = {"first_name", "last_name"},
unique = true)})
class User {
@PrimaryKey
public int id;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
@Ignore
Bitmap picture;
}
關係
因為SQLite是關係資料庫,你可以指定物件之間的關聯。雖然大多數ORM庫允許entity物件相互引用,但是Room明確禁止了這種行為。詳細情況見:Addendum: No object references between entities。
雖然不可以使用直接的關聯,Room仍然允許你定義entity之間的外來鍵(Foreign Key)約束。
比如,假設有另外一個entity叫做calledBook,你可以使用@ForeignKey註解定義它和User entity之間的關聯,如下:
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
class Book {
@PrimaryKey
public int bookId;
public String title;
@ColumnInfo(name = "user_id")
public int userId;
}
外來鍵非常強大,因為它允許你指定當被關聯的entity更新時做什麼操作。例如,通過在@ForeignKey註解中包含Delete = CASCADE, 你可以告訴SQLite,如果相應的User例項被刪除,那麼刪除這個User下的所有book。
巢狀物件
有時你可能想把一個entity或者一個POJOs作為一個整體看待,即使這個物件包含幾個field。這種情況下,你可以使用@Embedded註解,表示你想把一個物件分解為表的子欄位。然後你就可以像其它獨立欄位那樣查詢這些嵌入的欄位。
比如,我們的User類可以包含一個型別為Address的field,Address代表street,city,state, 和postCode欄位的組合。為了讓這些組合的欄位單獨存放在這個表中,對User類中的Address欄位使用@Embedded註解,如下面的程式碼所示:
class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code")
public int postCode;
}
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}
那麼現在代表一個User物件的表就有了如下的欄位:id,firstName,street,state,city,以及post_code。
注:巢狀欄位也可以包含其它的巢狀欄位。
如果一個entity有多個巢狀欄位是相同型別,你可以設定prefix屬性保持每個欄位的唯一性。Room就會在巢狀物件中的每個欄位名的前面新增上這個值。
Data Access Objects (DAOs)
Room中的主要元件是Dao類。DAO抽象出了一種操作資料庫的簡便方法。
注:Room不允許通過主執行緒上訪問資料庫,除非您在構建器上呼叫allowMainThreadQueries(),因為它可能會長時間地鎖定使用者介面。非同步查詢(返回LiveData或RxJava Flowable的查詢)將免除此規則,因為它們在需要時非同步地在後臺執行緒上執行查詢。
便利的方法
DAO提供了多種簡便的查詢方式,本文件列出幾種常見的例子。
insert
當你建立了一個DAO方法並且添加了@Insert註解,Room生成一個實現,將所有的引數在一次事務中插入資料庫。
下面的程式碼片段演示了幾種查詢的例子:
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);
@Insert
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
如果@Insert方法只接收一個引數,它可以返回一個long,代表新插入元素的rowId,如果引數是一個數組或者集合,那麼應該返回long[]或者List。
update
Update是一個更新一系列entity的簡便方法。它根據每個entity的主鍵作為更新的依據。下面的程式碼演示瞭如何定義這個方法:
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}
你可以讓這個方法返回一個int型別的值,表示更新影響的行數,雖然通常並沒有這個必要。
delete
這個API用於刪除一系列entity。它使用主鍵找到要刪除的entity。下面的程式碼演示瞭如何定義這個方法:
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}
你可以讓這個方法返回一個int型別的值,表示從資料庫中被刪除的行數,雖然通常並沒有這個必要。
使用@Query的方法
@Query是DAO類中主要被使用的註解。它允許你在資料庫中執行讀寫操作。每個@Query方法都是在編譯時檢查,因此如果查詢存在問題,將出現編譯錯誤,而不是在執行時引起崩潰。
Room還會檢查查詢的返回值,如果返回的物件的欄位名和查詢結果的相應欄位名不匹配,Room將以下面兩種方式提醒你:
- 如果某些欄位名不匹配給出警告。
- 如果沒有匹配的欄位名給出錯誤提示。
簡單的查詢
@Dao
public interface MyDao {
@Query(“SELECT * FROM user”)
public User[] loadAllUsers();
}
這是一個非常簡單的查詢,載入所有的user。在編譯時,Room知道它是查詢user表中的所有欄位。如果query有語法錯誤,或者user表不存在,Room將在app編譯時顯示恰當的錯誤資訊。
向query傳遞引數
大多數時候,你需要向查詢傳遞引數來執行過濾操作,比如只顯示大於某個年齡的user。為此,在Room註解中使用方法引數,如下:
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
當編譯時處理到這個查詢的時候,Room把:minAge用方法中的minAge匹配。Room使用引數的名稱來匹配。如果有不匹配的情況,app編譯的時候就會出現錯誤。
你也可以傳遞多個引數或者在查詢中多次引用它們,如下面的程式碼所示:
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);
@Query("SELECT * FROM user WHERE first_name LIKE :search "
+ "OR last_name LIKE :search")
public List<User> findUserWithName(String search);
}
返回欄位的子集
大多數時候,我們只需要一個entity的部分欄位。比如,你的介面也許只需顯示user的first name 和 last name,而不是使用者的每個詳細資訊。只獲取UI需要的欄位可以節省可觀的資源,查詢也更快。
只要結果的欄位可以和返回的物件匹配,Room允許返回任何的Java物件。比如,你可以建立如下的POJO獲取user的first name 和 last name:
public class NameTuple {
@ColumnInfo(name="first_name")
public String firstName;
@ColumnInfo(name="last_name")
public String lastName;
}
現在你可以在query方法中使用這個POJO:
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}
Room知道這個查詢返回first_name和last_name欄位的值,並且這些值可以被對映到NameTuple類的field中。因此Room可以生成正確的程式碼。如果查詢返回了太多的欄位,或者某個欄位不在NameTuple類中,Room將顯示一個警告。
注:這些POJO也可以使用@Embedded註解。
傳入引數集合
一些查詢可能需要你傳入個數是一個變數的引數,只有在執行時才知道具體的引數個數。比如,你可能想獲取一個區間的使用者資訊。當一個引數代表一個集合的時候Room是知道的,它在執行時自動根據提供的引數個數擴充套件。
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
可觀察的查詢
當執行查詢的時候,你通常希望app的UI能自動在資料更新的時候更新。為此,在query方法中使用LiveData型別的返回值。當資料庫變化的時候,Room會生成所有的必要程式碼來更新LiveData。
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
注:對於version 1.0,Room使用query中的table列表來決定是否更新LiveData物件。
RxJava
Room還可以讓你定義的查詢返回RxJava2的Publisher和Flowable物件。要使用這個功能,在Gradle dependencies中新增android.arch.persistence.room:rxjava2
。然後你就可以返回RxJava2中定義的物件型別了,如下面的程式碼所示:
@Dao
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
}
Direct cursor access
如果你的app需要獲得直接返回的行,你可以在查詢中返回Cursor物件,如下面的程式碼所示:
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
注:不推薦使用Cursor API,因為它無法保證行是否存在或者行中有哪些值。只有在當前的程式碼需要一個cursor,而且你又不好重構的時候才使用這個功能。
多表查詢
某些查詢可能需要根據多個表查詢出結果。Room允許你書寫任何查詢,因此表連線(join)也是可以的。而且如果響應是一個可觀察的資料型別,比如Flowable或者LiveData,Room將觀察查詢中涉及到的所有表。
下面的程式碼演示瞭如何執行一個表連線查詢來查出借閱圖書的user與被借出圖書之間的資訊。
@Dao
public interface MyDao {
@Query("SELECT * FROM book "
+ "INNER JOIN loan ON loan.book_id = book.id "
+ "INNER JOIN user ON user.id = loan.user_id "
+ "WHERE user.name LIKE :userName")
public List<Book> findBooksBorrowedByNameSync(String userName);
}
你也可以返回POJO物件。比如你可以寫一個如下的查詢載入user與它們的寵物名字:
@Dao
public interface MyDao {
@Query("SELECT user.name AS userName, pet.name AS petName "
+ "FROM user, pet "
+ "WHERE user.id = pet.user_id")
public LiveData<List<UserPet>> loadUserAndPetNames();
// You can also define this class in a separate file, as long as you add the
// "public" access modifier.
static class UserPet {
public String userName;
public String petName;
}
}
使用型別轉換器
Room內建了原始型別。但是,有時你會希望使用自定義資料型別。 要為自定義型別新增這種支援,可以提供一個TypeConverter,它將一個自定義類轉換為Room保留的已知型別。
比如,如果我們要保留Date的例項,我們可以編寫以下TypeConverter來儲存資料庫中的等效的Unix時間戳記:
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
上述示例定義了兩個函式,一個將Date物件轉換為Long物件,另一個將從Long到Date轉換為執行逆轉換。 由於Room已經知道如何持久化Long物件,因此可以使用此轉換器來持久儲存Date型別的值。
接下來,將@TypeConverters註釋新增到AppDatabase類,以便Room可以使用你為該AppDatabase中的每個實體和DAO定義的轉換器:
AppDatabase.java
@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
使用這些轉換器,可以在其他查詢中使用自定義型別,就像使用原始型別一樣,如以下程式碼片段所示:
User.java
@Entity
public class User {
...
private Date birthday;
}
UserDao.java
@Dao
public interface UserDao {
...
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List<User> findUsersBornBetweenDates(Date from, Date to);
}
資料庫遷移
隨著app功能的新增和修改,你需要修改entity類來反應這些變化。當一個使用者更新了app的最新版本之後,你並不希望它們丟失所有的現有資料,尤其是當你無法通過遠端伺服器恢復這些資料的時候。
Room讓你可以讓你寫Migration類來儲存使用者資料。每個Migration類指定from和to版本。執行時Room執行每個Migration類的 migrate() 方法,使用正確的順序把資料庫遷移到新版本。
注意:如果你沒有提供必要的migration,Room將重建資料庫,也就是說資料庫中的所有資料都會丟失。
Room.databaseBuilder(getApplicationContext(), MyDb.class, "database-name")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3).build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book "
+ " ADD COLUMN pub_year INTEGER");
}
};
注意:為了讓遷移的邏輯是可預知的,請使用完整的查詢而不是用引用代表查詢的constant。
當遷移過程完成之後,Room會檢查schema以確保遷移正確進行。如果Room發現了問題,會丟擲異常。
測試遷移
寫遷移不是一件簡單的事情,如果寫法不恰當可能導致app的進入崩潰的惡性迴圈。為了保證app的穩定性,你應該先測試遷移。Room提供了一個testing Maven artifact來幫助你完成這個測試過程。但是要讓這個artifact工作,需要匯出資料庫的schema。
匯出 schemas
在編譯的時候,Room將database的schema資訊匯出到一個JSON檔案中。為此,要在build.gradle 檔案中設定room.schemaLocation註解處理器屬性,如下面的程式碼所示:
build.gradle
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
你應該把匯出的在這個JSON檔案-它代表了你的資料庫的schema歷史-儲存到你的版本管理系統中,這樣就可以讓Room建立舊版本的資料庫來測試。
測試遷移需要把Room的Maven artifact android.arch.persistence.room:testing
新增到你的test dependencies,並且把schema的位置作為 asset folder新增進去,程式碼如下:
build.gradle
android {
...
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}
testing package提供了一個MigrationTestHelper類,它可以讀出這些schema檔案。它同時也是一個 Junit4 TestRule類,因此可以管理建立的資料庫。
下面的程式碼是一個遷移測試的例子:
@RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
@Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getContext(),
MigrationDb.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
@Test
public void migrate1To2() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
// db has schema version 1. insert some data using SQL queries.
// You cannot use DAO classes because they expect the latest schema.
db.execSQL(...);
// Prepare for the next version.
db.close();
// Re-open the database with version 2 and provide
// MIGRATION_1_2 as the migration process.
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
// MigrationTestHelper automatically verifies the schema changes,
// but you need to validate that the data was migrated properly.
}
}
相關推薦
谷歌官方Android應用架構庫——Room 持久化庫
架構庫版本:1.0.0 Alpha 2 - June 2, 2017 Room提供了一個SQLite之上的抽象層,使得在充分利用SQLite功能的前提下順暢的訪問資料庫。 對於需要處理大量結構化資料的App來說,把這些資料做本地持久化會帶來很大的好處。常見的
谷歌官方Android應用架構庫--LiveData
LiveData 是一個數據持有者類,它持有一個值並允許觀察該值。不同於普通的可觀察者,LiveData 遵守應用程式元件的生命週期,以便 Observer 可以指定一個其應該遵守的 Lifecycle。 如果 Observer 的 Lifecycle 處於 STAR
谷歌官方Android應用架構庫(Android Architecture Components)學習完整版
架構庫版本:1.0.0 Alpha 2 - June 2, 2017 1 導語 本次 Google IO 大會不僅確立了 Kotlin 為安卓開發的官方語言,不為人注意是,還發布了谷歌官方 Android 應用架構庫。這個新的架構庫旨在幫助我們設計健壯、
Android——谷歌官方下拉刷新控件SwipeRefreshLayout(轉)
reference top 顯示 是個 not ext html blog right 轉自:http://blog.csdn.net/zouzhigang96/article/details/50476402 版權聲明:本文為博主原創文章,未經博主允許不得轉載。
Android MVP模式 谷歌官方程式碼解讀
Google官方MVP Sample程式碼解讀 關於Android程式的構架, 當前(2016.10)最流行的模式即為MVP模式, Google官方提供了Sample程式碼來展示這種模式的用法. Repo地址: android-architecture. 本文為閱讀官方sample程式碼的閱讀筆記和分析. 官
App開發架構指南(谷歌官方文件譯文)
這篇文章面向的是已經掌握app開發基本知識,想知道如何開發健壯app的讀者。 注:本指南假設讀者對 Android Framework 已經很熟悉。如果你還是app開發的新手,請檢視 Getting Started 系列教程,該教程涵蓋了本指南的預備知識。 ap
Android谷歌官方語言Kotlin用法入門教程
導語 Kotlin語法與Java的區別挺大的,一開始很想放棄,如果不是谷歌讓其成為Android的官方開發語言,想必很少人會嘗試這樣一門小眾語言,但是換了Kotlin後會發現這些年究竟浪費多少時間在寫無用的Java程式碼了,Kotlin在相容Java的基礎上還大大提升效
Android——谷歌官方下拉重新整理控制元件SwipeRefreshLayout
前言: 如今谷歌推出了更官方的下拉重新整理控制元件, 這無疑是對安卓開發人員來說是個好訊息,很方便的使用這個SwipeRefreshLayout控制元件實現下拉重新整理功能。Android4.0以下的版本需要用到 android-support-v4.jar
Android系統使用谷歌官方GCM推送指南
本文旨在為所有Android系統手機普通使用者提供使用谷歌GCM推送的教程,注意這不是用於開發者的。雖名為指南,但多少是探索性的,因為一些細節筆者尚未搞清楚,希望各位指正。由於大部分國內應用沒有使用GCM推送服務,那麼如果你手機裡的應用幾乎沒有使用GCM服務的應用,那麼即使打通GCM也是沒有意義的。本文的適用
谷歌playstore中應用下載量已超150億次
.cn odi dex odm www wot mdi mdm odex peerjs%E5%AE%9E%E7%8E%B0%E6%B5%8F%E8%A7%88%E5%99%A8%E5%AF%B9%E7%AD%89%E8%BF%9E%E6%8E%A5%E7%9A%84js%E
谷歌敦促android開發者停止使用菜單按鈕
and ova pap 卻又 abp 這樣的 dbo 開發 lac 鮮血濺落在那泛光的綠珠之上,頓時驚變忽生,那珠子猛然綻放出耀眼光芒,一股勁風在周圍肆虐起來,吹動著那些碎石飛舞起來,但是淩雪和珠子卻被籠罩在一個帷幕之中,絲毫不受到影響。 飛火眼裏立刻透出向往的神色,笑道“
Android應用架構分析
描述 nal 目的 manifest 分析 colors 內部類 roi ldp 一、res目錄: 1、屬性:Android必需; 2、作用:存放Android項目的各種資源文件。這些資源會自動生成R.java。 2.1、layout:存放界面布局文件。
Android應用架構之Retrofit使用
網路訪問框架經過了從使用最原始的AsyncTask構建簡單的網路訪問框架(甚至不能稱為框架),後來使用開源的android-async-http庫,再到使用google釋出的volley庫,一直不懈的尋找更好的解決方案(銀彈),到現在雖然銀彈沒找到,也算找 到了一些更好的
Android應用架構之Retrofit、RxAndroid使用
上篇部落格客http://blog.csdn.net/liuhongwei123888/article/details/50375283 已經介紹了Retrofit的簡單使用方法,接下來介紹的是在Retrofit中怎麼使用RxAndroid,如果還不瞭解RxAn
ADB和Fastboot最新版的谷歌官方下載連結
sunrain_hjb的BLOG ARM.WinCE.Android.Robot.Linux.IoT.VR... Develop Helpful and Effective apps to make Jobs easier and lives Better!
谷歌官方兩種下拉重新整理樣式(橫線樣式、圓圈樣式)
下拉重新整理經常會用到,谷歌官方也推出了自己的下拉重新整理控制元件SwipeRefreshLayout,使用也非常簡單。直接在Listview,Gridview等外層巢狀android.support.v4.widget.SwipeRefreshLayout即可。需要注意的
谷歌Chrome瀏覽器應用程式無法啟動,因為應用程式的並行配置不正確問題
總結網上的幾種解決情況: 方法一: 開始 - 執行(輸入services.msc)- 確定或回車,開啟:服務(本地); 我們在服務(本地)視窗找到:Windows Modules Installer服務,檢視是否被禁用; 如果Windows Module
谷歌Object Detection API(實物檢測模型庫)安裝過程踩過的坑
在一個狀態極佳的夜晚,終於將惦記了好幾個月的object detection API裝好了,主要安裝步驟參考了這個部落格 ,手動比心❤ 但是在安裝過程中還是遇到了不少該部落格中沒有提到的問題,希望記錄下來,給自己日後參考,也希望能為有需要的人提供綿薄之力。1. 開發環
Google 官方Android MVP架構實踐
一、Google 官方MVP介紹 近期,關於Android開發架構的討論沸沸揚揚,各大技術平臺隨處可見關於Android架構的技術文章。MVC、MVP、MVVM等等,就目前的形式來看,MVP模式在Android開發領域界逐漸流行了起來。前段時間,Google也忍耐不住And