2017-09-10 31 views
6

Vì Hans Boehm trong Google I/O '17 talk "How to Manage Native C++ Memory in Android" đề xuất tôi sử dụng lớp PhantomReference để đảm bảo các đồng nghiệp gốc bị xóa đúng cách.Xóa đồng đẳng gốc với lớp chung PhantomReference

Trong video được liên kết tại 18 min 57 sec, anh ấy hiển thị ví dụ về việc thực hiện đối tượng tự đăng ký lớp PhantomReference cho loại đó. Lớp học PhantomReference này, sau đó anh hiển thị ở số 19 min 49 sec. Vì vậy, tôi sao chép cách tiếp cận của mình cho đối tượng ví dụ của tôi. Xem bên dưới.

Trong khi phương pháp này hoạt động tốt, nó không mở rộng. Tôi sẽ cần tạo một số lượng đối tượng và tôi không tìm được cách tạo lớp cơ sở (hoặc cho đối tượng của tôi hoặc lớp cơ sở PhantomReference) sẽ lấy bất kỳ đối tượng nào và xử lý việc xóa gốc một cách chính xác.

Làm cách nào để tạo một lớp chung cơ sở PhantomReference mà có thể gọi phương thức tĩnh gốc trên đối tượng được cung cấp?

Tôi đã cố gắng biến đổi phương thức xóa chung PhantomReference nhưng phương pháp xóa tĩnh gốc cản trở việc triển khai.

My WorkViewModel

import android.databinding.*; 

public class WorkViewModel extends BaseObservable 
{ 
    private long _nativeHandle; 

    public WorkViewModel(Database database, int workId) 
    { 
    _nativeHandle = create(database.getNativeHandle(), workId); 
    WorkViewModelPhantomReference.register(this, _nativeHandle); 
    } 

    private static native long create(long databaseHandle, int workId); 
    static native void delete(long nativeHandle); 

    @Bindable 
    public native int getWorkId(); 
    public native void setWorkId(int workId); 
} 

WorkViewModelPhantomReference

import java.lang.ref.*; 
import java.util.*; 

public class WorkViewModelPhantomReference extends PhantomReference<WorkViewModel> 
{ 
    private static Set<WorkViewModelPhantomReference> phantomReferences = new HashSet<WorkViewModelPhantomReference>(); 
    private static ReferenceQueue<WorkViewModel> garbageCollectedObjectsQueue = new ReferenceQueue<WorkViewModel>(); 
    private long _nativeHandle; 

    private WorkViewModelPhantomReference(WorkViewModel workViewModel, long nativeHandle) 
    { 
    super(workViewModel, garbageCollectedObjectsQueue); 
    _nativeHandle = nativeHandle; 
    } 

    public static void register(WorkViewModel workViewModel, long nativeHandle) 
    { 
    phantomReferences.add(new WorkViewModelPhantomReference(workViewModel, nativeHandle)); 
    } 

    public static void deleteOrphanedNativePeerObjects() 
    { 
    WorkViewModelPhantomReference reference; 

    while((reference = (WorkViewModelPhantomReference)garbageCollectedObjectsQueue.poll()) != null) 
    { 
     WorkViewModel.delete(reference._nativeHandle); 
     phantomReferences.remove(reference); 
    } 
    } 
} 
+0

Tôi tin rằng ngay lập tức phương pháp này không mở rộng quy mô. Nhưng tôi không hiểu vấn đề thứ hai của bạn rằng bạn "* chưa tìm ra cách tạo lớp cơ sở ... sẽ lấy bất kỳ đối tượng nào và sẽ xử lý việc xóa nội dung chính xác *". Bạn có một giải pháp làm việc bao gồm hai lớp. Vấn đề gì mà lớp cơ sở giả thuyết đó giải quyết và làm thế nào? – Holger

+0

@Holger cảm ơn câu trả lời của bạn. Xin giải thích cho tôi vấn đề quy mô? Đó là những gì tôi cố gắng giải quyết với một lớp cơ sở giả định. Tôi đã có rất nhiều đối tượng như vậy và cho mỗi người trong số những người tôi đã tạo ra một lớp ma thứ hai. Với một lớp cơ sở, tôi muốn giải quyết rằng tôi sẽ không cần phải tạo thêm một lớp hoặc làm cho nó đơn giản đến nỗi tôi chỉ cần định nghĩa kiểu đó. –

+0

Đợi — bạn tạo một lớp ma mới cho từng đối tượng bạn tạo? Hoặc bạn có ý gì với "mỗi người trong số đó"? – Holger

Trả lời

5

My Bạn có thể có một cái nhìn tại Cleaner Java API 9, mà giải quyết một nhiệm vụ tương tự, dọn dẹp xây dựng xung quanh một PhantomReference, và thực hiện một điều tương tự , thích nghi với nhu cầu của bạn. Vì bạn không cần hỗ trợ nhiều trình dọn dẹp, bạn có thể ở lại với phương thức đăng ký static. Tôi khuyên bạn nên giữ trừu tượng của các tài liệu tham khảo, ví dụ: giao diện Cleanable, để đảm bảo rằng không có phương pháp tham khảo di truyền có thể được gọi, đặc biệt là khi clear()clean() rất dễ nhầm lẫn:

public class Cleaner { 
    public interface Cleanable { 
     void clean(); 
    } 
    public static Cleanable register(Object o, Runnable r) { 
     CleanerReference c = new CleanerReference(
       Objects.requireNonNull(o), Objects.requireNonNull(r)); 
     phantomReferences.add(c); 
     return c; 
    } 
    private static final Set<CleanerReference> phantomReferences 
              = ConcurrentHashMap.newKeySet(); 
    private static final ReferenceQueue<Object> garbageCollectedObjectsQueue 
               = new ReferenceQueue<>(); 

    static final class CleanerReference extends PhantomReference<Object> 
             implements Cleanable { 
     private final Runnable cleaningAction; 

     CleanerReference(Object referent, Runnable action) { 
      super(referent, garbageCollectedObjectsQueue); 
      cleaningAction = action; 
     } 
     public void clean() { 
      if(phantomReferences.remove(this)) { 
       super.clear(); 
       cleaningAction.run(); 
      } 
     } 
    } 
    public static void deleteOrphanedNativePeerObjects() { 
     CleanerReference reference; 
     while((reference=(CleanerReference)garbageCollectedObjectsQueue.poll()) != null) { 
      reference.clean(); 
     } 
    } 
} 

này sử dụng Java 8 tính năng; nếu ConcurrentHashMap.newKeySet() không có sẵn, bạn có thể sử dụng Collections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>()) để thay thế.

Nó giữ deleteOrphanedNativePeerObjects() để kích hoạt dọn dẹp một cách rõ ràng, nhưng nó là chủ đề an toàn, do đó, sẽ không có vấn đề gì khi tạo chuỗi nền daemon để làm sạch mặt hàng ngay sau khi được xếp hàng, như trong bản gốc.

Thể hiện hành động là Runnable cho phép sử dụng tài nguyên tùy ý này và lấy lại Cleanable cho phép hỗ trợ dọn dẹp rõ ràng mà không dựa vào bộ thu gom rác trong khi vẫn có lưới an toàn cho những đối tượng đó không bị đóng.

public class WorkViewModel extends BaseObservable implements AutoCloseable 
{ 
    private long _nativeHandle; 
    Cleaner.Cleanable cleanable; 

    public WorkViewModel(Database database, int workId) 
    { 
     _nativeHandle = create(database.getNativeHandle(), workId); 
     cleanable = createCleanable(this, _nativeHandle); 
    } 
    private static Cleaner.Cleanable createCleanable(Object o, long _nativeHandle) { 
     return Cleaner.register(o,() -> delete(_nativeHandle)); 
    } 

    @Override 
    public void close() { 
     cleanable.clean(); 
    } 

    private static native long create(long databaseHandle, int workId); 
    static native void delete(long nativeHandle); 

    @Bindable 
    public native int getWorkId(); 
    public native void setWorkId(int workId); 

} 

Bằng cách thực hiện AutoCloseable, nó có thể được sử dụng với try-with-resources xây dựng, mà còn gọi close() bằng tay là có thể, nếu không có một phạm vi khối đơn giản. Ưu điểm của việc đóng nó theo cách thủ công, không chỉ là tài nguyên cơ bản bị đóng sớm hơn, mà còn đối tượng ảo bị loại bỏ khỏi Set và sẽ không bao giờ được enqueued, làm cho toàn bộ vòng đời hiệu quả hơn, đặc biệt là khi bạn tạo và sử dụng rất nhiều đối tượng trong ngắn hạn. Nhưng nếu không được gọi là close(), chất tẩy rửa sẽ được thu gom bởi bộ thu gom rác cuối cùng.

Đối với các lớp được hỗ trợ đóng theo cách thủ công, việc giữ cờ sẽ hữu ích, để phát hiện và từ chối các nỗ lực sử dụng sau khi đóng.

Nếu biểu thức lambda không có sẵn cho mục tiêu của bạn, bạn có thể triển khai Runnable qua lớp bên trong; nó vẫn đơn giản hơn việc tạo một lớp con khác của tham chiếu ảo. Cần phải cẩn thận để không chụp được bản sao this, đó là lý do tại sao quá trình tạo đã được chuyển sang phương thức static trong ví dụ ở trên. Nếu không có một phạm vi this, nó không thể bị bắt một cách tình cờ. Phương thức này cũng khai báo tham chiếu là Object, để thực thi việc sử dụng các giá trị tham số thay vì các trường mẫu.

+0

Nó hoạt động như một sự quyến rũ. Cảm ơn nhiều. Tôi đã thay thế 'Runnable' bằng một lớp riêng, vì' Runnable' rất thường đề cập đến việc sử dụng luồng và tôi không muốn giới thiệu bất kỳ sự nhầm lẫn nào về điều này. –

+0

Tôi đã phải sử dụng 'Collections.newSetFromMap (mới ConcurrentHashMap ())' gọi vì phiên bản đích cho ứng dụng Android là API cấp 15. Tôi chạy vào vấn đề sau https://stackoverflow.com/ a/25705596/1306012 –

+0

Vâng, 'Collections.newSetFromMap (…)' là những gì tôi cũng đề xuất trong câu trả lời của tôi cho các môi trường tiền Java 8. Chú ý sự khác biệt giữa 'newKeySet()' và 'keySet()': trước đây là phương thức factory đặc biệt 'ConcurrentHashMap' chỉ tồn tại trong Java 8 hoặc mới hơn, phương thức sau là phương thức' Map' chung trả về một khung nhìn đã đặt các khóa của bản đồ và không hỗ trợ các hoạt động 'add', do đó, nó sẽ không phù hợp. – Holger

Các vấn đề liên quan