कार यूआई प्लगइन्स

रनटाइम रिसोर्स ओवरले (आरआरओ) का उपयोग करने के बजाय कार यूआई लाइब्रेरी में घटक अनुकूलन का पूर्ण कार्यान्वयन बनाने के लिए कार यूआई लाइब्रेरी प्लगइन्स का उपयोग करें। आरआरओ आपको कार यूआई लाइब्रेरी घटकों के केवल एक्सएमएल संसाधनों को बदलने में सक्षम बनाता है, जो आपके द्वारा अनुकूलित किए जाने की सीमा को सीमित करता है।

एक प्लगइन बनाएं

कार यूआई लाइब्रेरी प्लगइन एक एपीके है जिसमें ऐसी कक्षाएं शामिल हैं जो प्लगइन एपीआई के एक सेट को लागू करती हैं। प्लगइन एपीआई को एक स्थिर लाइब्रेरी के रूप में प्लगइन में संकलित किया जा सकता है।

सूंग और ग्रैडल में उदाहरण देखें:

सूंग

इस सूंग उदाहरण पर विचार करें:

android_app {
    name: "my-plugin",

    min_sdk_version: "28",
    target_sdk_version: "30",
    aaptflags: ["--shared-lib"],
    sdk_version: "current",

    manifest: "src/main/AndroidManifest.xml",
    srcs: ["src/main/java/**/*.java"],
    resource_dirs: ["src/main/res"],
    static_libs: [
        "car-ui-lib-oem-apis",
    ],
    // Disable optimization is mandatory to prevent R.java class from being
    // stripped out
    optimize: {
        enabled: false,
    },

    certificate: ":my-plugin-certificate",
}

ग्रैडल

यह build.gradle फ़ाइल देखें:

apply plugin: 'com.android.application'

android {
  compileSdkVersion 30

  defaultConfig {
    minSdkVersion 28
    targetSdkVersion 30
  }

  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

  signingConfigs {
    debug {
      storeFile file('chassis_upload_key.jks')
      storePassword 'chassis'
      keyAlias 'chassis'
      keyPassword 'chassis'
    }
  }
}

dependencies {
  implementation project(':oem-apis')
  // Or use the following if you'd like to use the maven artifact
  // implementation 'com.android.car.ui:car-ui-lib-plugin-apis:1.0.0'
}

Settings.gradle :

// You can remove the ':oem-apis' if you're using the maven artifact.
include ':oem-apis'
project(':oem-apis').projectDir = new File('./path/to/oem-apis')
include ':my-plugin'
project(':my-plugin').projectDir = new File('./my-plugin')

प्लगइन के मेनिफेस्ट में एक सामग्री प्रदाता घोषित होना चाहिए जिसमें निम्नलिखित विशेषताएं हों:

  android:authorities="com.android.car.ui.plugin"
  android:enabled="true"
  android:exported="true"

android:authorities="com.android.car.ui.plugin" प्लगइन को कार यूआई लाइब्रेरी में खोजने योग्य बनाता है। प्रदाता को निर्यात करना होगा ताकि रनटाइम पर उससे पूछताछ की जा सके। साथ ही, यदि enabled विशेषता false पर सेट है तो प्लगइन कार्यान्वयन के बजाय डिफ़ॉल्ट कार्यान्वयन का उपयोग किया जाएगा। सामग्री प्रदाता वर्ग का अस्तित्व आवश्यक नहीं है। ऐसी स्थिति में, प्रदाता परिभाषा में tools:ignore="MissingClass" जोड़ना सुनिश्चित करें। नीचे नमूना मेनिफेस्ट प्रविष्टि देखें:

    <application>
        <provider
            android:name="com.android.car.ui.plugin.PluginNameProvider"
            android:authorities="com.android.car.ui.plugin"
            android:enabled="false"
            android:exported="true"
            tools:ignore="MissingClass"/>
    </application>

अंत में, सुरक्षा उपाय के रूप में, अपने ऐप पर हस्ताक्षर करें

एक साझा लाइब्रेरी के रूप में प्लगइन्स

