1. 程式人生 > >谷歌官方Android應用架構庫——Room 持久化庫

谷歌官方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:

room_architecture

下面是一個只有一個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