Một vài kinh nghiệm khi dùng Realm trong android - Part 2
Bài đăng này đã không được cập nhật trong 6 năm
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 View mà Observe 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 Fragment và Activity, 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 instance và RecyclerViewAdapter đượ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ả RealmObjects và RealmResults đượ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 !
All rights reserved