एंड्रॉइड स्टैटिक लाइब्रेरीज़ के विपरीत, जिन्हें सीधे ऐप्स में संकलित किया जाता है, एंड्रॉइड साझा लाइब्रेरीज़ को एक स्टैंडअलोन एपीके में संकलित किया जाता है जिसे रनटाइम पर अन्य ऐप्स द्वारा संदर्भित किया जाता है।

एंड्रॉइड साझा लाइब्रेरी के रूप में कार्यान्वित किए गए प्लगइन्स की कक्षाएं स्वचालित रूप से ऐप्स के बीच साझा क्लासलोडर में जुड़ जाती हैं। जब कोई ऐप जो कार यूआई लाइब्रेरी का उपयोग करता है, प्लगइन साझा लाइब्रेरी पर रनटाइम निर्भरता निर्दिष्ट करता है, तो इसका क्लासलोडर प्लगइन साझा लाइब्रेरी की कक्षाओं तक पहुंच सकता है। सामान्य एंड्रॉइड ऐप्स (साझा लाइब्रेरी नहीं) के रूप में लागू किए गए प्लगइन्स ऐप कोल्ड स्टार्ट टाइम पर नकारात्मक प्रभाव डाल सकते हैं।

साझा पुस्तकालयों को लागू करें और बनाएं

एंड्रॉइड साझा लाइब्रेरीज़ के साथ विकास करना कुछ महत्वपूर्ण अंतरों के साथ सामान्य एंड्रॉइड ऐप्स की तरह ही है।

  • अपने प्लगइन के ऐप मेनिफेस्ट में प्लगइन पैकेज नाम के साथ application टैग के अंतर्गत library टैग का उपयोग करें:
    <application>
        <library android:name="com.chassis.car.ui.plugin" />
        ...
    </application>
  • अपने Soong android_app बिल्ड नियम ( Android.bp ) को AAPT ध्वज shared-lib के साथ कॉन्फ़िगर करें, जिसका उपयोग साझा लाइब्रेरी बनाने के लिए किया जाता है:
android_app {
  ...
  aaptflags: ["--shared-lib"],
  ...
}

साझा पुस्तकालयों पर निर्भरता

सिस्टम पर प्रत्येक ऐप के लिए जो कार यूआई लाइब्रेरी का उपयोग करता है, प्लगइन पैकेज नाम के साथ application टैग के तहत ऐप मेनिफेस्ट में uses-library टैग शामिल करें:

<manifest>
  <application
      android:name=".MyApp"
      ...>
    <uses-library android:name="com.chassis.car.ui.plugin" android:required="false"/>
    ...
  </application>
</manifest>

एक प्लगइन स्थापित करें

PRODUCT_PACKAGES में मॉड्यूल को शामिल करके प्लगइन्स को सिस्टम विभाजन पर पूर्वस्थापित किया जाना चाहिए। पहले से इंस्टॉल किए गए पैकेज को किसी अन्य इंस्टॉल किए गए ऐप की तरह ही अपडेट किया जा सकता है।

यदि आप सिस्टम पर किसी मौजूदा प्लगइन को अपडेट कर रहे हैं, तो उस प्लगइन का उपयोग करने वाला कोई भी ऐप स्वचालित रूप से बंद हो जाता है। उपयोगकर्ता द्वारा दोबारा खोले जाने पर, उनके पास अद्यतन परिवर्तन होते हैं। यदि ऐप नहीं चल रहा था, तो अगली बार जब यह शुरू होगा तो इसमें अपडेटेड प्लगइन होगा।

एंड्रॉइड स्टूडियो के साथ प्लगइन इंस्टॉल करते समय, कुछ अतिरिक्त बातों का ध्यान रखना चाहिए। लेखन के समय, एंड्रॉइड स्टूडियो ऐप इंस्टॉलेशन प्रक्रिया में एक बग है जिसके कारण प्लगइन के अपडेट प्रभावी नहीं हो पाते हैं। इसे प्लगइन के बिल्ड कॉन्फ़िगरेशन में हमेशा पैकेज मैनेजर के साथ इंस्टॉल करें (एंड्रॉइड 11 और बाद के संस्करणों पर अनुकूलन को अक्षम करता है) विकल्प का चयन करके ठीक किया जा सकता है।

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

प्लगइन एंड्रॉइड स्टूडियो कॉन्फ़िगरेशन चित्र 1. प्लगइन एंड्रॉइड स्टूडियो कॉन्फ़िगरेशन

