分類
Uncategorized

[Tool] 跨裝置共享滑鼠鍵盤軟體 – Synergy

因為工作的關係,我最少也會使用3台實體裝置(Win + Mac + Ubuntu)。

但在不同實體裝置之間共享鍵鼠其實是相當麻煩的事,我也嘗試過KVM,KVM最大的缺點說是你必須空出一隻手點擊切換按鈕,而且切換按鈕點久了壞掉也等同整組KVM掛掉。

所以大概在5~6年前我就找到Synergy作為共享鍵鼠的工具(那時候還有免費版,現在應該已經沒了)

以下為Synergy的推坑文,先說兩個重要的前置條件,

首先沒有免費版,一般個人版大約800多台幣,專業版1000多台幣。(我是用一般個人版)。官網售價

第二,要共享鍵鼠的裝置必須處於同個區網,也就是說遠端就沒辦法了。

接下來就是基本的使用過程。

Step 1.首先到官網下載並安裝Synergy,安裝完就會提示你輸入License,License可在你的官網帳號資訊取得。(當然,前提是你已經在官網註冊帳號並付費完成)

Step 2.在每台你想共享的裝置重複Step 1.步驟

Step 3.決定並在Synergy上設定“主要分享裝置“及”其他共享裝置“。這個”主要分享裝置“就是實體的鍵鼠安裝的裝置。至於”其他共享裝置“則會共享”主要分享裝置“的鍵鼠。

預設只要讓主要分享裝置和其他共享裝置位於同一個區網並勾選哪個是”主要分享裝置”,哪個是”其他共享裝置“即可。不必調整任何IP設定,設定非常間單!(官網設定連結)

Step 4.開始使用!

預設的使用方式只要把滑鼠移到目前裝置的螢幕以外位置,就會自動進入其他裝置。你也可以自行設定熱鍵方便跳轉不同裝置

分類
React Native Uncategorized

[React Native] React Native version mismatch

Situation

終端機輸入 npx react-native run-ios 後,啟動iOS模擬器出現錯誤訊息 “React Native version mismatch”

Action

關閉所有終端機並重新開啟位於專案根目錄的終端機

Result

在新開啟的終端機輸入 npx react-native run-ios,不再出現該訊息

分類
Uncategorized

[Delphi] 使用 OutputDebugString 輸出訊息

Situation :

在某些情況下無法或不想使用 ShowMessage 來輸出訊息,其實還可以選用 OutPutDebugString 來輸出訊息。

Action :

調用 OutputDebugString 方法,如下

OutputDebugString(PChar('call outputdebugstgring to output message'));

記得要呼叫 PChar 處理要輸出的內容!

輸出的視窗在 Delphi -> View -> Debug Windows -> Event log

Result :

在 Event Log 視窗會出現想要輸出的訊息

分類
Android Uncategorized

如何從 adb 啟動 App 並帶參數

概述

如何從adb啟動App並帶參數

做法

以todo-app為例,使用adb啟動App使用的指令為

adb shell am start -n [PACKAGE-NAME]/[ACTIVITY-NAME]

因此需要先找到PACKAGE-NAME 和 ACTIVITY-NAME

1.找PACKAGE-NAME

先安裝 todo App 到裝置上
1.1輸入以下指令便會列出 App 上所有已安裝的 PACKAGE-NAME

adb shell pm list packages -f

若連接多台裝置則使用 -s 指定裝置號碼如下

adb -s DeviceNumber shell pm listpackages -f

如何取得裝置號碼則使用 adb devices
輸入adb shell pm list packages -f 之後回傳的內容可能太長,因此可以在指令的最後加上 > D:\testlog\get_packages.txt 將顯示內容輸出到D:\testlog\get_packages.txt
因此輸入

adb -s DeviceNumber shell pm list packages -f > D:\testlog\get_packages.txt

在d:\testlog\get_packages.txt尋找todo關鍵字,只找到一項如下

package:/data/app/com.example.android.architecture.blueprints.todomvp.mock-1/base.apk=com.example.android.architecture.blueprints.todomvp.mock

 

2.找ACTIVITY-NAME

輸入 adb shell dumpsys activity 便可列出正在 App 上執行的所有 Activity
注意,App必須正在執行中才會顯示 activity name
2.1先在裝置上啟動 todo-app
2.2 輸入

adb -s DeviceNumber shell dumpsys activity -f > D:\testlog\get_activities.txt

2.3在D:\testlog\get_activities.txt 尋找 todo關鍵字,找到其中一個區塊如下

