2017-09-10 17 views
6

के साथ मूल सहकर्मी को हटाएं Google I/O '17 बातचीत में हंस बोहेम के रूप में "How to Manage Native C++ Memory in Android" सुझाव देता है कि मैं PhantomReference कक्षा का उपयोग करता हूं ताकि देशी सहकर्मियों को ठीक से हटा दिया जा सके।सामान्य फ़ैंटॉम रिफरेंस क्लास

18 min 57 sec पर लिंक किए गए वीडियो में वह किसी ऑब्जेक्ट का उदाहरण कार्यान्वयन दिखाता है जो PhantomReference कक्षा में इसके प्रकार के लिए पंजीकृत है। यह PhantomReference वर्ग, वह 19 min 49 sec पर दिखाता है। तो मैंने अपने उदाहरण वस्तु के लिए अपना दृष्टिकोण कॉपी किया। निचे देखो।

हालांकि यह दृष्टिकोण ठीक काम करता है, यह स्केल नहीं करता है। मुझे कुछ मात्रा में ऑब्जेक्ट्स बनाने की आवश्यकता होगी और मुझे बेस क्लास बनाने के लिए कोई रास्ता नहीं मिला है (या तो मेरी ऑब्जेक्ट्स या PhantomReference बेस क्लास) जो कोई ऑब्जेक्ट ले लेगा और मूल हटाने को ठीक से संभालेगा।

मैं सामान्य आधार PhantomReference कक्षा कैसे बना सकता हूं जो प्रदान की गई वस्तु पर देशी स्थैतिक विधि को कॉल कर सकता है?

मैंने PhantomReference जेनेरिक को बदलने की कोशिश की है लेकिन देशी स्थिर हटाने विधि एक कार्यान्वयन में बाधा डालती है।

मेरे 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

मुझे विश्वास है कि यह दृष्टिकोण पैमाने पर नहीं है। लेकिन मैं आपके दूसरे मुद्दे को समझ नहीं पा रहा हूं कि आपको "बेस क्लास बनाने का कोई तरीका नहीं मिला है ... जो किसी भी वस्तु को ले जाएगा और देशी हटाने को ठीक से संभालेगा *"। आपके पास दो वर्गों का एक समाधान समाधान है। उस समस्या को किस समस्या को हल करना चाहिए और कैसे? – Holger

+0

@ होल्गर आपके उत्तर के लिए धन्यवाद। कृपया मुझे स्केल मुद्दा समझाओ? यही वह है जो मैं एक काल्पनिक आधार वर्ग के साथ हल करने की कोशिश करता हूं। मेरे पास ऐसी कई वस्तुएं हैं और उनमें से प्रत्येक के लिए मुझे दूसरी प्रेत कक्षा बनाना है। बेस क्लास के साथ मैं यह हल करना चाहता हूं कि मुझे अतिरिक्त कक्षा बनाने की आवश्यकता नहीं होगी या इसे इतना आसान बनाना होगा कि मुझे केवल प्रकार को परिभाषित करने की आवश्यकता है। –

+0

रुको- आप अपनी प्रत्येक वस्तु के लिए एक नया प्रेत वर्ग बनाते हैं? या "उनमें से प्रत्येक" के साथ आपका क्या मतलब है? – Holger

उत्तर

5

आप जावा 9 की Cleaner एपीआई, जो एक समान कार्य, सफाई एक PhantomReference के आसपास बनाया गया के पते पर एक नज़र हो सकता है, और एक समान बात लागू , इसे आपकी जरूरतों के अनुरूप अनुकूलित किया। चूंकि आपको एकाधिक क्लीनर का समर्थन करने की आवश्यकता नहीं है, इसलिए आप static पंजीकरण विधि के साथ रह सकते हैं। मैं संदर्भ की अमूर्त है, यानी Cleanable इंटरफेस, रखने के लिए यह सुनिश्चित करें कि कोई विरासत में मिला संदर्भ विधि लागू किया जा सकता है की सलाह देते हैं, विशेष रूप से के रूप में clear() और clean() भ्रमित करने के लिए आसान कर रहे हैं:

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

यह जावा 8 सुविधाओं का उपयोग करता; यदि ConcurrentHashMap.newKeySet() उपलब्ध नहीं है, तो आप इसके बजाय Collections.newSetFromMap(new ConcurrentHashMap<CleanerReference,Boolean>()) का उपयोग कर सकते हैं।