प्रॉक्सी प्लगइन

कार यूआई लाइब्रेरी का उपयोग करके ऐप्स के अनुकूलन के लिए एक आरआरओ की आवश्यकता होती है जो संशोधित किए जाने वाले प्रत्येक विशिष्ट ऐप को लक्षित करता है, जिसमें सभी ऐप्स में अनुकूलन समान होने पर भी शामिल है। इसका मतलब है कि प्रति ऐप एक आरआरओ आवश्यक है। देखें कि कौन से ऐप्स कार यूआई लाइब्रेरी का उपयोग करते हैं।

कार यूआई लाइब्रेरी प्रॉक्सी प्लगइन एक उदाहरण प्लगइन साझा लाइब्रेरी है जो अपने घटक कार्यान्वयन को कार यूआई लाइब्रेरी के स्थिर संस्करण में सौंपती है। इस प्लगइन को आरआरओ के साथ लक्षित किया जा सकता है, जिसका उपयोग उन ऐप्स के लिए अनुकूलन के एकल बिंदु के रूप में किया जा सकता है जो कार्यात्मक प्लगइन को लागू करने की आवश्यकता के बिना कार यूआई लाइब्रेरी का उपयोग करते हैं। आरआरओ के बारे में अधिक जानकारी के लिए, रनटाइम पर ऐप के संसाधनों का मूल्य बदलें देखें।

प्रॉक्सी प्लगइन एक प्लगइन का उपयोग करके अनुकूलन करने के लिए केवल एक उदाहरण और शुरुआती बिंदु है। आरआरओ से परे अनुकूलन के लिए, कोई प्लगइन घटकों का एक सबसेट लागू कर सकता है और बाकी के लिए प्रॉक्सी प्लगइन का उपयोग कर सकता है, या सभी प्लगइन घटकों को पूरी तरह से स्क्रैच से लागू कर सकता है।

हालाँकि प्रॉक्सी प्लगइन ऐप्स के लिए आरआरओ अनुकूलन का एक बिंदु प्रदान करता है, जो ऐप्स प्लगइन का उपयोग करने से ऑप्ट-आउट करते हैं उन्हें अभी भी एक आरआरओ की आवश्यकता होगी जो सीधे ऐप को ही लक्षित करता है।

प्लगइन एपीआई लागू करें

प्लगइन का मुख्य प्रवेश बिंदु com.android.car.ui.plugin.PluginVersionProviderImpl वर्ग है। सभी प्लगइन्स में इस सटीक नाम और पैकेज नाम के साथ एक क्लास शामिल होनी चाहिए। इस क्लास में एक डिफ़ॉल्ट कंस्ट्रक्टर होना चाहिए और PluginVersionProviderOEMV1 इंटरफ़ेस लागू करना चाहिए।

CarUi प्लगइन्स को उन ऐप्स के साथ काम करना चाहिए जो प्लगइन से पुराने या नए हैं। इसे सुविधाजनक बनाने के लिए, सभी प्लगइन एपीआई को उनके क्लासनाम के अंत में V# के साथ संस्करणित किया गया है। यदि कार यूआई लाइब्रेरी का नया संस्करण नई सुविधाओं के साथ जारी किया जाता है, तो वे घटक के V2 संस्करण का हिस्सा हैं। कार यूआई लाइब्रेरी पुराने प्लगइन घटक के दायरे में नई सुविधाओं को काम करने की पूरी कोशिश करती है। उदाहरण के लिए, टूलबार में एक नए प्रकार के बटन को MenuItems में परिवर्तित करके।

हालाँकि, कार यूआई लाइब्रेरी के पुराने संस्करण वाला ऐप नए एपीआई के खिलाफ लिखे गए नए प्लगइन को अनुकूलित नहीं कर सकता है। इस समस्या को हल करने के लिए, हम प्लगइन्स को ऐप्स द्वारा समर्थित OEM एपीआई के संस्करण के आधार पर स्वयं के विभिन्न कार्यान्वयन वापस करने की अनुमति देते हैं।

PluginVersionProviderOEMV1 में एक विधि है:

Object getPluginFactory(int maxVersion, Context context, String packageName);