…
TaskRecord{cc64a79 #6942 A=com.example.android.architecture.blueprints.todomvp.mock U=0 StackId=1 sz=1}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity bnds=[18,1035][268,1320] (has extras) }
        Hist #0: ActivityRecord{199dedc u0 com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity t6942}
          Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity bnds=[18,1035][268,1320] (has extras) }
          ProcessRecord{66be5e 5459:com.example.android.architecture.blueprints.todomvp.mock/u0a566}
    Running activities (most recent first):
      TaskRecord{cc64a79 #6942 A=com.example.android.architecture.blueprints.todomvp.mock U=0 StackId=1 sz=1}
        Run #0: ActivityRecord{199dedc u0 com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity t6942}
    mResumedActivity: ActivityRecord{199dedc u0 com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity t6942}
…

在該區塊中尋找關鍵字cmp,可以找到以下內容

cmp=com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity

cmp=後方的內容其實就是PACKAGE-NAME/ACTIVITY-NAME
因此可以輸入下方指令來啟動todo App了

adb -s DeviceNumber shell am start -n com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity

成功啟動便會回應

Starting: Intent { cmp=com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity }

並在裝置上啟動todo app。

3.啟動App附帶參數

啟動App附帶參數的方式為在指令的最後加入 –es key ‘value’ 或 –ez key ‘value’或 –ei key ‘value’ 或 –ef key ‘value’
其中 –es 參數為string, –ez 參數為boolean, –ei參數型態為int, –ef參數為float。
若要取得參數,則在該Activity的onCreate方法中使用 getIntent().getXXXExtra的方式,xxx則視傳入參數為甚麼型態而定,若現在想啟動todo app並帶參數為string型態,參數的key為test, 參數的value為 test parameter from adb
,則輸入指令如下

adb -s DeviceNumber shell am start -n com.example.android.architecture.blueprints.todomvp.mock/com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity --es test 'test parameter from adb'

 
在TasksActivity的onCreate方法中加入

Log.d(TAG, "onCreate: "+getIntent().getStringExtra("test"));

觀察logcat輸出便可看到 onCreate: test parameter from adb,代表使用adb成功啟動App並附帶參數。
 

分類
Android Uncategorized

android.util.Log 列印訊息內容太多無法完全顯示

使用 android.util.Log 列印訊息時,若訊息內容太多就會出現無法完全顯示訊息內容的情況。
解決的方法就是分段列印,如下

    private void logLongMessage(String TAG, String message) {
        int maxMessageSize = 2000;
        for (int i = 0; i <= message.length() / maxMessageSize; i++) {
            int start = i * maxMessageSize;
            int end = (i + 1) * maxMessageSize;
            end = end > message.length() ? message.length() : end;
            Log.d(TAG, message.substring(start, end));
        }
    }

 

分類
Android Uncategorized

使用 Room DAO 存取資料 (Room)

要使用 Room 存取資料,需要使用 DAO。
這組 DAO 物件形成了 Room 的主要組件,因為每個 DAO 都包含提供對資料庫的抽象訪問方法。
通過使用 DAO 類別而不是查詢構建器或直接查詢來訪問資料庫,使用者可以分離出資料庫架構的不同元件。
此外,DAO 可在測試應用程序時輕鬆模擬資料庫訪問。
注意:
在加入 DAO 類別之前,先在 app 的 build.gradle 加入相依性。
DAO 可以是 interface,也可以是抽象類別。如果是抽象類別,它可以選擇有一個構造函數,它將 RoomDatabase 作為唯一的參數。Room 在編譯時期建立每個 DAO 實作。
注意:
Room 並不支援在 Main thread 上存取資料庫,因為可能會長時間鎖定 UI。
如果要讓 Room 支援在其他執行緒存取資料庫則必須在建構時呼叫allowMainThreadQueries() 方法。
異步查詢 – 若是查詢會返回 LiveData 或 Flowable 實例 – 不受此規則的約束,因為它們在需要時在後台線程上異步運行查詢
 

Define methods for convenience

可以使用 DAO 類別表示多個便捷查詢。

Insert

當建立 DAO 方法並使用 @Insert 註釋時,Room 會生成一個實現,該實現在單一事務(single transaction)中將所有參數插入到資料庫中。

@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 方法只接收 1 個參數,則它可以返回 long,這是該插入項目的新 rowId。如果參數是陣列或集合,則應返回 long [] 或 List <Long>。
有關更多詳細信息,請參閱 @Insert 的文件,以及 SQLite documenation for rowid tables

Update

Update 修改資料庫中作為參數給出的一組實體。它使用每個實體的主鍵做查詢。

@Dao
public interface MyDao {
    @Update
    public void updateUsers(User... users);
}

雖然通常沒有必要,但可以讓此方法返回一個 int 值,表示資料庫中更新的行數。
 

Delete

Delete 從資料庫中刪除一組從參數傳入的實體。使用主鍵來查找要刪除的實體。

@Dao
public interface MyDao {
    @Delete
    public void deleteUsers(User... users);
}

雖然通常沒有必要,但可以讓此方法返回一個 int 值,表示從資料庫中刪除的行數。
 

Query for information

@Query 是 DAO 類別中使用的主要註釋。它可以對資料庫執行讀/寫操作。
每個 @Query 方法都在編譯時進行驗證,因此如果查詢出現問題,則會發生編譯錯誤而不是運行時失敗。
Room 也會驗證查詢的返回值,避免返回的物件中的屬性名稱與查詢的相應列名稱不相符,Room 使用以下兩個方法之一

  • 如果只有一些屬性名稱相符,它會發出警告。
  • 如果沒有屬性名稱相符,則會發出錯誤。

Simple queries

@Dao
public interface MyDao {
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();
}

以上是一個非常簡單的查詢,可以取得所有用戶。在編譯時,Room 知道正在查詢用戶表中的所有列。如果查詢包含語法錯誤,或者資料庫中不存在該資料表,則在應用程序編譯時,Room 會顯示包含相應消息的錯誤。

Passing parameters into the query

大多數情況下,需要將參數傳遞給查詢以執行過濾操作,例如僅顯示年齡超過特定年齡的用戶。
要傳入參數,請在 Room 註釋中使用方法參數,如以下代碼段所示

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

在編譯時處理此查詢時,Room 會將:minAge 綁定參數與 minAge 方法的參數。
Room 使用參數名稱檢查是否相符。如果存在不相符,則在應用編譯時會發生錯誤。
還可以在查詢中多次傳遞多個參數或引用它們,如以下代碼段所示:

@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);
}

 

