2017-09-10 6 views
6

Google I/O '17トークの「Hans Boehm」は、クラスを使用して、ネイティブピアが正しく削除されるようにすることを提案しています。一般的なPhantomReferenceクラスのネイティブピアを削除する

18 min 57 secのリンク先のビデオでは、PhantomReferenceクラスに自身を登録しているオブジェクトの実装例を示しています。このPhantomReferenceクラスでは、彼は19 min 49 secになります。だから私は彼のアプローチを私のサンプルオブジェクトにコピーしました。下記参照。

このアプローチは問題なく機能しますが、縮尺は変わりません。かなりの量のオブジェクトを作成する必要があります。オブジェクトを取得し、ネイティブの削除を適切に処理する基本クラス(自分のオブジェクト用またはPhantomReference基本クラス)を作成する方法が見つかりませんでした。

付属のネイティブ静的メソッドを呼び出すことができる汎用ベースPhantomReferenceクラスを作成するにはどうすればよいですか?

PhantomReference genericを変換しようとしましたが、ネイティブの静的な削除方法が実装を妨げています。

私のあなたはPhantomReferenceを中心に構築された同様のタスク、クリーンアップに取り組むJavaの9のCleaner API、見ていると、同じようなことを実現することが私の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

私はすぐにこのアプローチが拡大縮小しないと信じています。しかし、私は、あなたが "*基本クラスを作成する方法を見つけられていないというあなたの2番目の問題を理解していません。オブジェクトを取ってネイティブの削除を適切に処理します*"。あなたは2つのクラスから成る実用的なソリューションを持っています。その仮説的な基底クラスがどのような問題を解決し、どのようにしたらよいでしょうか? – Holger

+0

@Holgerご返信ありがとうございます。規模の問題を私に説明してください。それが私が仮定的な基底クラスで解決しようとしているものです。私は多くのそのようなオブジェクトを持っていて、それらのそれぞれについて、私は第2のファントムクラスを作成しました。ベースクラスでは、余分なクラスを作成する必要はなく、単純にタイプを定義するだけでよいということを解決したいと考えています。 –

+0

作成したオブジェクトごとに新しいファントムクラスを作成するのを待ちますか?あるいは「それぞれの」とはどういう意味ですか? – Holger

答えて

5

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

それをあなたのニーズに合わせました。複数のクリーナーをサポートする必要がないので、static登録メソッドを使用することができます。私はclear()clean()が混同しやすい、特にとして、継承された参照メソッドが呼び出されないことを保証するために、すなわちCleanableインターフェース、参照の抽象化を維持することをお勧めします:

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

これは、Java 8つの機能を使用しています。 ConcurrentHashMap.newKeySet()が利用できない場合は、代わりにCollections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>())を使用することができます。

これは、明示的にクリーンアップをトリガするdeleteOrphanedNativePeerObjects()を保ったが、それはスレッドセーフなので、元のように、できるだけ早く彼らがエンキューされているなどの項目をきれいにするデーモンバックグラウンドスレッドを作成しても問題ないだろう。

Runnableと表現すると、これを任意のリソースに使用することができ、Cleanableを戻すことで、ガベージコレクタに頼らずに明示的なクリーンアップをサポートできるようになります。 AutoCloseableを実装することによって

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

} 

は、try-with-resources構築物で使用することができるだけでなく、簡単なブロックスコープがない場合、手動でclose()を呼び出すことは、可能です。手動でクローズする利点は、基盤リソースがもっと早期にクローズされるだけでなく、ファントムオブジェクトがSetから削除され、エンキューされないため、ライフサイクルをより効率的にします。短期間でオブジェクト。しかし、close()が呼び出されていない場合は、最終的にはガベージコレクタによってエンキューされます。

手動で閉じることをサポートするクラスでは、フラグを保持すると、閉じた後にその使用を検出して拒否することができます。

ターゲットでラムダ式が使用できない場合は、内部クラスを使用してRunnableを実装できます。ファントム参照の別のサブクラスを作成するよりも簡単です。 thisインスタンスをキャプチャしないように注意する必要があります。そのため、上記の例では、作成がstaticメソッドに移動されています。範囲内にthisがなければ、それは偶然にキャプチャすることはできません。また、このメソッドは、参照フィールドをObjectと宣言し、インスタンスフィールドの代わりにパラメータ値の使用を強制します。

+0

それは魅力のように動作します。どうもありがとうございました。私は 'Runnable'を独自のクラスで置き換えました。なぜなら、' Runnable'は非常にしばしばスレッドの使用を指しており、私はこれについて何らかの混乱を招きたくないからです。 –

+0

Android AppのターゲットバージョンがAPIレベル15であるため、 'Collections.newSetFromMap(新しいConcurrentHashMap ())'呼び出しを使用する必要がありました。次の問題が発生しました。https://stackoverflow.com/ a/25705596/1306012 –

+0

「Collections.newSetFromMap(...)」は、Java 8以前の環境での私の答えでも示唆したものでした。 'newKeySet()'と 'keySet()'の違いを気にしてください。前者はJava 8以降にしか存在しない特別な 'ConcurrentHashMap'ファクトリメソッドです。後者はセットビューを返す一般的な' Map'メソッドです。地図のキーであり、 'add'操作をサポートしていないため、とにかく適切ではありません。 – Holger