यह स्पष्ट सफाई को गति प्रदान करने deleteOrphanedNativePeerObjects() रखा है, लेकिन यह धागा सुरक्षित है, तो यह, जैसे ही वे कतारबद्ध नहीं हैं आइटम साफ करने के लिए मूल में की तरह एक डेमॉन पृष्ठभूमि धागा बनाने के लिए कोई समस्या नहीं होगी।

Runnable के रूप में कार्रवाई जताते मनमाने ढंग से संसाधनों के लिए इस का उपयोग करने की अनुमति देता है, और हो रही Cleanable वापस कचरा कलेक्टर पर निर्भर, जबकि अभी भी उन वस्तुओं है कि बंद कर दिया नहीं किया गया है के लिए सुरक्षा तंत्र बिना स्पष्ट सफाई का समर्थन करने के लिए अनुमति देता है।

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

} 

AutoCloseable लागू करने से, यह try-with-resources निर्माण के साथ इस्तेमाल किया जा सकता, लेकिन यह भी लागू close() मैन्युअल रूप से संभव है अगर वहाँ एक सरल ब्लॉक गुंजाइश नहीं है। इसे मैन्युअल रूप से बंद करने का लाभ केवल इतना नहीं है कि अंतर्निहित संसाधन बहुत पहले बंद हो जाता है, प्रेत वस्तु को Set से हटा दिया जाता है और पूरे जीवन चक्र को और अधिक कुशल बनाने के लिए कभी भी नकल नहीं किया जाएगा, खासकर जब आप बहुत कुछ बनाते हैं और उपयोग करते हैं छोटी शर्तों में वस्तुओं। लेकिन अगर close() नहीं कहा जाता है, तो क्लीनर अंततः कचरा कलेक्टर द्वारा लगाया जाता है।

कक्षाओं को मैन्युअल रूप से बंद करने के लिए समर्थन करने वाले वर्गों के लिए, बंद होने के बाद इसका उपयोग करने के प्रयासों को पहचानने और अस्वीकार करने के लिए ध्वज रखना उपयोगी होगा।

यदि आपके लक्ष्य के लिए लैम्ब्डा एक्सप्रेशन उपलब्ध नहीं हैं, तो आप आंतरिक कक्षा के माध्यम से Runnable लागू कर सकते हैं; यह प्रेत संदर्भ के एक और उपclass बनाने से अभी भी आसान है। this उदाहरण कैप्चर करने के लिए देखभाल नहीं की जानी चाहिए, इसीलिए सृजन को ऊपर दिए गए उदाहरण में static विधि में स्थानांतरित कर दिया गया है। this के दायरे में, इसे दुर्घटना से पकड़ा नहीं जा सकता है। उदाहरण फ़ील्ड के बजाय पैरामीटर मानों के उपयोग को लागू करने के लिए विधि Object के रूप में संदर्भ को भी घोषित करती है।

+0

यह एक आकर्षण की तरह काम करता है। आपका बहुत बहुत धन्यवाद। मैंने 'रननेबल' को अपनी कक्षा से बदल दिया है, क्योंकि 'रननेबल' अक्सर थ्रेडिंग के उपयोग को संदर्भित करता है और मैं इसके बारे में किसी भी संभावित भ्रम को पेश नहीं करना चाहता था। –

+0

मुझे 'Collections.newSetFromMap (नया ConcurrentHashMap <क्लीनर रिफरेंस, बूलियन>()) 'कॉल का उपयोग करना था क्योंकि एंड्रॉइड ऐप का लक्ष्य संस्करण एपीआई स्तर 15 है। मैं निम्नलिखित समस्या में भाग गया https://stackoverflow.com/ ए/25705596/1306012 –

+0

ठीक है, 'संग्रह। newewetFromMap (...)' मैंने पूर्व-जावा 8 वातावरण के लिए मेरे उत्तर में भी सुझाव दिया था। 'NewKeySet() 'और' keySet() 'के बीच का अंतर याद रखें: पूर्व एक विशेष' ConcurrentHashMap' फैक्ट्री विधि है जो केवल जावा 8 या नए में मौजूद है, बाद वाला एक सामान्य 'मानचित्र' विधि है जो एक सेट दृश्य देता है मानचित्र की चाबियाँ और 'एड' ऑपरेशंस का समर्थन नहीं करती हैं, इसलिए, यह वैसे भी उचित नहीं होगा। – Holger

संबंधित मुद्दे