यह विधि एक ऑब्जेक्ट लौटाती है जो प्लगइन द्वारा समर्थित PluginFactoryOEMV# के उच्चतम संस्करण को लागू करती है, जबकि अभी भी maxVersion से कम या उसके बराबर है। यदि किसी प्लगइन में इतने पुराने PluginFactory का कार्यान्वयन नहीं है, तो यह null वापस आ सकता है, ऐसी स्थिति में CarUi घटकों के स्थिर रूप से जुड़े कार्यान्वयन का उपयोग किया जाता है।

स्थिर कार यूआई लाइब्रेरी के पुराने संस्करणों के विरुद्ध संकलित ऐप्स के साथ बैकवर्ड संगतता बनाए रखने के लिए, आपके प्लगइन के PluginVersionProvider वर्ग के कार्यान्वयन के भीतर 2, 5 और उच्चतर के maxVersion s का समर्थन करने की अनुशंसा की जाती है। संस्करण 1, 3, और 4 समर्थित नहीं हैं। अधिक जानकारी के लिए, PluginVersionProviderImpl देखें।

PluginFactory वह इंटरफ़ेस है जो अन्य सभी CarUi घटकों को बनाता है। यह यह भी परिभाषित करता है कि उनके इंटरफ़ेस के किस संस्करण का उपयोग किया जाना चाहिए। यदि प्लगइन इनमें से किसी भी घटक को कार्यान्वित नहीं करना चाहता है, तो यह उनके निर्माण फ़ंक्शन में null वापस आ सकता है (टूलबार के अपवाद के साथ, जिसमें एक अलग customizesBaseLayout() फ़ंक्शन है)।

pluginFactory सीमित करती है कि CarUi घटकों के किन संस्करणों को एक साथ उपयोग किया जा सकता है। उदाहरण के लिए, ऐसी कोई pluginFactory कभी नहीं होगी जो Toolbar का संस्करण 100 और RecyclerView का संस्करण 1 भी बना सके, क्योंकि इस बात की बहुत कम गारंटी होगी कि घटकों के विभिन्न प्रकार के संस्करण एक साथ काम करेंगे। टूलबार संस्करण 100 का उपयोग करने के लिए, डेवलपर्स से pluginFactory के एक संस्करण का कार्यान्वयन प्रदान करने की अपेक्षा की जाती है जो एक टूलबार संस्करण 100 बनाता है, जो फिर बनाए जा सकने वाले अन्य घटकों के संस्करणों पर विकल्पों को सीमित करता है। अन्य घटकों के संस्करण समान नहीं हो सकते हैं, उदाहरण के लिए एक pluginFactoryOEMV100 एक ToolbarControllerOEMV100 और एक RecyclerViewOEMV70 बना सकता है।

उपकरण पट्टी

आधार लेआउट

टूलबार और "बेस लेआउट" बहुत निकट से संबंधित हैं, इसलिए टूलबार बनाने वाले फ़ंक्शन को installBaseLayoutAround कहा जाता है। बेस लेआउट एक अवधारणा है जो टूलबार को ऐप की सामग्री के आसपास कहीं भी स्थित करने की अनुमति देता है, ऐप के ऊपर/नीचे, किनारों के साथ लंबवत, या यहां तक ​​कि पूरे ऐप को घेरने वाले एक गोलाकार टूलबार के लिए टूलबार की अनुमति देता है। यह टूलबार/बेस लेआउट को लपेटने के लिए installBaseLayoutAround पर एक दृश्य पास करके पूरा किया जाता है।

प्लगइन को दिए गए दृश्य को लेना चाहिए, उसे अपने पैरेंट से अलग करना चाहिए, प्लगइन के स्वयं के लेआउट को पैरेंट के समान इंडेक्स में और उसी LayoutParams के साथ उस दृश्य के साथ फुलाना चाहिए जो अभी अलग किया गया था, और फिर दृश्य को उस लेआउट के अंदर कहीं फिर से जोड़ना चाहिए जो कि था बस फुलाया हुआ. यदि ऐप द्वारा अनुरोध किया गया है, तो फुलाए गए लेआउट में टूलबार शामिल होगा।