Returning subsets of columns

大多數情況下,只需要獲得實體的幾個屬性。
例如,UI 可能只顯示用戶的名字和姓氏,而不是用戶的每一個訊息。
通過僅提取 UI 顯示的列,可以節省寶貴的資源,並且讓查詢可以更快地完成。
Room 允許從查詢中返回任何基於 Java 的對象,只要結果可以映射到返回的對象即可。
例如,可以建立以下(POJO)來獲取用戶的名字和姓氏

public class NameTuple {
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
}

現在,可以在查詢方法中使用此 POJO:

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user")
    public List<NameTuple> loadFullName();
}

Room 了解查詢返回 first_name 和 last_name 列的值,並且這些值可以映射到 NameTuple 類別的屬性中。因此,Room 可以生成正確的代碼。
如果查詢返回太多列,或者有 NameTuple 類別中不存在的列,則 Room 會顯示警告。
注意:
這些 POJO 也可以使用 @Embedded 註釋。
 

Passing a collection of arguments

某些查詢可能要求傳入可變數量的參數,並且在運行時之前不知道參數的確切數量。
例如,可能希望從區域子集中檢索有關所有用戶的信息。
Room 了解參數何時表示集合,並根據提供的參數數量在運行時自動擴展它。

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

 

Observable queries

執行查詢時,通常希望應用程序的UI在資料更改時自動更新。
要實現此目的,請在查詢方法描述中使用 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);
}

注意:
從版本 1.0 開始,Room 使用查詢中訪問的表列表來決定是否更新 LiveData 的實例。
 

Reactive queries with RxJava

Room 為 RxJava2 類型的返回值提供以下支持:

  • @Query methods: Room supports return values of type Publisher, Flowable, and Observable
  • @Insert, @Update, and @Delete methods: Room 2.1.0 and higher supports return values of type Completable, Single<T>, and Maybe<T>

要使用此功能,請在 app 的 build.gradle 文件中包含最新版本的 rxjava2:

dependencies {
    implementation 'androidx.room:room-rxjava2:2.1.0-alpha02'
}

以下代碼示範如何使用這些返回類型:

@Dao
public interface MyDao {
    @Query("SELECT * from user where id = :id LIMIT 1")
    public Flowable<User> loadUserById(int id);
    // Emits the number of users added to the database.
    @Insert
    public Maybe<Integer> insertLargeNumberOfUsers(List<User> users);
    // Makes sure that the operation finishes successfully.
    @Insert
    public Completable insertLargeNumberOfUsers(User... users);
    /* Emits the number of users removed from the database. Always emits at
       least one user. */
    @Delete
    public Single<Integer> deleteUsers(List<User> users);
}

有關更多詳細信息,請參閱 Google Developers Room and RxJava
 

Direct cursor access

如果需要直接存取返回的行,則可以從查詢中返回 Cursor 物件,如以下代碼段所示:

@Dao
public interface MyDao {
    @Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
    public Cursor loadRawUsersOlderThan(int minAge);
}

警告:
非常不建議使用 Cursor API,因為它不能保證行是否存在或行包含的值。
 

Query multiple tables

某些查詢可能需要訪問多個表來計算結果。Room 允許編寫任何查詢,因此也可以查詢多個資料表。
此外,如果響應是可觀察的數據類型(如 Flowable 或 LiveData),則會監視查詢中引用的所有資料表以檢查是否無效。
以下代碼段顯示如何查詢不同的資料表:

@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。

@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;
   }
}

 

分類
Android Uncategorized

在資料庫中建立 View (Room)

Room 2.1.0 及更高版本提供對 SQLite 資料庫的 View 功能,允許使用者將查詢封裝到類別中。Room 將這些查詢支持的類別稱為 View,作用和使用 DAO 的簡單資料物件相同。
注意:
與實體(Entitiy)一樣,可以針對 View 運行 SELECT 語句。但是無法對 View 進行 INSERT, UPDATE 或 DELETE 語句。
 

Create a view

要建立 View,請將 @DatabaseView 註釋加入類別。將註釋的值設置為該類別應表示的查詢。以下代碼段提供了一個 View 範例:

@DatabaseView("SELECT user.id, user.name, user.departmentId," +
              "department.name AS departmentName FROM user " +
              "INNER JOIN department ON user.departmentId = department.id")
public class UserDetail {
    public long id;
    public String name;
    public long departmentId;
    public String departmentName;
}

 
 

Associate a view with your database

要將該 View 作為資料庫的一部分,請在 @Database 註釋中加入 views 屬性

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

 

分類
Android Uncategorized

使用 Room entities 定義資料 (Room)

使用 Room 時,可以將相關的屬性定義為實體(entities)。
對於每個實體,將會創建一個資料表來保存項目在 Database 物件中,必須透過 Database 類別中的 entities 陣列來引用實體類別。
以下示範如何定義一個實體(entity)

@Entity
public class User {
    @PrimaryKey
    public int id;
    public String firstName;
    public String lastName;
}

為了保存屬性,Room 必須去存取它。可以透過將該屬性設定為 public 或提供 getter 和 setter 存取器。若是使用存取器必須注意該存取器必須符合 JavaBeans 的命名規則。
注意:
實體(Entity)可以有一個空構造函數(如果相應的 DAO 類別可以訪問每個持久化字段),或者一個構造函數,其參數包含與實體中的屬性匹配的類型和名稱。Room 也可以使用完整或部分構造函數,例如只接收某些屬性的構造函數。

Use a primary key

每個實體必須至少定義一個屬性作為主鍵。即使只有一個屬性,仍需要使用 @PrimaryKey 註釋來註釋該屬性。
此外,如果希望 Room 為實體分配自動 ID,可以設定 @PrimaryKey 的 autoGenerate 屬性。
如果實體具有複合主鍵,則可以使用 @Entity 註釋的 primaryKeys 屬性,如以下代碼段所示:

@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
    public String firstName;
    public String lastName;
}

 
Room 預設使用類別名稱作為資料表名稱。如果希望資料表使用不同的名稱,可以設定  @Entity 註釋的 tableName 屬性,如以下代碼段所示:

@Entity(tableName = "users")
public class User {
    // ...
}

注意:
資料表名稱是區分大小寫的
 
類似於 tableName,Room 使用屬性名稱作為資料庫中的列名稱。如果希望列具有不同的名稱,請將 @ColumnInfo 註釋加到屬性中,如以下代碼段所示:

@Entity(tableName = "users")
public class User {
    @PrimaryKey
    public int id;
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
}

 

Ignore fileds

預設情況下,Room 會為實體中定義的每個屬性都創建一列。如果實體具有不想保存的屬性,則可以使用 @Ignore 對其進行註釋,如以下代碼段所示:

@Entity
public class User {
    @PrimaryKey
    public int id;
    public String firstName;
    public String lastName;
    @Ignore
    Bitmap picture;
}

 
如果實體從另一個實體(父實體)繼承屬性,則通常使用 @Entity 屬性的 ignoredColumns:

@Entity(ignoredColumns = "picture")
public class RemoteUser extends User {
    @PrimaryKey
    public int id;
    public boolean hasVpn;
}

 

Provide table search support

Room 支持多種類型的註釋,可以更輕鬆地搜索資料表中的詳細信息。除非應用程序的 minSdkVersion 小於 16,否則請使用全文搜索(full-text search)。

Support full-text search

如果應用程序需要通過全文搜索(FTS)快速訪問數據庫信息,就讓實體由使用 FTS3 或 FTS4 SQLite 擴展模塊的虛擬表支持。
要使用全文搜索,必須在 Room 2.1.0 及更高版本,請將 @ Fts3 或 @ Fts4 註釋添加到給定實體,如以下代碼段所示:

// Use `@Fts3` only if your app has strict disk space requirements or if you
// require compatibility with an older SQLite version.
@Fts4
@Entity(tableName = "users")
public class User {
    // Specifying a primary key for an FTS-table-backed entity is optional, but
    // if you include one, it must use this type and column name.
    @PrimaryKey
    @ColumnInfo(name = "rowid")
    public int id;
    @ColumnInfo(name = "first_name")
    public String firstName;
}

注意:啟用 FTS 的資料表必須使用 INTEGER 類型的主鍵和列名”rowid”。如果 FTS 資料表支持的實體定義主鍵,則它必須使用該類型和列名稱。
 
如果資料表支持多種語言的內容,請使用 languageId 選項指定存儲每行語言信息的列:

@Fts4(languageId = "lid")
@Entity(tableName = "users")
public class User {
    // ...
    @ColumnInfo(name = "lid")
    int languageId;
}

 
Room 提供了其他幾個用於定義FTS支持的實體選項,包括結果排序(result ordering),tokenizer 類型(tokenizer types)和作為外部內容管理的資料表。有關這些選項的更多詳細信息,請參閱 FtsOptions 參考。
 

Index specific columns

如果應用程序必須支持不允許使用 FTS3 或 FTS4 資料表支持實體的 SDK 版本,仍然可以索引數據庫中的某些列以加快查詢速度。
要向實體添加索引,請在 @Entity 註釋中包含 indices 屬性,列出要包含在索引或複合索引中的列的名稱。如下所示

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})
public 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)})
public class User {
    @PrimaryKey
    public int id;
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
    @Ignore
    Bitmap picture;
}

 

Include AutoValue-based objects

注意:
這個特性只用於 java-based entities,不支援 Kotlin。
在 Room 2.1.0 及更高版本中,可以使用基於 Java 的不可變值類(使用 @AutoValue 進行註釋)作為數據庫中的實體。
如果實體的兩個實例的列包含相同的值,則此支持特別有用。
使用帶 @AutoValue 註釋的類別作為實體時,可以使用 @PrimaryKey,@ColumnInfo,@ Embedded 和 @Relation 註釋類別的抽象方法。但是,在使用這些註釋時,每次都必須包含 @CopyAnnotations 註釋,以便 Room 可以正確解釋方法的自動生成實現。
以下代碼段顯示了一個使用 @AutoValue 註釋類別的示例,Room 將其識別為實體:

@AutoValue
@Entity
public abstract class User {
    // Supported annotations must include `@CopyAnnotations`.
    @CopyAnnotations
    @PrimaryKey
    public abstract long getId();
    public abstract String getFirstName();
    public abstract String getLastName();
    // Room uses this factory method to create User objects.
    public static User create(long id, String firstName, String lastName) {
        return new AutoValue_User(id, firstName, lastName);
    }
}

 

Define relationships between objects

由於 SQLite 是關係數據庫,因此可以指定物件之間的關係。儘管大多數對象關係映射庫允許實體對象相互引用,但 Room 明確禁止這樣做。即使不能使用直接關係,Room 仍允許在實體之間定義外鍵約束。
例如,如果有另一個名為 Book 的實體,可以使用 @ForeignKey 註釋定義其與 User 實體的關係,如以下代碼段所示:

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))
public class Book {
    @PrimaryKey
    public int bookId;
    public String title;
    @ColumnInfo(name = "user_id")
    public int userId;
}

