12/08/2018, 16:47

Một vài kinh nghiệm khi dùng Realm trong android - Part 2

Các bạn có thể đọc phần 1 tại đây Hạn chế lỗi “Application Not Responding” (ANR). Mặc dù Realm đủ nhanh để đọc và ghi dữ liệu ngay trên Android main thread. Tuy nhiên, write transactions block accross threads, nghĩa là giả sử bạn đang ghi dữ liệu ở background thread , lời gọi ghi ...

Các bạn có thể đọc phần 1 tại đây

Hạn chế lỗi “Application Not Responding” (ANR).

Mặc dù Realm đủ nhanh để đọc và ghi dữ liệu ngay trên Android main thread. Tuy nhiên, write transactions block accross threads, nghĩa là giả sử bạn đang ghi dữ liệu ở background thread, lời gọi ghi dữ liệu trên main thread sẽ bị block và lỗi ANR sẽ xảy ra. Vì thế, lời khuyên ở đây là các thao tác ghi nên ở background thread thông qua Realms Asynchronous Transactions

Quản lý vòng đời của một Realm instances

Lựa chọn vòng đời hợp lý cho Realm instance là cần thiết. Bởi vì các RealmObjects và RealmResults được truy cập thông qua một bộ lazy cache, nên giữ một Realm instance mở càng lâu càng tốt. Không chỉ tránh được phí tổn phát sinh trong việc mở và đóng nó, mà còn cho phép truy vấn nhanh hơn. Mặc khác, một Realm instance đang mở sẽ giữ các các tài nguyên quan trọng, một vài trong chúng lại không quản lý bở trình quản lý bộ nhớ của Java (Java Memory Manager). Java không thể tự động quản lý những tài nguyên này. Do đó, điều cần thiết là nên đóng realm instance khi nó không cần thiết nữa. Realm sử dụng một internal reference counted cache, nên sau khi get Realm instance đầu tiên, các instance tiếp theo trên cùng một thread sẽ là miễn phí. Các tài nguyên cơ bản được giải phóng chỉ khi tất cả các instance trên thread đó được đóng lại.

Một sự lựa chọn hợp lý là làm sao cho vòng đời của Realm instance đồng nhất với các vòng đời của các ViewObserve nó.

// Setup Realm in your Application
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build();
        Realm.setDefaultConfiguration(realmConfiguration);
    }
}

// onCreate()/onDestroy() overlap when switching between activities.
// Activity2.onCreate() will be called before Activity1.onDestroy()
// so the call to getDefaultInstance in Activity2 will be fast.
public class MyActivity extends Activity {
    private Realm realm;
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        realm = Realm.getDefaultInstance();
        setContentView(R.layout.activity_main);
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        recyclerView.setAdapter(
            new MyRecyclerViewAdapter(this, realm.where(MyModel.class).findAllAsync()));
        // ...
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        realm.close();
    }
}

// Use onCreateView()/onDestroyView() for Fragments.
public class MyFragment extends Fragment {
    private Realm realm;
    private RecyclerView recyclerView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        realm = Realm.getDefaultInstance();
        View root = inflater.inflate(R.layout.fragment_view, container, false);
        recyclerView = (RecyclerView) root.findViewById(R.id.recycler_view);
        recyclerView.setAdapter(
            new MyRecyclerViewAdapter(getActivity(), realm.where(MyModel.class).findAllAsync()));
        // ...
        return root;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        realm.close();
    }
}

Ở ví dụ trên ta sử dụng một FragmentActivity, với một RecyclerView hiển thị dữ liệu được lấy ra từ một Realm instance. Trong cả 2 ví dụ, Realm instanceRecyclerViewAdapter được khởi tạo trong callback tạo và đóng trong callback hủy tương ứng. Điều này là an toàn, thậm chí cho các Activity: cơ sở dữ liệu sẽ được chuyển sang trạng thái nhất quán (consistent) ngay cả khi onDestroy và phương thức close() không bao giờ được gọi.

Rõ ràng, nếu hầu hết các Fragment liên quan đến một Activity yêu cầu truy cập vào cùng một tập dữ liệu, điều này có ý nghĩa đối với Activity, không phải là 1 Fragment riêng lẻ, để kiểm soát vòng đời của instance.

Một lưu ý là đối với Fragment, nếu database lớn, get Realm instance có thể trong thời gian ngắn làm block rendering. Trong trường hợp này, nên quản lý Realm instance trong onStart/onStop. Return View ngay tức thì trong onCreateView cho phép UI của Fragment được render trong khi Realm instance được khởi tạo và View đã loaded.

Tái sử dụng RealmResults and RealmObjects

Trên UI thread và tất cả các Looper thread khác, tất cả RealmObjectsRealmResults được tự động làm mới khi có thay đổi đối với Realm. Điều này có nghĩa là không cần phải lấy các đối tượng này một lần nữa khi phản ứng với một RealmChangedListener. Các đối tượng đã được cập nhật và sẵn sàng để được vẽ lại trên màn hình. chúng ta có thể tái sử dụng RealmResults and RealmObjects

Dùng Autoincrementing IDs

Autoincrementing IDs không được hỗ trợ bởi Realm (by design). Bạn vẫn có thể tạo ra các khóa chính đáp ứng các usecase cung cấp bởi autoincrementing IDs, nhưng điều quan trọng là xác định loại autoincrementing ID nào, sử dụng cho mục đích nào.

Cung cấp định danh duy nhất để xác định đối tượng

Điều này có thể được thay thế bởi một GUID, đảm bảo duy nhất và được tạo ra bở 1 thiết bị ngay cả khi nó offline

public class Person extends RealmObject {
    @PrimaryKey
    private String id = UUID.randomUUID().toString();
    private String name;
}

Cung cấp trật tự chèn lỏng lẻo (loose insertion order)

Một ví dụ là sắp xếp các tweet, điều này hoàn toàn có thể thay thế bởi một trường createdAt mà không cần phải là khóa chính

public class Person extends RealmObject {
    @PrimaryKey
    private String id = UUID.randomUUID().toString();
    private Date createdAt = new Date();
    private String name;
}

Cung cấp trật tự chèn chặt chẽ (strict insertion order)

Một ví dụ là sắp xếp một list các task, điều này có thể được thực hiện bởi việc dùng RealmList, nó sẽ đảm bảo được thứ tự chèn ngay cả khi thiết bị đang offline

public class SortedPeople extends RealmObject {
    @PrimaryKey
    private int id = 0
    private RealmList<Person> persons;
}

public class Person extends RealmObject {
    private String name;
}

// Create wrapper object when creating object
RealmConfiguration config = new RealmConfiguration.Builder()
.initialData(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        realm.insert(new SortedPeople());
    }
});

// Insert objects through the wrapper
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        SortedPeople sortedPeople = realm.where(SortedPeople.class).findFirst();
        sortedPeople.getPersons().add(new Person());
    }
});

Nếu cả 3 phương pháp trên chưa thỏa được usecase của bài toán, bạn có thể tự tạo riêng cho mình theo cách dưới đây, nhưng phải nhớ là luôn query giá trị (ID) lớn nhất mỗi khi bắt đầu một transaction

realm.beginTransaction();
Number maxValue = realm.where(MyObject.class).max("ID");
long pk = (maxValue != null) ? maxValue + 1 : 0;
realm.createObject(MyObject.class, pk++);
realm.createObject(MyObject.class, pk++);
realm.commitTransaction();

Happy Coding !

0