ऐप टूलबार के बिना बेस लेआउट का अनुरोध कर सकता है। यदि ऐसा होता है, तो installBaseLayoutAround शून्य वापस आना चाहिए। अधिकांश प्लगइन्स के लिए, बस इतना ही होना आवश्यक है, लेकिन यदि प्लगइन लेखक ऐप के किनारे के चारों ओर एक सजावट लागू करना चाहता है, तो यह अभी भी बेस लेआउट के साथ किया जा सकता है। ये सजावट गैर-आयताकार स्क्रीन वाले उपकरणों के लिए विशेष रूप से उपयोगी हैं, क्योंकि वे ऐप को आयताकार स्थान में धकेल सकते हैं और गैर-आयताकार स्थान में स्वच्छ बदलाव जोड़ सकते हैं।

installBaseLayoutAround को एक Consumer<InsetsOEMV1> भी पारित किया गया है। इस उपभोक्ता का उपयोग ऐप को यह बताने के लिए किया जा सकता है कि प्लगइन ऐप की सामग्री को आंशिक रूप से कवर कर रहा है (टूलबार के साथ या अन्यथा)। ऐप तब इस स्थान पर ड्राइंग बनाए रखना जानता होगा, लेकिन किसी भी महत्वपूर्ण उपयोगकर्ता-इंटरैक्टेबल घटकों को इससे बाहर रखेगा। इस प्रभाव का उपयोग हमारे संदर्भ डिज़ाइन में टूलबार को अर्ध-पारदर्शी बनाने और उसके नीचे सूचियाँ स्क्रॉल करने के लिए किया जाता है। यदि यह सुविधा लागू नहीं की गई थी, तो सूची में पहला आइटम टूलबार के नीचे अटका रहेगा और क्लिक करने योग्य नहीं होगा। यदि इस प्रभाव की आवश्यकता नहीं है, तो प्लगइन उपभोक्ता को अनदेखा कर सकता है।

सामग्री टूलबार के नीचे स्क्रॉल हो रही है चित्र 2. टूलबार के नीचे स्क्रॉल की गई सामग्री

ऐप के परिप्रेक्ष्य से, जब प्लगइन नए इनसेट भेजता है, तो यह उन्हें InsetsChangedListener लागू करने वाली किसी भी गतिविधि या टुकड़े से प्राप्त करेगा। यदि कोई गतिविधि या टुकड़ा InsetsChangedListener लागू नहीं करता है, तो कार Ui लाइब्रेरी खंड Activity या FragmentActivity में पैडिंग के रूप में इनसेट को लागू करके डिफ़ॉल्ट रूप से इनसेट को संभाल लेगी। लाइब्रेरी डिफॉल्ट रूप से इनसेट को टुकड़ों पर लागू नहीं करती है। यहां कार्यान्वयन का एक नमूना स्निपेट है जो इनसेट को ऐप में RecyclerView पर पैडिंग के रूप में लागू करता है:

public class MainActivity extends Activity implements InsetsChangedListener {
  @Override
  public void onCarUiInsetsChanged(Insets insets) {
    CarUiRecyclerView rv = requireViewById(R.id.recyclerview);
    rv.setPadding(insets.getLeft(), insets.getTop(),
                  insets.getRight(), insets.getBottom());
  }
}

अंत में, प्लगइन को एक fullscreen संकेत दिया जाता है, जिसका उपयोग यह इंगित करने के लिए किया जाता है कि जिस दृश्य को लपेटा जाना चाहिए वह संपूर्ण ऐप लेता है या केवल एक छोटा सा अनुभाग। इसका उपयोग किनारे पर कुछ सजावट लगाने से बचने के लिए किया जा सकता है जो केवल तभी समझ में आती हैं जब वे पूरी स्क्रीन के किनारे पर दिखाई देती हैं। एक नमूना ऐप जो गैर-फ़ुलस्क्रीन बेस लेआउट का उपयोग करता है वह सेटिंग्स है, जिसमें दोहरे फलक लेआउट के प्रत्येक फलक का अपना टूलबार होता है।

चूंकि toolbarEnabled false होने पर installBaseLayoutAround के लिए null वापस लौटने की उम्मीद की जाती है, इसलिए प्लगइन को यह इंगित करने के लिए कि वह बेस लेआउट को कस्टमाइज़ नहीं करना चाहता है, उसे customizesBaseLayout से false वापस आना होगा।