外鍵非常強大,因為它們允許指定更新引用實體時發生的情況。
例如,如果通過在 @ForeignKey 註釋中包含 onDelete = CASCADE 來刪除相應的 User 實例,則可以告訴 SQLite 刪除用戶的所有書籍。
注意:
SQLite 將 @Insert(onConflict = REPLACE)作為一組 REMOVE 和 REPLACE 操作處理,而不是單個 UPDATE 操作。這種替換衝突值的方法可能會影響外鍵約束。
 

Create nested objects

有時候希望將實體或 POJO 表達為資料庫邏輯中的一個整體,即使該物件包含多個屬性。
在這些情況下,可以使用 @Embedded 註釋來表示要分解到資料表內子字屬性的物件。然後,可以像查找其他單個列一樣查詢嵌入屬性(Embedded filed)。
例如,User 類別可以包含 Address 類別的屬性,而 Address 也有其屬性為 street,city,state 和 postCode 屬性。
User.java

@Entity
public class User {
    @PrimaryKey
    public int id;
    public String firstName;
    @Embedded
    public Address address;
}

Address.java

public class Address {
    public String street;
    public String state;
    public String city;
    @ColumnInfo(name = "post_code")
    public int postCode;
}

然後,表示 User 物件的資料表包含具有以下名稱的列:id,firstName,street,state,city 和 post_code。
注意:
嵌入屬性(Embedded filed)可以再包含其它嵌入屬性(Embedded filed)
如果實體具有多個相同類型的嵌入屬性,則可以通過設置 prefix 屬性使每個列保持唯一。然後,Room 將提供的值添加到嵌入屬性中每個列名稱的開頭。

分類
Android Uncategorized

在本地端資料庫儲存資料 (Room)

在 Room 中有 3 個主要的元件

1.Database:

包含資料庫持有者(database holder),並作為應用程序的持久化數據基礎的主要訪問點。
使用 @Database 註釋的類別需要滿足以下條件:

  • 是一個抽象類別並繼承 RoomDatabase 類別
  • 在註釋加入與資料庫關聯的實體列表
  • 包含一個無參數的抽象方法,並返回使用 @Dao 註釋的類別

在程式運行時,可以通過呼叫 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder()來獲取 Database 實體。

2.Entity:

表示為資料庫中的資料表。

3.DAO:

包含用於訪問資料庫的方法。
應用程序使用 Room 資料庫來獲取與該資料庫關聯的數據訪問對像(DAO)。接著,應用程序使用每個 DAO 從資料庫中獲取 Entity,並將對這些 Entity 的任何更改保存回資料庫。最後,應用程序使用 Entity 來獲取和設置與資料庫的表列互相對應的值。
如下圖所示

以下的範例為包含一個 entity 和一個 dao 的資料庫
User.java

@Entity
public class User {
    @PrimaryKey
    public int uid;
    @ColumnInfo(name = "first_name")
    public String firstName;
    @ColumnInfo(name = "last_name")
    public String lastName;
}

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();
}

注意:
AppDatabase 即為上方第 1 點提到的 Database 並符合其 3 個條件,它是一個抽象類別並繼承 RoomDatabase,在註釋中加入使用的 Entity,提供無參數並返回 Dao 的抽象方法。
 
下面是建立並取得 database 的實體

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

 
注意:
如果應用程序在單進程(single process)中執行,則在實例化 AppDatabase 物件時應該套用 Singleton Pattern。因為建立 RoomDatabase 實例相當昂貴,很少需要在單進程中訪問多個實例。
如果應用程序在多進程中運行,請在數據庫構建器調用中包含 enableMultiInstanceInvalidation(),當在每個進程中都有一個 AppDatabase 實例時,可在一個進程中使共享數據庫文件無效,並且讓此無效自動傳播到其他進程中的 AppDatabase 實例。
 

分類
Android Uncategorized

Room overview

什麼是 Room?

Room 是 Android 官方所提供的 ORM,可快速方便的存取 SQLite。
基本上 Room 為 SQLite 提供了一個抽象層,該層透過使用 SQLite 實現更強大的資料庫存取功能。
處理大量結構化數據的應用程序可以從本地持久化保存數據中獲得好處。最常見的範例是當設備無法使用網絡時緩存相關的數據,讓用戶仍然可以在離線時瀏覽該內容。之後在設備重新連上網路時,任何更改的內容都會同步到服務器。

強烈建議使用 Room 而不直接使用 SQLite。

使用 Room 在本地端資料庫儲存資料
使用 Room entities 定義資料
在資料庫中建立 View
使用 Room DAO 存取資料