रोटरी नियंत्रणों को पूरी तरह से समर्थन देने के लिए बेस लेआउट में एक FocusParkingView और एक FocusArea होना चाहिए। इन दृश्यों को उन उपकरणों पर छोड़ा जा सकता है जो रोटरी का समर्थन नहीं करते हैं। FocusParkingView/FocusAreas स्थिर कारयूआई लाइब्रेरी में कार्यान्वित किया जाता है, इसलिए संदर्भों से दृश्य बनाने के लिए फ़ैक्टरियां प्रदान करने के लिए setRotaryFactories उपयोग किया जाता है।

फोकस दृश्य बनाने के लिए उपयोग किए जाने वाले संदर्भ स्रोत संदर्भ होने चाहिए, न कि प्लगइन का संदर्भ। FocusParkingView को यथासंभव पेड़ में पहले दृश्य के सबसे करीब होना चाहिए, क्योंकि यह वह है जिस पर ध्यान केंद्रित किया जाता है जब उपयोगकर्ता को कोई फोकस दिखाई नहीं देना चाहिए। FocusArea यह इंगित करने के लिए टूलबार को बेस लेआउट में लपेटना होगा कि यह एक रोटरी नज ज़ोन है। यदि FocusArea प्रदान नहीं किया गया है, तो उपयोगकर्ता रोटरी नियंत्रक के साथ टूलबार में किसी भी बटन पर नेविगेट करने में असमर्थ है।

टूलबार नियंत्रक

मूल लेआउट की तुलना में वास्तविक ToolbarController को लागू करना अधिक सरल होना चाहिए। इसका काम अपने सेटर्स को दी गई जानकारी लेना और उसे बेस लेआउट में प्रदर्शित करना है। अधिकांश विधियों की जानकारी के लिए Javadoc देखें। कुछ अधिक जटिल तरीकों पर नीचे चर्चा की गई है।

getImeSearchInterface का उपयोग IME (कीबोर्ड) विंडो में खोज परिणाम दिखाने के लिए किया जाता है। यह कीबोर्ड के साथ-साथ खोज परिणामों को प्रदर्शित/एनिमेट करने के लिए उपयोगी हो सकता है, उदाहरण के लिए यदि कीबोर्ड केवल स्क्रीन का आधा हिस्सा लेता है। अधिकांश कार्यक्षमता स्थिर CarUi लाइब्रेरी में कार्यान्वित की जाती है, प्लगइन में खोज इंटरफ़ेस केवल स्थिर लाइब्रेरी के लिए TextView और onPrivateIMECommand कॉलबैक प्राप्त करने के तरीके प्रदान करता है। इसका समर्थन करने के लिए, प्लगइन को एक TextView उपवर्ग का उपयोग करना चाहिए जो onPrivateIMECommand ओवरराइड करता है और दिए गए श्रोता को कॉल को उसके खोज बार के TextView के रूप में पास करता है।

setMenuItems केवल स्क्रीन पर MenuItems प्रदर्शित करता है, लेकिन इसे आश्चर्यजनक रूप से अक्सर कहा जाएगा। चूंकि MenuItems के लिए प्लगइन एपीआई अपरिवर्तनीय है, जब भी कोई MenuItem बदला जाता है, तो एक बिल्कुल नया setMenuItems कॉल होगा। यह इतनी छोटी सी बात के लिए हो सकता है जैसे कि किसी उपयोगकर्ता ने स्विच मेनूआइटम पर क्लिक किया और उस क्लिक के कारण स्विच चालू हो गया। प्रदर्शन और एनीमेशन दोनों कारणों से, इसलिए पुरानी और नई मेनूआइटम सूची के बीच अंतर की गणना करने और केवल उन दृश्यों को अपडेट करने के लिए प्रोत्साहित किया जाता है जो वास्तव में बदल गए हैं। MenuItems एक key फ़ील्ड प्रदान करता है जो इसमें मदद कर सकता है, क्योंकि कुंजी एक ही MenuItem के लिए setMenuItems पर विभिन्न कॉलों में समान होनी चाहिए।

AppStyledView

AppStyledView एक ऐसे दृश्य के लिए एक कंटेनर है जो बिल्कुल भी अनुकूलित नहीं है। इसका उपयोग उस दृश्य के चारों ओर एक बॉर्डर प्रदान करने के लिए किया जा सकता है जो इसे बाकी ऐप से अलग बनाता है, और उपयोगकर्ता को संकेत देता है कि यह एक अलग प्रकार का इंटरफ़ेस है। AppStyledView द्वारा लपेटा गया दृश्य setContent में दिया गया है। ऐप के अनुरोध के अनुसार AppStyledView में एक बैक या क्लोज़ बटन भी हो सकता है।

AppStyledView तुरंत अपने दृश्यों को installBaseLayoutAround की तरह दृश्य पदानुक्रम में सम्मिलित नहीं करता है, इसके बजाय यह केवल getView माध्यम से अपने दृश्य को स्थिर लाइब्रेरी में लौटाता है, जो फिर सम्मिलन करता है। AppStyledView की स्थिति और आकार को getDialogWindowLayoutParam लागू करके भी नियंत्रित किया जा सकता है।

संदर्भों

संदर्भों का उपयोग करते समय प्लगइन को सावधान रहना चाहिए, क्योंकि प्लगइन और "स्रोत" दोनों संदर्भ हैं। प्लगइन संदर्भ getPluginFactory के तर्क के रूप में दिया गया है, और यह एकमात्र संदर्भ है जिसमें प्लगइन के संसाधन हैं। इसका मतलब यह है कि यह एकमात्र संदर्भ है जिसका उपयोग प्लगइन में लेआउट को बढ़ाने के लिए किया जा सकता है।

हालाँकि, प्लगइन संदर्भ में सही कॉन्फ़िगरेशन सेट नहीं हो सकता है। सही कॉन्फ़िगरेशन प्राप्त करने के लिए, हम घटकों को बनाने वाली विधियों में स्रोत संदर्भ प्रदान करते हैं। स्रोत संदर्भ आमतौर पर एक गतिविधि है, लेकिन कुछ मामलों में एक सेवा या अन्य एंड्रॉइड घटक भी हो सकता है। प्लगइन संदर्भ से संसाधनों के साथ स्रोत संदर्भ से कॉन्फ़िगरेशन का उपयोग करने के लिए, createConfigurationContext का उपयोग करके एक नया संदर्भ बनाया जाना चाहिए। यदि सही कॉन्फ़िगरेशन का उपयोग नहीं किया जाता है, तो एंड्रॉइड सख्त मोड का उल्लंघन होगा, और फुलाए गए दृश्यों में सही आयाम नहीं हो सकते हैं।

Context layoutInflationContext = pluginContext.createConfigurationContext(
        sourceContext.getResources().getConfiguration());

मोड बदलता है

कुछ प्लगइन्स अपने घटकों के लिए कई मोड का समर्थन कर सकते हैं, जैसे स्पोर्ट मोड या इको मोड जो देखने में अलग दिखते हैं। CarUi में ऐसी कार्यक्षमता के लिए कोई अंतर्निहित समर्थन नहीं है, लेकिन प्लगइन को इसे पूरी तरह से आंतरिक रूप से लागू करने से कोई नहीं रोक सकता है। प्लगइन किसी भी स्थिति की निगरानी कर सकता है, यह पता लगाना चाहता है कि मोड कब स्विच करना है, जैसे प्रसारण सुनना। प्लगइन मोड बदलने के लिए कॉन्फ़िगरेशन परिवर्तन को ट्रिगर नहीं कर सकता है, लेकिन वैसे भी कॉन्फ़िगरेशन परिवर्तनों पर भरोसा करने की अनुशंसा नहीं की जाती है, क्योंकि प्रत्येक घटक की उपस्थिति को मैन्युअल रूप से अपडेट करना उपयोगकर्ता के लिए आसान है और उन बदलावों की भी अनुमति देता है जो कॉन्फ़िगरेशन परिवर्तनों के साथ संभव नहीं हैं।

जेटपैक कम्पोज़

प्लगइन्स को जेटपैक कंपोज़ का उपयोग करके कार्यान्वित किया जा सकता है, लेकिन यह एक अल्फा-स्तरीय सुविधा है और इसे स्थिर नहीं माना जाना चाहिए।

रेंडर करने के लिए कंपोज़-सक्षम सतह बनाने के लिए प्लगइन्स ComposeView उपयोग कर सकते हैं। यह ComposeView वह होगा जो घटकों में getView विधि से ऐप पर लौटाया जाता है।

ComposeView का उपयोग करने में एक प्रमुख मुद्दा यह है कि यह वैश्विक चर को संग्रहीत करने के लिए लेआउट में रूट व्यू पर टैग सेट करता है जो पदानुक्रम में विभिन्न ComposeViews में साझा किए जाते हैं। चूंकि प्लगइन की संसाधन आईडी को ऐप से अलग से नामित नहीं किया गया है, इसलिए जब ऐप और प्लगइन दोनों एक ही दृश्य पर टैग सेट करते हैं तो इससे टकराव हो सकता है। एक कस्टम ComposeViewWithLifecycle जो इन वैश्विक चरों को ComposeView में ले जाता है, नीचे दिया गया है। फिर, इसे स्थिर नहीं माना जाना चाहिए।

ComposeViewWithLifecycle :

class ComposeViewWithLifecycle @JvmOverloads constructor(
  context: Context,
  attrs: AttributeSet? = null,
  defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr),
    LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {

  private val lifeCycle = LifecycleRegistry(this)
  private val modelStore = ViewModelStore()
  private val savedStateRegistryController = SavedStateRegistryController.create(this)
  private var composeView: ComposeView? = null
  private var content = @Composable {}

  init {
    ViewTreeLifecycleOwner.set(this, this)
    ViewTreeViewModelStoreOwner.set(this, this)
    ViewTreeSavedStateRegistryOwner.set(this, this)
    compositionContext = createCompositionContext()
  }

  fun setContent(content: @Composable () -> Unit) {
    this.content = content
    composeView?.setContent(content)
  }

  override fun getLifecycle(): Lifecycle {
    return lifeCycle
  }

  override fun getViewModelStore(): ViewModelStore {
    return modelStore
  }

  override fun getSavedStateRegistry(): SavedStateRegistry {
    return savedStateRegistryController.savedStateRegistry
  }

  override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    savedStateRegistryController.performRestore(Bundle())
    lifeCycle.currentState = Lifecycle.State.RESUMED
    composeView = ComposeView(context)
    composeView?.setContent(content)
    addView(composeView, LayoutParams(
      LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT))
  }

  override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    lifeCycle.currentState = Lifecycle.State.DESTROYED
    modelStore.clear()
    removeAllViews()
    composeView = null
  }

  // Exact copy of View.createCompositionContext() in androidx's WindowRecomposer.android.kt
  private fun createCompositionContext(): CompositionContext {
    val currentThreadContext = AndroidUiDispatcher.CurrentThread
    val pausableClock = currentThreadContext[MonotonicFrameClock]?.let {
      PausableMonotonicFrameClock(it).apply { pause() }
    }
    val contextWithClock = currentThreadContext + (pausableClock ?: EmptyCoroutineContext)
    val recomposer = Recomposer(contextWithClock)
    val runRecomposeScope = CoroutineScope(contextWithClock)
    val viewTreeLifecycleOwner = checkNotNull(ViewTreeLifecycleOwner.get(this)) {
      "ViewTreeLifecycleOwner not found from $this"
    }
    viewTreeLifecycleOwner.lifecycle.addObserver(
      LifecycleEventObserver { _, event ->
        @Suppress("NON_EXHAUSTIVE_WHEN")
        when (event) {
          Lifecycle.Event.ON_CREATE ->
            // Undispatched launch since we've configured this scope
            // to be on the UI thread
            runRecomposeScope.launch(start = CoroutineStart.UNDISPATCHED) {
              recomposer.runRecomposeAndApplyChanges()
            }
          Lifecycle.Event.ON_START -> pausableClock?.resume()
          Lifecycle.Event.ON_STOP -> pausableClock?.pause()
          Lifecycle.Event.ON_DESTROY -> {
            recomposer.cancel()
          }
        }
      }
    )
    return recomposer
  }

//  TODO: ComposeViewWithLifecycle should handle saving state and other lifecycle things
//  override fun onSaveInstanceState(): Parcelable? {
//    val superState = super.onSaveInstanceState()
//    val bundle = Bundle()
//    savedStateRegistryController.performSave(bundle)
//  }
}