تعبیه فعالیت

تعبیه فعالیت، با تقسیم پنجره وظیفه یک برنامه بین دو فعالیت یا دو نمونه از یک فعالیت، برنامه‌ها را در دستگاه‌های صفحه نمایش بزرگ بهینه می‌کند.

شکل ۱. برنامه تنظیمات به همراه فعالیت‌ها در کنار هم.

اگر برنامه شما از چندین اکتیویتی تشکیل شده است، تعبیه اکتیویتی به شما این امکان را می‌دهد که تجربه کاربری بهتری را در تبلت‌ها، دستگاه‌های تاشو و دستگاه‌های ChromeOS ارائه دهید.

تعبیه فعالیت نیازی به تغییر کد ندارد. شما با ایجاد یک فایل پیکربندی XML یا با فراخوانی‌های API مربوط به Jetpack WindowManager ، نحوه نمایش فعالیت‌های برنامه خود را - در کنار هم یا روی هم - تعیین می‌کنید.

پشتیبانی از صفحه نمایش‌های کوچک به طور خودکار حفظ می‌شود. وقتی برنامه شما روی دستگاهی با صفحه نمایش کوچک است، فعالیت‌ها یکی پس از دیگری روی هم قرار می‌گیرند. در صفحه نمایش‌های بزرگ، فعالیت‌ها در کنار هم نمایش داده می‌شوند. سیستم نحوه نمایش را بر اساس پیکربندی ایجاد شده توسط شما تعیین می‌کند - نیازی به منطق شاخه‌بندی نیست.

تعبیه فعالیت، تغییرات جهت‌گیری دستگاه را در خود جای می‌دهد و به طور یکپارچه روی دستگاه‌های تاشو کار می‌کند و همزمان با تا شدن و باز شدن دستگاه، فعالیت‌ها را روی هم قرار می‌دهد و از هم جدا می‌کند.

تعبیه فعالیت در اکثر دستگاه‌های صفحه بزرگ که اندروید ۱۲L (سطح API 32) و بالاتر را اجرا می‌کنند، پشتیبانی می‌شود.

پنجره تقسیم وظایف

تعبیه فعالیت، پنجره وظیفه برنامه را به دو کانتینر تقسیم می‌کند: اصلی و فرعی. کانتینرها، فعالیت‌هایی را که از فعالیت اصلی یا از سایر فعالیت‌های موجود در کانتینرها اجرا می‌شوند، نگه می‌دارند.

فعالیت‌ها هنگام اجرا در کانتینر ثانویه انباشته می‌شوند و کانتینر ثانویه در صفحه نمایش‌های کوچک روی کانتینر اصلی قرار می‌گیرد، بنابراین چیدمان فعالیت‌ها و پیمایش به عقب با ترتیب فعالیت‌های از پیش ساخته شده در برنامه شما سازگار است.

تعبیه فعالیت به شما امکان می‌دهد فعالیت‌ها را به روش‌های مختلفی نمایش دهید. برنامه شما می‌تواند با اجرای همزمان دو فعالیت در کنار هم یا یکی بالای دیگری، پنجره وظیفه را تقسیم کند:

شکل ۲. دو فعالیت در کنار هم و یکی بالای دیگری.

فعالیتی که کل پنجره وظیفه را اشغال می‌کند می‌تواند با راه‌اندازی یک فعالیت جدید در کنار آن، یک تقسیم ایجاد کند:

شکل ۳. فعالیت الف، فعالیت ب را در کنار آن آغاز می‌کند.

فعالیت‌هایی که از قبل در یک پنجره تقسیم‌شده و مشترک قرار دارند، می‌توانند فعالیت‌های دیگری را به روش‌های زیر اجرا کنند:

  • در کنار فعالیت دیگری:

    شکل ۴. فعالیت A، فعالیت C را در کنار فعالیت B آغاز می‌کند.
  • به کنار، و شکاف را به پهلو حرکت دهید، و فعالیت اصلی قبلی را پنهان کنید:

    شکل ۵. فعالیت B، فعالیت C را به کنار شروع می‌کند و شکاف را به پهلو منتقل می‌کند.
  • یک اکتیویتی را در بالا اجرا کنید؛ یعنی، در همان پشته اکتیویتی:

    شکل ۶. فعالیت B، فعالیت C را بدون هیچ پرچم اضافیِ intent آغاز می‌کند.
  • اجرای یک پنجره کامل فعالیت در همان وظیفه:

    شکل ۷. فعالیت A یا فعالیت B، فعالیت C را آغاز می‌کند که پنجره وظیفه را پر می‌کند.

ناوبری برگشت

انواع مختلف برنامه‌ها می‌توانند بسته به وابستگی‌های بین فعالیت‌ها یا نحوه‌ی فعال‌سازی رویداد برگشت توسط کاربران، قوانین ناوبری برگشت متفاوتی در حالت پنجره‌ی وظایف مجزا داشته باشند، برای مثال:

  • هماهنگی: اگر فعالیت‌ها مرتبط باشند و یکی بدون دیگری نمایش داده نشود، می‌توان پیمایش به عقب را طوری پیکربندی کرد که هر دو را به پایان برساند.
  • به تنهایی عمل کردن: اگر فعالیت‌ها کاملاً مستقل باشند، پیمایش به عقب در یک فعالیت، وضعیت فعالیت دیگر در پنجره وظیفه را تحت تأثیر قرار نمی‌دهد.

هنگام استفاده از پیمایش دکمه، رویداد back به آخرین فعالیت متمرکز ارسال می‌شود.

برای ناوبری مبتنی بر ژست:

  • اندروید ۱۴ (سطح API ۳۴) و پایین‌تر - رویداد back به اکتیویتی‌ای که حرکت در آن رخ داده است ارسال می‌شود. وقتی کاربران از سمت چپ صفحه نمایش سوایپ می‌کنند، رویداد back به اکتیویتی در پنل سمت چپ پنجره تقسیم‌شده ارسال می‌شود. وقتی کاربران از سمت راست صفحه نمایش سوایپ می‌کنند، رویداد back به اکتیویتی در پنل سمت راست ارسال می‌شود.

  • اندروید ۱۵ (سطح API 35) و بالاتر

    • هنگام کار با چندین فعالیت از یک برنامه، این ژست حرکتی صرف نظر از جهت کشیدن انگشت، فعالیت برتر را به پایان می‌رساند و تجربه یکپارچه‌تری را ارائه می‌دهد.

    • در سناریوهایی که شامل دو اکتیویتی از برنامه‌های مختلف (overlay) هستند، رویداد back به آخرین اکتیویتی که در فوکوس است هدایت می‌شود و با رفتار ناوبری دکمه‌ها هماهنگ می‌شود.

طرح‌بندی چند قسمتی

Jetpack WindowManager شما را قادر می‌سازد تا یک طرح‌بندی چندبخشی با قابلیت تعبیه فعالیت در دستگاه‌های صفحه نمایش بزرگ با اندروید ۱۲L (سطح API ۳۲) یا بالاتر و در برخی از دستگاه‌های دارای نسخه‌های پلتفرم قبلی بسازید. برنامه‌های موجودی که مبتنی بر چندین فعالیت هستند و نه قطعات یا طرح‌بندی‌های مبتنی بر نما مانند SlidingPaneLayout می‌توانند بدون تغییر کد منبع، تجربه کاربری بهبود یافته‌ای را برای صفحه نمایش بزرگ ارائه دهند.

یک مثال رایج، تفکیک لیست-جزئیات است. برای اطمینان از ارائه با کیفیت بالا، سیستم فعالیت لیست را شروع می‌کند و سپس برنامه بلافاصله فعالیت جزئیات را شروع می‌کند. سیستم انتقال منتظر می‌ماند تا هر دو فعالیت ترسیم شوند، سپس آنها را با هم نمایش می‌دهد. برای کاربر، دو فعالیت به صورت یکجا اجرا می‌شوند.

شکل ۸. دو فعالیت به طور همزمان در یک طرح چند قسمتی آغاز شدند.

ویژگی‌های تقسیم‌شده

می‌توانید مشخص کنید که پنجره‌ی وظیفه چگونه بین کانتینرهای تقسیم‌شده تناسب داشته باشد و کانتینرها نسبت به یکدیگر چگونه چیدمان شوند.

برای قوانین تعریف شده در یک فایل پیکربندی XML، ویژگی‌های زیر را تنظیم کنید:

  • splitRatio : نسبت‌های ظرف را تنظیم می‌کند. مقدار یک عدد اعشاری در بازه باز (0.0، 1.0) است.
  • splitLayoutDirection : نحوه چیدمان کانتینرهای تقسیم‌شده نسبت به یکدیگر را مشخص می‌کند. مقادیر شامل موارد زیر است:
    • ltr : چپ به راست
    • rtl : راست به چپ
    • locale : یا ltr یا rtl از تنظیمات locale تعیین می‌شود.

برای مثال‌ها به بخش پیکربندی XML مراجعه کنید.

برای قوانینی که با استفاده از APIهای WindowManager ایجاد شده‌اند، یک شیء SplitAttributes با SplitAttributes.Builder ایجاد کنید و متدهای سازنده زیر را فراخوانی کنید:

  • setSplitType() : نسبت‌های ظروف تقسیم‌شده را تنظیم می‌کند. برای آرگومان‌های معتبر، از جمله متد SplitAttributes.SplitType.ratio() ، به SplitAttributes.SplitType مراجعه کنید.
  • setLayoutDirection() : طرح‌بندی کانتینرها را تنظیم می‌کند. برای مقادیر ممکن به SplitAttributes.LayoutDirection مراجعه کنید.

برای مثال‌ها به بخش API مربوط به WindowManager مراجعه کنید.

شکل ۹. دو تقسیم‌بندی فعالیت از چپ به راست اما با نسبت‌های تقسیم متفاوت.

جهت‌گیری تقسیم‌شده

ابعاد و نسبت ابعاد نمایشگر، موقعیت فعالیت‌ها را در تقسیم‌بندی‌های تعبیه‌شده برای فعالیت‌ها تعیین می‌کند. در نمایشگرهای بزرگ افقی، فعالیت‌ها در کنار هم نمایش داده می‌شوند؛ در نمایشگرهای عمودی بلند یا حالت رومیزی روی میزهای تاشو، یکی بالای دیگری قرار می‌گیرد.

شما می‌توانید جهت تقسیم را با استفاده از ماشین حساب SplitController SplitAttributes مشخص کنید. این ماشین حساب، SplitAttributes برای SplitRule فعال محاسبه می‌کند.

از ماشین حساب برای تقسیم کانتینر والد در جهات مختلف برای حالت‌های مختلف دستگاه استفاده کنید، برای مثال:

کاتلین

if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator { params ->
        val parentConfiguration = params.parentConfiguration
        val builder = SplitAttributes.Builder()
        return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build()
        } else if (parentConfiguration.screenHeightDp >= 600) {
            // Horizontal split for tall displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
                .build()
        } else {
            // Fallback to expand the secondary container.
            builder
                .setSplitType(SPLIT_TYPE_EXPAND)
                .build()
        }
    }
}

جاوا

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
        Configuration parentConfiguration = params.getParentConfiguration();
        SplitAttributes.Builder builder = new SplitAttributes.Builder();
        if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build();
        } else if (parentConfiguration.screenHeightDp >= 600) {
            // Horizontal split for tall displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
                .build();
        } else {
            // Fallback to expand the secondary container.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
                .build();
        }
    });
}

در دستگاه‌های تاشو، اگر دستگاه افقی باشد، می‌توانید صفحه را به صورت عمودی تقسیم کنید، اگر دستگاه عمودی باشد، یک فعالیت واحد را نمایش دهید و اگر دستگاه در حالت رومیزی باشد، صفحه را به صورت افقی تقسیم کنید:

کاتلین

if (WindowSdkExtensions.getInstance().extensionVersion >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator { params ->
        val tag = params.splitRuleTag
        val parentWindowMetrics = params.parentWindowMetrics
        val parentConfiguration = params.parentConfiguration
        val foldingFeatures =
            params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>()
        val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null
        val builder = SplitAttributes.Builder()
        builder.setSplitType(SPLIT_TYPE_HINGE)
        return@setSplitAttributesCalculator if (feature?.isSeparating == true) {
            // Horizontal split for tabletop posture.
            builder
                .setSplitType(SPLIT_TYPE_HINGE)
                .setLayoutDirection(
                    if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) {
                        SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
                    } else {
                        SplitAttributes.LayoutDirection.LOCALE
                    }
                )
                .build()
        } else if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build()
        } else {
            // No split for tall displays.
            builder
                .setSplitType(SPLIT_TYPE_EXPAND)
                .build()
        }
    }
}

جاوا

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) {
    SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
        String tag = params.getSplitRuleTag();
        WindowMetrics parentWindowMetrics = params.getParentWindowMetrics();
        Configuration parentConfiguration = params.getParentConfiguration();
        List<FoldingFeature> foldingFeatures =
            params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter(
                    item -> item instanceof FoldingFeature)
                .map(item -> (FoldingFeature) item)
                .collect(Collectors.toList());
        FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null;
        SplitAttributes.Builder builder = new SplitAttributes.Builder();
        builder.setSplitType(SplitType.SPLIT_TYPE_HINGE);
        if (feature != null && feature.isSeparating()) {
            // Horizontal slit for tabletop posture.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_HINGE)
                .setLayoutDirection(
                    feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL
                        ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP
                        : SplitAttributes.LayoutDirection.LOCALE)
                .build();
        }
        else if (parentConfiguration.screenWidthDp >= 840) {
            // Side-by-side dual-pane layout for wide displays.
            return builder
                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
                .build();
        } else {
            // No split for tall displays.
            return builder
                .setSplitType(SplitType.SPLIT_TYPE_EXPAND)
                .build();
        }
    });
}

متغیرهایی

فعالیت‌های جای‌نگهدار، فعالیت‌های ثانویه خالی هستند که بخشی از یک تقسیم‌بندی فعالیت را اشغال می‌کنند. در نهایت قرار است با فعالیت دیگری که حاوی محتوا است جایگزین شوند. برای مثال، یک فعالیت جای‌نگهدار می‌تواند سمت ثانویه یک تقسیم‌بندی فعالیت را در یک طرح‌بندی لیست-جزئیات اشغال کند تا زمانی که یک آیتم از لیست انتخاب شود، در آن نقطه یک فعالیت حاوی اطلاعات جزئی برای آیتم لیست انتخاب شده جایگزین جای‌نگهدار می‌شود.

به طور پیش‌فرض، سیستم فقط زمانی که فضای کافی برای تقسیم فعالیت وجود داشته باشد، متغیرهایی را نمایش می‌دهد. متغیرهایی که به طور خودکار زمانی که اندازه نمایش به عرض یا ارتفاعی بسیار کوچک برای نمایش تقسیم تغییر می‌کند، پایان می‌یابند. وقتی فضا اجازه دهد، سیستم متغیر را با حالت مقداردهی اولیه مجدد راه‌اندازی می‌کند.

شکل ۱۰. دستگاه تاشو در حال تا شدن و باز شدن. فعالیت Placeholder با تغییر اندازه نمایشگر، تمام شده و دوباره ایجاد می‌شود.

با این حال، ویژگی stickyPlaceholder از SplitPlaceholderRule یا متد setSticky() از SplitPlaceholder.Builder می‌تواند رفتار پیش‌فرض را لغو کند. هنگامی که ویژگی یا متد مقدار true را مشخص می‌کند، سیستم، زمانی که صفحه نمایش از حالت دو قسمتی به حالت تک قسمتی تغییر اندازه می‌دهد، placeholder را به عنوان بالاترین فعالیت در پنجره وظیفه نمایش می‌دهد (برای مثال به پیکربندی Split مراجعه کنید).

شکل ۱۱. دستگاه تاشو در حال تا شدن و باز شدن. فعالیت نگهدارنده مکان چسبنده است.

تغییر اندازه پنجره

وقتی تغییرات پیکربندی دستگاه، عرض پنجره وظیفه را کاهش می‌دهد، به طوری که برای طرح‌بندی چند پنجره‌ای به اندازه کافی بزرگ نباشد (برای مثال، وقتی یک دستگاه تاشو با صفحه نمایش بزرگ از اندازه تبلت به اندازه تلفن تا می‌شود یا اندازه پنجره برنامه در حالت چند پنجره‌ای تغییر می‌کند)، فعالیت‌های غیرمتصل به مکان در پنجره ثانویه پنجره وظیفه، روی فعالیت‌های پنجره اصلی انباشته می‌شوند.

فعالیت‌های جای‌نگهدار فقط زمانی نمایش داده می‌شوند که عرض نمایشگر برای تقسیم‌بندی کافی باشد. در صفحه نمایش‌های کوچک‌تر، جای‌نگهدار به‌طور خودکار حذف می‌شود. وقتی ناحیه نمایش دوباره به اندازه کافی بزرگ شود، جای‌نگهدار دوباره ایجاد می‌شود. (به بخش جای‌نگهدارها مراجعه کنید.)

انباشت فعالیت‌ها امکان‌پذیر است زیرا WindowManager فعالیت‌های پنل ثانویه را بالای فعالیت‌های پنل اصلی به صورت z-order مرتب می‌کند.

چندین فعالیت در صفحه ثانویه

فعالیت B، فعالیت C را در جای خود و بدون هیچ پرچم intent اضافی آغاز می‌کند:

تقسیم فعالیت شامل فعالیت‌های A، B و C است که C روی B قرار گرفته است.

که منجر به ترتیب z زیر از فعالیت‌ها در همان وظیفه می‌شود:

پشته فعالیت ثانویه حاوی فعالیت C که روی B قرار گرفته است. پشته ثانویه روی پشته فعالیت اولیه حاوی فعالیت A قرار گرفته است.

بنابراین، در یک پنجره وظیفه کوچک‌تر، برنامه به یک فعالیت واحد با C در بالای پشته خلاصه می‌شود:

پنجره کوچکی که فقط فعالیت C را نشان می‌دهد.

حرکت به عقب در پنجره کوچکتر، از میان فعالیت‌هایی که روی هم انباشته شده‌اند، حرکت می‌کند.

اگر پیکربندی پنجره وظیفه به اندازه بزرگتری که بتواند چندین پنل را در خود جای دهد، بازیابی شود، فعالیت‌ها دوباره در کنار هم نمایش داده می‌شوند.

تقسیم‌بندی‌های انباشته

فعالیت B، فعالیت C را به یک طرف شروع می‌کند و شکاف را به یک طرف منتقل می‌کند:

پنجره‌ی وظایف، فعالیت‌های A و B و سپس فعالیت‌های B و C را نشان می‌دهد.

نتیجه، ترتیب z زیر از فعالیت‌ها در یک وظیفه مشابه است:

فعالیت‌های A، B و C در یک دسته. فعالیت‌ها به ترتیب زیر از بالا به پایین روی هم قرار می‌گیرند: C، B، A.

در یک پنجره وظیفه کوچک‌تر، برنامه به یک فعالیت واحد با C در بالا خلاصه می‌شود:

پنجره کوچکی که فقط فعالیت C را نشان می‌دهد.

جهت گیری عمودی ثابت

تنظیمات مانیفست android:screenOrientation به برنامه‌ها این امکان را می‌دهد که فعالیت‌ها را به جهت عمودی یا افقی محدود کنند. برای بهبود تجربه کاربری در دستگاه‌های با صفحه نمایش بزرگ مانند تبلت‌ها و دستگاه‌های تاشو، تولیدکنندگان دستگاه (OEMها) می‌توانند درخواست‌های جهت‌گیری صفحه نمایش را نادیده بگیرند و برنامه را در جهت عمودی در نمایشگرهای افقی یا در جهت افقی در نمایشگرهای عمودی در کادر حروف قرار دهند.

شکل ۱۲. فعالیت‌های دارای جعبه حروف: حالت عمودی ثابت روی دستگاه افقی (چپ)، حالت افقی ثابت روی دستگاه عمودی (راست).

به طور مشابه، وقتی تعبیه فعالیت فعال باشد، تولیدکنندگان اصلی تجهیزات (OEM) می‌توانند دستگاه‌ها را طوری سفارشی کنند که فعالیت‌های با تصویر ثابت را در جهت افقی روی صفحه نمایش‌های بزرگ (عرض ≥ ۶۰۰dp) به صورت Letterbox نمایش دهند. وقتی یک فعالیت با تصویر ثابت، فعالیت دوم را اجرا می‌کند، دستگاه می‌تواند دو فعالیت را در کنار هم در یک صفحه نمایش دو قسمتی نمایش دهد.

شکل ۱۳. فعالیت پرتره ثابت A، فعالیت B را در کنار آن آغاز می‌کند.

همیشه ویژگی android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED را به فایل مانیفست برنامه خود اضافه کنید تا به دستگاه‌ها اطلاع دهید که برنامه شما از تعبیه فعالیت پشتیبانی می‌کند (به بخش پیکربندی Split مراجعه کنید). سپس دستگاه‌های سفارشی‌شده توسط OEM می‌توانند تعیین کنند که آیا فعالیت‌های با پرتره ثابت را به صورت Letterbox نمایش دهند یا خیر.

پیکربندی تقسیم شده

قوانین تقسیم، تقسیم‌بندی فعالیت‌ها را پیکربندی می‌کنند. شما می‌توانید قوانین تقسیم‌بندی را در یک فایل پیکربندی XML یا با فراخوانی‌های API مربوط به Jetpack WindowManager تعریف کنید.

در هر صورت، برنامه شما باید به کتابخانه WindowManager دسترسی داشته باشد و به سیستم اطلاع دهد که برنامه، تعبیه فعالیت (activity embedding) را پیاده‌سازی کرده است.

موارد زیر را انجام دهید:

  1. آخرین وابستگی کتابخانه WindowManager را به فایل build.gradle در سطح ماژول برنامه خود اضافه کنید، برای مثال:

    implementation 'androidx.window:window:1.1.0-beta02'

    کتابخانه WindowManager تمام اجزای مورد نیاز برای تعبیه فعالیت را فراهم می‌کند.

  2. به سیستم اطلاع دهید که برنامه شما قابلیت تعبیه فعالیت (activity embedding) را پیاده‌سازی کرده است.

    ویژگی android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED را به عنصر <application> در فایل manifest برنامه اضافه کنید و مقدار آن را روی true تنظیم کنید، برای مثال:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    در نسخه ۱.۱.۰-alpha06 و بالاتر از WindowManager، تقسیم‌بندی‌های تعبیه فعالیت غیرفعال هستند، مگر اینکه این ویژگی به مانیفست اضافه شده و روی true تنظیم شود.

    همچنین، تولیدکنندگان دستگاه از این تنظیم برای فعال کردن قابلیت‌های سفارشی برای برنامه‌هایی که از تعبیه فعالیت پشتیبانی می‌کنند، استفاده می‌کنند. برای مثال، دستگاه‌ها می‌توانند یک فعالیت فقط عمودی را در نمایشگرهای افقی به صورت جعبه‌ای قرار دهند تا هنگام شروع فعالیت دوم، فعالیت را برای انتقال به یک طرح دو قسمتی جهت‌دهی کنند ( به جهت‌گیری ثابت عمودی مراجعه کنید).

پیکربندی XML

برای ایجاد یک پیاده‌سازی مبتنی بر XML از تعبیه فعالیت، مراحل زیر را انجام دهید:

  1. یک فایل منبع XML ایجاد کنید که موارد زیر را انجام دهد:

    • فعالیت‌هایی را تعریف می‌کند که سهم مشترکی دارند
    • گزینه‌های تقسیم را پیکربندی می‌کند
    • وقتی محتوا در دسترس نباشد، یک جای‌نگهدار برای ظرف ثانویه‌ی تقسیم‌شده ایجاد می‌کند
    • فعالیت‌هایی را مشخص می‌کند که هرگز نباید بخشی از یک تقسیم‌بندی باشند

    برای مثال:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. یک مقداردهی اولیه ایجاد کنید.

    کامپوننت WindowManager RuleController فایل پیکربندی XML را تجزیه و تحلیل می‌کند و قوانین را در دسترس سیستم قرار می‌دهد. یک کتابخانه‌ی راه‌اندازی Jetpack Initializer فایل XML را در هنگام راه‌اندازی برنامه در دسترس RuleController قرار می‌دهد تا قوانین با شروع هر فعالیتی اعمال شوند.

    برای ایجاد یک مقدار اولیه، موارد زیر را انجام دهید:

    1. آخرین وابستگی کتابخانه Jetpack Startup را به فایل build.gradle در سطح ماژول خود اضافه کنید، برای مثال:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. یک کلاس ایجاد کنید که رابط Initializer را پیاده‌سازی کند.

      مقداردهنده‌ی اولیه با ارسال شناسه‌ی فایل پیکربندی XML ( main_split_config.xml ) به متد RuleController.parseRules() قوانین تقسیم‌شده را در دسترس RuleController قرار می‌دهد.

      کاتلین

      class SplitInitializer : Initializer<RuleController> {
      
          override fun create(context: Context): RuleController {
              return RuleController.getInstance(context).apply {
                  setRules(RuleController.parseRules(context, R.xml.main_split_config))
              }
          }
      
          override fun dependencies(): List<Class<out Initializer<*>>> {
              return emptyList()
          }
      }

      جاوا

      public class SplitInitializer implements Initializer<RuleController> {
      
          @NonNull
          @Override
          public RuleController create(@NonNull Context context) {
              RuleController ruleController = RuleController.getInstance(context);
              ruleController.setRules(
                  RuleController.parseRules(context, R.xml.main_split_config)
              );
               return ruleController;
           }
      
           @NonNull
           @Override
           public List<Class<? extends Initializer<?>>> dependencies() {
               return Collections.emptyList();
           }
      }

  3. یک ارائه دهنده محتوا برای تعاریف قانون ایجاد کنید.

    androidx.startup.InitializationProvider به عنوان یک <provider> به فایل manifest برنامه خود اضافه کنید. همچنین یک ارجاع به پیاده‌سازی مقداردهنده RuleController خود، SplitInitializer ، اضافه کنید:

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    InitializationProvider قبل از فراخوانی متد onCreate() برنامه، SplitInitializer کشف و مقداردهی اولیه می‌کند. در نتیجه، قوانین تقسیم‌بندی هنگام شروع فعالیت اصلی برنامه اعمال می‌شوند.

رابط برنامه‌نویسی کاربردی (API) مدیر پنجره

شما می‌توانید تعبیه فعالیت را به صورت برنامه‌نویسی شده با تعدادی فراخوانی API پیاده‌سازی کنید. فراخوانی‌ها را در متد onCreate() از یک زیرکلاس از Application انجام دهید تا مطمئن شوید که قوانین قبل از اجرای هرگونه فعالیتی اعمال می‌شوند.

برای ایجاد تقسیم‌بندی فعالیت‌ها به صورت برنامه‌نویسی، موارد زیر را انجام دهید:

  1. یک قانون تقسیم ایجاد کنید:

    1. یک SplitPairFilter ایجاد کنید که فعالیت‌هایی را که تقسیم را به اشتراک می‌گذارند، شناسایی کند:

      کاتلین

      val splitPairFilter = SplitPairFilter(
          ComponentName(this, ListActivity::class.java),
          ComponentName(this, DetailActivity::class.java),
          null
      )

      جاوا

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );

    2. فیلتر را به یک مجموعه فیلتر اضافه کنید:

      کاتلین

      val filterSet = setOf(splitPairFilter)

      جاوا

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
      ```

    3. ایجاد ویژگی‌های طرح‌بندی برای تقسیم‌بندی:

      کاتلین

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()

      جاوا

      SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder یک شیء حاوی ویژگی‌های طرح‌بندی ایجاد می‌کند:

      • setSplitType() : نحوه تخصیص ناحیه نمایش موجود به هر کانتینر فعالیت را تعریف می‌کند. نوع تقسیم نسبت، نسبت ناحیه نمایش موجود اختصاص داده شده به کانتینر اصلی را مشخص می‌کند؛ کانتینر ثانویه بقیه ناحیه نمایش موجود را اشغال می‌کند.
      • setLayoutDirection() : نحوه چیدمان کانتینرهای اکتیویتی نسبت به یکدیگر را مشخص می‌کند، به طوری که کانتینر اصلی در اولویت باشد.
    4. ساخت یک SplitPairRule :

      کاتلین

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()

      جاوا

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();

      SplitPairRule.Builder قانون را ایجاد و پیکربندی می‌کند:

      • filterSet : شامل فیلترهای جفت تقسیم‌شده است که با شناسایی فعالیت‌هایی که یک تقسیم‌بندی مشترک دارند، تعیین می‌کنند که چه زمانی قانون اعمال شود.
      • setDefaultSplitAttributes() : ویژگی‌های طرح‌بندی را به قانون اعمال می‌کند.
      • setMinWidthDp() : حداقل عرض نمایش (برحسب پیکسل‌های مستقل از چگالی، dp) را که امکان تقسیم را فراهم می‌کند، تنظیم می‌کند.
      • setMinSmallestWidthDp() حداقل مقداری (برحسب dp) را که کوچکترین بُعد از بین دو بُعد نمایشگر باید داشته باشد تا امکان تقسیم‌بندی صرف نظر از جهت‌گیری دستگاه فراهم شود، تعیین می‌کند.
      • setMaxAspectRatioInPortrait() : حداکثر نسبت ابعاد نمایش (ارتفاع:عرض) را در جهت عمودی که تقسیم‌بندی‌های فعالیت برای آن نمایش داده می‌شوند، تنظیم می‌کند. اگر نسبت ابعاد یک نمایش عمودی از حداکثر نسبت ابعاد بیشتر شود، تقسیم‌بندی‌ها صرف نظر از عرض صفحه نمایش غیرفعال می‌شوند. توجه: مقدار پیش‌فرض ۱.۴ است که منجر به اشغال کل پنجره وظیفه در جهت عمودی در اکثر تبلت‌ها توسط فعالیت‌ها می‌شود. همچنین به SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT و setMaxAspectRatioInLandscape() مراجعه کنید. مقدار پیش‌فرض برای افقی ALWAYS_ALLOW است.
      • setFinishPrimaryWithSecondary() : تعیین می‌کند که اتمام تمام فعالیت‌های کانتینر ثانویه چگونه بر فعالیت‌های کانتینر اصلی تأثیر می‌گذارد. NEVER نشان می‌دهد که سیستم نباید فعالیت‌های اصلی را پس از اتمام تمام فعالیت‌های کانتینر ثانویه به پایان برساند (به Finish activities مراجعه کنید).
      • setFinishSecondaryWithPrimary() : تعیین می‌کند که چگونه اتمام تمام فعالیت‌های کانتینر اصلی، فعالیت‌های کانتینر ثانویه را تحت تأثیر قرار می‌دهد. ALWAYS نشان می‌دهد که سیستم باید همیشه فعالیت‌های کانتینر ثانویه را زمانی که تمام فعالیت‌های کانتینر اصلی به پایان می‌رسند، به پایان برساند (به Finish activities مراجعه کنید).
      • setClearTop() : مشخص می‌کند که آیا با راه‌اندازی یک فعالیت جدید در کانتینر، تمام فعالیت‌های موجود در کانتینر ثانویه به پایان رسیده‌اند یا خیر. مقدار false مشخص می‌کند که فعالیت‌های جدید روی فعالیت‌های موجود در کانتینر ثانویه قرار می‌گیرند.
    5. نمونه‌ی تک‌لایه از WindowManager RuleController را دریافت کنید و قانون زیر را به آن اضافه کنید:

      کاتلین

      val ruleController = RuleController.getInstance(this)
      ruleController.addRule(splitPairRule)

      جاوا

      RuleController ruleController = RuleController.getInstance(this);
      ruleController.addRule(splitPairRule);

    6. وقتی محتوا در دسترس نیست، یک placeholder برای کانتینر ثانویه ایجاد کنید:

    7. یک ActivityFilter ایجاد کنید که فعالیتی را که placeholder با آن تقسیم پنجره وظیفه را به اشتراک می‌گذارد، شناسایی کند:

      کاتلین

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      جاوا

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );

    8. فیلتر را به یک مجموعه فیلتر اضافه کنید:

      کاتلین

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      جاوا

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);

    9. یک SplitPlaceholderRule ایجاد کنید:

      کاتلین

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
          placeholderActivityFilterSet,
          Intent(context, PlaceholderActivity::class.java)
      ).setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
          .setSticky(false)
          .build()

      جاوا

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(this, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      SplitPlaceholderRule.Builder قانون را ایجاد و پیکربندی می‌کند:

      • placeholderActivityFilterSet : شامل فیلترهای فعالیت است که با شناسایی فعالیت‌هایی که فعالیت placeholder با آنها مرتبط است، زمان اعمال قانون را تعیین می‌کنند.
      • Intent : زمان شروع فعالیت placeholder را مشخص می‌کند.
      • setDefaultSplitAttributes() : ویژگی‌های طرح‌بندی را به قانون اعمال می‌کند.
      • setMinWidthDp() : حداقل عرض نمایش (برحسب پیکسل‌های مستقل از چگالی، dp) را که امکان تقسیم را فراهم می‌کند، تنظیم می‌کند.
      • setMinSmallestWidthDp() حداقل مقداری (برحسب dp) را تعیین می‌کند که کوچکترین بُعد از بین دو بُعد نمایشگر باید داشته باشد تا امکان تقسیم‌بندی صرف نظر از جهت‌گیری دستگاه فراهم شود.
      • setMaxAspectRatioInPortrait() : حداکثر نسبت ابعاد نمایش (ارتفاع:عرض) را در جهت عمودی تنظیم می‌کند که برای آن تقسیم‌بندی‌های فعالیت نمایش داده می‌شوند. توجه: مقدار پیش‌فرض ۱.۴ است که منجر به پر شدن پنجره وظیفه در جهت عمودی در اکثر تبلت‌ها توسط فعالیت‌ها می‌شود. همچنین به SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT و setMaxAspectRatioInLandscape() مراجعه کنید. مقدار پیش‌فرض برای افقی ALWAYS_ALLOW است.
      • setFinishPrimaryWithPlaceholder() : تعیین می‌کند که پایان دادن به فعالیت نگهدارنده‌ی مکان، چگونه بر فعالیت‌های موجود در ظرف اصلی تأثیر می‌گذارد. ALWAYS نشان می‌دهد که سیستم باید همیشه فعالیت‌های موجود در ظرف اصلی را پس از پایان یافتن نگهدارنده‌ی مکان، به پایان برساند (به Finish activities مراجعه کنید).
      • setSticky() : تعیین می‌کند که آیا فعالیت مربوط به placeholder، پس از اینکه placeholder برای اولین بار به صورت تقسیم‌شده با حداقل عرض کافی ظاهر شد، در نمایشگرهای کوچک در بالای پشته فعالیت ظاهر می‌شود یا خیر.
    10. این قانون را به WindowManager RuleController اضافه کنید:

      کاتلین

      ruleController.addRule(splitPlaceholderRule)

      جاوا

      ruleController.addRule(splitPlaceholderRule);

  2. فعالیت‌هایی را مشخص کنید که هرگز نباید بخشی از یک تقسیم‌بندی باشند:

    1. یک ActivityFilter ایجاد کنید که فعالیتی را شناسایی کند که همیشه باید کل ناحیه نمایش وظیفه را اشغال کند:

      کاتلین

      val expandedActivityFilter = ActivityFilter(
          ComponentName(this, ExpandedActivity::class.java),
          null
      )

      جاوا

      ActivityFilter expandedActivityFilter = new ActivityFilter(
          new ComponentName(this, ExpandedActivity.class),
          null
      );

    2. فیلتر را به یک مجموعه فیلتر اضافه کنید:

      کاتلین

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      جاوا

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);

    3. ایجاد یک ActivityRule :

      کاتلین

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      جاوا

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      ActivityRule.Builder قانون را ایجاد و پیکربندی می‌کند:

      • expandedActivityFilterSet : شامل فیلترهای فعالیت است که با شناسایی فعالیت‌هایی که می‌خواهید از تقسیم‌بندی‌ها مستثنی شوند، زمان اعمال قانون را تعیین می‌کنند.
      • setAlwaysExpand() : مشخص می‌کند که آیا فعالیت باید کل پنجره وظیفه را پر کند یا خیر.
    4. این قانون را به WindowManager RuleController اضافه کنید:

      کاتلین

      ruleController.addRule(activityRule)

      جاوا

      ruleController.addRule(activityRule);

جاسازی بین برنامه‌های کاربردی

در اندروید ۱۳ (سطح API ۳۳) و بالاتر، برنامه‌ها می‌توانند فعالیت‌ها را از برنامه‌های دیگر جاسازی کنند. جاسازی فعالیت بین برنامه‌هایی یا شناسه رابط کاربری بین برنامه‌هایی، امکان ادغام بصری فعالیت‌ها از چندین برنامه اندروید را فراهم می‌کند. سیستم، فعالیت برنامه میزبان و فعالیت جاسازی شده از برنامه دیگر را درست مانند جاسازی فعالیت تک برنامه‌ای، در کنار هم یا بالا و پایین صفحه نمایش می‌دهد.

برای مثال، برنامه تنظیمات می‌تواند فعالیت انتخاب تصویر زمینه را از برنامه WallpaperPicker جاسازی کند:

شکل ۱۴. برنامه تنظیمات (منو در سمت چپ) به همراه انتخابگر تصویر زمینه به عنوان فعالیت (سمت راست).

مدل اعتماد

فرآیندهای میزبانی که فعالیت‌های برنامه‌های دیگر را جاسازی می‌کنند، قادر به تعریف مجدد نمایش فعالیت‌های جاسازی‌شده، از جمله اندازه، موقعیت، برش و شفافیت هستند. میزبان‌های مخرب می‌توانند از این قابلیت برای گمراه کردن کاربران و ایجاد کلیک‌ربایی یا سایر حملات اصلاح رابط کاربری استفاده کنند.

برای جلوگیری از سوءاستفاده از جاسازی فعالیت‌های بین‌برنامه‌ای، اندروید از برنامه‌ها می‌خواهد که اجازه جاسازی فعالیت‌های خود را بدهند. برنامه‌ها می‌توانند میزبان‌ها را به عنوان قابل اعتماد یا غیرقابل اعتماد تعیین کنند.

میزبان‌های مورد اعتماد

برای اینکه به برنامه‌های دیگر اجازه دهید فعالیت‌های برنامه شما را جاسازی کرده و به طور کامل کنترل کنند، گواهی SHA-256 برنامه میزبان را در ویژگی android:knownActivityEmbeddingCerts از عناصر <activity> یا <application> فایل manifest برنامه خود مشخص کنید.

مقدار android:knownActivityEmbeddingCerts را به صورت رشته تنظیم کنید:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

یا برای مشخص کردن چندین گواهی، آرایه‌ای از رشته‌ها:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

که به منبعی مانند منبع زیر اشاره می‌کند:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

صاحبان برنامه‌ها می‌توانند با اجرای وظیفه Gradle signingReport خلاصه گواهی SHA را دریافت کنند. خلاصه گواهی، اثر انگشت SHA-256 بدون جدا کردن دو نقطه است. برای اطلاعات بیشتر، به Run a signing report و Authenticating Your Client مراجعه کنید.

میزبان‌های غیرقابل اعتماد

برای اینکه به هر برنامه‌ای اجازه دهید فعالیت‌های برنامه شما را جاسازی کند و نحوه نمایش آنها را کنترل کند، ویژگی android:allowUntrustedActivityEmbedding در عناصر <activity> یا <application> در مانیفست برنامه مشخص کنید، برای مثال:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

مقدار پیش‌فرض این ویژگی false است که از جاسازی فعالیت بین برنامه‌های مختلف جلوگیری می‌کند.

احراز هویت سفارشی

برای کاهش خطرات ناشی از جاسازی فعالیت‌های نامعتبر، یک مکانیزم احراز هویت سفارشی ایجاد کنید که هویت میزبان را تأیید کند. اگر گواهی‌های میزبان را می‌دانید، از کتابخانه androidx.security.app.authenticator برای تأیید هویت استفاده کنید. اگر میزبان پس از جاسازی فعالیت شما تأیید هویت شود، می‌توانید محتوای واقعی را نمایش دهید. در غیر این صورت، می‌توانید به کاربر اطلاع دهید که این عمل مجاز نبوده و محتوا را مسدود کنید.

برای بررسی اینکه آیا میزبان، activity شما را جاسازی می‌کند یا خیر، از متد ActivityEmbeddingController#isActivityEmbedded() از کتابخانه Jetpack WindowManager استفاده کنید، برای مثال:

کاتلین

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

جاوا

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(context).isActivityEmbedded(activity);
}

محدودیت حداقل اندازه

سیستم اندروید حداقل ارتفاع و عرض مشخص شده در عنصر <layout> در manifest app را برای activity های تعبیه شده اعمال می کند. اگر برنامه ای حداقل ارتفاع و عرض را مشخص نکند، مقادیر پیش فرض سیستم ( sw220dp ) اعمال می شود.

اگر میزبان سعی کند اندازه کانتینر تعبیه‌شده را به اندازه‌ای کوچک‌تر از حداقل تغییر دهد، کانتینر تعبیه‌شده گسترش می‌یابد تا کل مرزهای وظیفه را اشغال کند.

<فعالیت-نام مستعار>

برای اینکه جاسازی فعالیت قابل اعتماد یا غیرقابل اعتماد با عنصر <activity-alias> کار کند، android:knownActivityEmbeddingCerts یا android:allowUntrustedActivityEmbedding باید به جای نام مستعار، روی فعالیت هدف اعمال شوند. سیاستی که امنیت را روی سرور سیستم تأیید می‌کند، بر اساس پرچم‌های تنظیم شده روی هدف است، نه نام مستعار.

برنامه میزبان

برنامه‌های میزبان، تعبیه فعالیت بین برنامه‌ای را به همان روشی که تعبیه فعالیت تک برنامه‌ای را پیاده‌سازی می‌کنند، پیاده‌سازی می‌کنند. اشیاء SplitPairRule و SplitPairFilter یا ActivityRule و ActivityFilter فعالیت‌های تعبیه‌شده و تقسیم پنجره وظایف را مشخص می‌کنند. قوانین تقسیم به صورت ایستا در XML یا در زمان اجرا با استفاده از فراخوانی‌های API Jetpack WindowManager تعریف می‌شوند.

اگر یک برنامه میزبان سعی کند فعالیتی را که برای جاسازی بین برنامه‌ای انتخاب نشده است، جاسازی کند، آن فعالیت کل محدوده وظایف را اشغال می‌کند. در نتیجه، برنامه‌های میزبان باید بدانند که آیا فعالیت‌های هدف اجازه جاسازی بین برنامه‌ای را می‌دهند یا خیر.

اگر یک اکتیویتیِ تعبیه‌شده، یک اکتیویتی جدید را در همان وظیفه شروع کند و اکتیویتی جدید، تعبیه بین‌برنامه‌ای را انتخاب نکرده باشد، اکتیویتی به جای اینکه روی اکتیویتیِ موجود در کانتینر تعبیه‌شده قرار گیرد، کل محدوده‌ی وظیفه را اشغال می‌کند.

یک برنامه میزبان می‌تواند فعالیت‌های خود را بدون محدودیت جاسازی کند، مادامی که فعالیت‌ها در همان وظیفه اجرا شوند.

نمونه‌های تقسیم‌شده

تقسیم از پنجره کامل

شکل ۱۵. فعالیت الف، فعالیت ب را در کنار آن آغاز می‌کند.

نیازی به تغییر ساختار نیست. می‌توانید پیکربندی مربوط به تقسیم را به صورت ایستا یا در زمان اجرا تعریف کنید و سپس بدون هیچ پارامتر اضافی، Context#startActivity() را فراخوانی کنید.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

تقسیم به صورت پیش‌فرض

وقتی صفحه فرود یک برنامه طوری طراحی شده باشد که در صفحه نمایش‌های بزرگ به دو بخش تقسیم شود، بهترین تجربه کاربری زمانی حاصل می‌شود که هر دو فعالیت به طور همزمان ایجاد و ارائه شوند. با این حال، ممکن است محتوا برای بخش ثانویه بخش تقسیم شده در دسترس نباشد تا زمانی که کاربر با فعالیت در بخش اصلی تعامل داشته باشد (به عنوان مثال، کاربر یک آیتم را از منوی ناوبری انتخاب کند). یک فعالیت جای‌نگهدار می‌تواند این جای خالی را پر کند تا زمانی که محتوا بتواند در بخش ثانویه بخش تقسیم شده نمایش داده شود (به بخش جای‌نگهدارها مراجعه کنید).

شکل ۱۶. تقسیم‌بندی ایجاد شده با باز کردن همزمان دو فعالیت. یکی از فعالیت‌ها به عنوان نگهدارنده‌ی مکان عمل می‌کند.

برای ایجاد یک تقسیم‌بندی با استفاده از یک placeholder، یک placeholder ایجاد کنید و آن را به activity اصلی مرتبط کنید:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

وقتی یک برنامه یک intent دریافت می‌کند، activity هدف می‌تواند به عنوان بخش ثانویه از یک activity split نمایش داده شود؛ برای مثال، درخواستی برای نمایش صفحه جزئیات با اطلاعات مربوط به یک آیتم از یک لیست. در نمایشگرهای کوچک، جزئیات در پنجره وظیفه کامل نشان داده می‌شود؛ در دستگاه‌های بزرگتر، در کنار لیست.

شکل ۱۷. فعالیت جزئیات پیوند عمیق که به تنهایی در یک صفحه کوچک نشان داده شده است، اما در یک صفحه بزرگ به همراه یک فعالیت فهرست نمایش داده می‌شود.

درخواست راه‌اندازی باید به فعالیت اصلی هدایت شود و فعالیت جزئیات هدف باید به صورت جداگانه راه‌اندازی شود. سیستم به طور خودکار ارائه صحیح - به صورت انباشته یا کنار هم - را بر اساس عرض نمایشگر موجود انتخاب می‌کند.

کاتلین

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

جاوا

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

مقصد لینک عمیق ممکن است تنها فعالیتی باشد که باید در پشته ناوبری پشتی برای کاربر در دسترس باشد، و شاید بخواهید از نادیده گرفتن فعالیت جزئیات و باقی گذاشتن فقط فعالیت اصلی خودداری کنید:

صفحه نمایش بزرگ با فعالیت لیست و فعالیت جزئیات در کنار هم.   پیمایش به عقب نمی‌تواند فعالیت جزئیات را رد کند و فعالیت لیست را روی صفحه باقی بگذارد.

نمایشگر کوچک فقط با فعالیت جزئی. پیمایش برگشت قادر به رد کردن فعالیت جزئی و نمایش فعالیت فهرست نیست.

در عوض، می‌توانید هر دو فعالیت را با استفاده از ویژگی finishPrimaryWithSecondary به طور همزمان به پایان برسانید:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

به بخش ویژگی‌های پیکربندی مراجعه کنید.

چندین فعالیت در کانتینرهای تقسیم‌شده

قرار دادن چندین فعالیت در یک کانتینر تقسیم‌شده، کاربران را قادر می‌سازد تا به محتوای عمیق دسترسی پیدا کنند. برای مثال، با تقسیم لیست-جزئیات، ممکن است کاربر نیاز داشته باشد به یک بخش فرعی جزئیات برود، اما فعالیت اصلی را در جای خود نگه دارد:

شکل ۱۸. فعالیت در صفحه ثانویه پنجره وظیفه در جای خود باز شده است.

کاتلین

class DetailActivity : AppCompatActivity() {
    fun onOpenSubdetail() {
        startActivity(Intent(this, SubdetailActivity::class.java))
    }
}

جاوا

public class DetailActivity  extends AppCompatActivity {
    void onOpenSubdetail() {
        startActivity(new Intent(this, SubdetailActivity.class));
    }
}

فعالیت sub-detail روی فعالیت detail قرار می‌گیرد و آن را پنهان می‌کند:

کاربر می‌تواند با حرکت به عقب در پشته، به سطح جزئیات قبلی بازگردد:

شکل ۱۹. فعالیت حذف شده از بالای پشته.

قرار دادن فعالیت‌ها روی هم، رفتار پیش‌فرض زمانی است که فعالیت‌ها از یک فعالیت در همان کانتینر ثانویه اجرا می‌شوند. فعالیت‌هایی که از کانتینر اصلی در یک تقسیم فعال اجرا می‌شوند، در کانتینر ثانویه در بالای پشته فعالیت نیز قرار می‌گیرند.

فعالیت‌ها در یک وظیفه جدید

وقتی فعالیت‌های موجود در یک پنجره‌ی وظیفه‌ی تقسیم‌شده، فعالیت‌های موجود در یک وظیفه‌ی جدید را آغاز می‌کنند، وظیفه‌ی جدید از وظیفه‌ای که شامل تقسیم‌بندی است جدا شده و در کل پنجره نمایش داده می‌شود. صفحه‌ی Recents دو وظیفه را نشان می‌دهد: وظیفه‌ی تقسیم‌شده و وظیفه‌ی جدید.

شکل ۲۰. فعالیت C را در یک کار جدید از فعالیت B شروع کنید.

جایگزینی فعالیت

فعالیت‌ها می‌توانند در پشته کانتینر ثانویه جایگزین شوند؛ برای مثال، وقتی از اکتیویتی اصلی برای ناوبری سطح بالا استفاده می‌شود و اکتیویتی ثانویه یک مقصد انتخاب شده است. هر انتخاب از ناوبری سطح بالا باید یک فعالیت جدید را در کانتینر ثانویه آغاز کند و فعالیت یا فعالیت‌هایی را که قبلاً در آنجا بودند حذف کند.

شکل ۲۱. فعالیت ناوبری سطح بالا در پنل اصلی جایگزین فعالیت‌های مقصد در پنل ثانویه می‌شود.

If the app doesn't finish the activity in the secondary container when the navigation selection changes, back navigation might be confusing when the split is collapsed (when the device is folded). For example, if you have a menu in the primary pane and screens A and B stacked in the secondary pane, when the user folds the phone, B is on top of A, and A is on top of the menu. When the user navigates back from B, A appears instead of the menu.

Screen A must be removed from the back stack in such cases.

The default behavior when launching to the side in a new container over an existing split is to put the new secondary containers on top and retain the old ones in the back stack. You can configure the splits to clear the previous secondary containers with clearTop and launch new activities normally.

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

کاتلین

inner class MenuActivity : AppCompatActivity() {
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

جاوا

public class MenuActivity extends AppCompatActivity{
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

Alternatively, use the same secondary activity, and from the primary (menu) activity send new intents that resolve to the same instance but trigger a state or UI update in the secondary container.

Multiple splits

Apps can provide multi-level deep navigation by launching additional activities to the side.

When an activity in a secondary container launches a new activity to the side, a new split is created over top of the existing split.

Figure 22. Activity B starts activity C to the side.

The back stack contains all activities that were previously opened, so users can navigate to the A/B split after finishing C.

Activities A, B, and C in a stack. The activities are stacked in
          the following order from top to bottom: C, B, A.

To create a new split, launch the new activity to the side from the existing secondary container. Declare the configurations for both the A/B and B/C splits and launch activity C normally from B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

کاتلین

class B : AppCompatActivity() {
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

جاوا

public class B extends AppCompatActivity{
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

React to split state changes

Different activities in an app can have UI elements that perform the same function; for example, a control that opens a window containing account settings.

Figure 23. Different activities with functionally identical UI elements.

If two activities that have a UI element in common are in a split, it's redundant and perhaps confusing to show the element in both activities.

Figure 24. Duplicate UI elements in activity split.

To know when activities are in a split, check the SplitController.splitInfoList flow or register a listener with SplitControllerCallbackAdapter for changes in the split state. Then, adjust the UI accordingly:

کاتلین

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

جاوا

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

Coroutines can be launched in any lifecycle state, but are typically launched in the STARTED state to conserve resources (see Use Kotlin coroutines with lifecycle-aware components for more information).

Callbacks can be made in any lifecycle state, including when an activity is stopped. Listeners should usually be registered in onStart() and unregistered in onStop() .

Full-window modal

Some activities block users from interacting with the application until a specified action is performed; for example, a login screen activity, policy acknowledgement screen, or error message. Modal activities should be prevented from appearing in a split.

An activity can be forced to always fill the task window by using the expand configuration:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

Finish activities

Users can finish activities on either side of the split by swiping from the edge of the display:

Figure 25. Swipe gesture finishing activity B.
Figure 26. Swipe gesture finishing activity A.

If the device is set up to use the back button instead of gesture navigation, the input is sent to the focused activity—the activity that was touched or launched last.

The effect that finishing all activities in a container has on the opposing container depends on the split configuration.

Configuration attributes

You can specify split pair rule attributes to configure how finishing all activities on one side of the split affects the activities on the other side of the split. The attributes are:

  • window:finishPrimaryWithSecondary — How finishing all activities in the secondary container affects the activities in the primary container
  • window:finishSecondaryWithPrimary — How finishing all activities in the primary container affects the activities in the secondary container

Possible values of the attributes include:

  • always — Always finish the activities in the associated container
  • never — Never finish the activities in the associated container
  • adjacent — Finish the activities in the associated container when the two containers are displayed adjacent to each other, but not when the two containers are stacked

برای مثال:

<SplitPairRule
    <!-- Do not finish primary container activities when all secondary container activities finish. -->
    window:finishPrimaryWithSecondary="never"
    <!-- Finish secondary container activities when all primary container activities finish. -->
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

پیکربندی پیش‌فرض

When all activities in one container of a split finish, the remaining container occupies the entire window:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Split containing activities A and B. A is finished, leaving B to
          occupy the entire window.

Split containing activities A and B. B is finished, leaving A to
          occupy the entire window.

Finish activities together

Finish the activities in the primary container automatically when all activities in the secondary container finish:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Split containing activities A and B. B is finished, which also
          finishes A, leaving the task window empty.

Split containing activities A and B. A is finished, leaving B alone
          in the task window.

Finish the activities in the secondary container automatically when all activities in the primary container finish:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Split containing activities A and B. A is finished, which also
          finishes B, leaving the task window empty.

Split containing activities A and B. B is finished, leaving A alone
          in the task window.

Finish activities together when all activities in either the primary or secondary container finish:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Split containing activities A and B. A is finished, which also
          finishes B, leaving the task window empty.

Split containing activities A and B. B is finished, which also
          finishes A, leaving the task window empty.

Finish multiple activities in containers

If multiple activities are stacked in a split container, finishing an activity on the bottom of the stack does not automatically finish activities on top.

For example, if two activities are in the secondary container, C on top of B:

Secondary activity stack containing activity C stacked on top of B
          is stacked on top of the prmary activity stack containing activity
          A.

and the configuration of the split is defined by the configuration of activities A and B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

finishing the top activity retains the split.

Split with activity A in primary container and activities B and C in
          secondary, C stacked on top of B. C finishes, leaving A and B in the
          activity split.

Finishing the bottom (root) activity of the secondary container does not remove the activities on top of it; and so, also retains the split.

Split with activity A in primary container and activities B and C in
          secondary, C stacked on top of B. B finishes, leaving A and C in the
          activity split.

Any additional rules for finishing activities together, such as finishing the secondary activity with the primary, are also executed:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Split with activity A in primary container and activities B and C in
          secondary container, C stacked on top of B. A finishes, also
          finishing B and C.

And when the split is configured to finish primary and secondary together:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Split with activity A in primary container and activities B and C in
          secondary, C stacked on top of B. C finishes, leaving A and B in the
          activity split.

Split with activity A in primary container and activities B and C in
          secondary, C stacked on top of B. B finishes, leaving A and C in the
          activity split.

Split with activity A in primary container and activities B and C in
          secondary, C stacked on top of B. A finishes, also finishing B and
          C.

Change split properties at runtime

The properties of an active and visible split cannot be changed. Changing the split rules affects additional activity launches and new containers, but not existing and active splits.

To change the properties of active splits, finish the side activity or activities in the split and launch to the side again with a new configuration.

Dynamic split properties

Android 15 (API level 35) and higher supported by Jetpack WindowManager 1.4 and higher offer dynamic features that enable configurability of activity embedding splits, including:

  • Pane expansion: An interactive, draggable divider enables users to resize the panes in a split presentation.
  • Activity stack pinning: Users can pin the content in one container and isolate navigation in the container from navigation in the other container.
  • Dialog full-screen dim: When displaying a dialog, apps can specify whether to dim the entire task window or just the container that opened the dialog.

Pane expansion

Pane expansion enables users to adjust the amount of screen space allocated to the two activities in a dual‑pane layout.

To customize the appearance of the window divider and set the divider's draggable range, do the following:

  1. Create an instance of DividerAttributes

  2. Customize the divider attributes:

    • color : The color of the draggable pane separator.

    • widthDp : The width of the draggable pane separator. Set to WIDTH_SYSTEM_DEFAULT to let the system determine the divider width.

    • Drag range: The minimum percentage of the screen either pane can occupy. Can range from 0.33 to 0.66. Set to DRAG_RANGE_SYSTEM_DEFAULT to let the system determine the drag range.

    کاتلین

    val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
    
    if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
        splitAttributesBuilder.setDividerAttributes(
            DividerAttributes.DraggableDividerAttributes.Builder()
                .setColor(getColor(R.color.divider_color))
                .setWidthDp(4)
                .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
                .build()
        )
    }
    val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

    جاوا

    SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
        .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
        .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);
    
    if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
        splitAttributesBuilder.setDividerAttributes(
          new DividerAttributes.DraggableDividerAttributes.Builder()
            .setColor(ContextCompat.getColor(this, R.color.divider_color))
            .setWidthDp(4)
            .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
            .build()
        );
    }
    SplitAttributes _splitAttributes = splitAttributesBuilder.build();

Activity stack pinning

Activity stack pinning enables users to pin one of the split windows so the activity stays as is while users navigate within the other window. Activity stack pinning provides an enhanced multitasking experience.

To enable activity stack pinning in your app, do the following:

  1. Add a button to the layout file of the activity you want to pin, for example, the detail activity of an list‑detail layout:

    <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. In the onCreate() method of the activity, set an onclick listener on the button:

    کاتلین

    val pinButton: Button = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext)
            .pinTopActivityStack(taskId, pinSplitRule)
    }

    جاوا

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) -> {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext())
            .pinTopActivityStack(getTaskId(), pinSplitRule);
    });

Dialog full-screen dim

Activities typically dim their displays to draw attention to a dialog. In activity embedding, both panes of the dual‑pane display should dim, not just the pane containing the activity that opened the dialog, for a unified UI experience.

With WindowManager 1.4 and higher, the entire app window dims by default when a dialog opens (see EmbeddingConfiguration.DimAreaBehavior.ON_TASK ).

To dim only the container of the activity that opened the dialog, use EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK .

Extract an activity from a split to full window

Create a new configuration that displays the side activity full window, and then relaunch the activity with an intent that resolves to the same instance.

Check for split support at runtime

Activity embedding is supported on Android 12L (API level 32) and higher, but is also available on some devices running earlier platform versions. To check at runtime for the availability of the feature, use the SplitController.splitSupportStatus property or SplitController.getSplitSupportStatus() method:

کاتلین

if (SplitController.getInstance(this).splitSupportStatus ==
    SplitController.SplitSupportStatus.SPLIT_AVAILABLE
) {
    // Device supports split activity features.
}

جاوا

if (SplitController.getInstance(this).getSplitSupportStatus() ==
    SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
    // Device supports split activity features.
}

If splits are not supported, activities are launched on top of the activity stack (following the non-activity embedding model).

Prevent system override

The manufacturers of Android devices (original equipment manufacturers, or OEMs), can implement activity embedding as a function of the device system. The system specifies split rules for multi-activity apps, overriding the windowing behavior of the apps. The system override forces multi-activity apps into a system-defined activity embedding mode.

System activity embedding can enhance app presentation through multi-pane layouts, such as list-detail , without any changes to the app. However, the system's activity embedding might also cause incorrect app layouts, bugs, or conflicts with activity embedding implemented by the app.

Your app can prevent or permit system activity embedding by setting PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE in the app manifest file, for example:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

The property name is defined in the Jetpack WindowManager WindowProperties object. Set the value to false if your app implements activity embedding, or if you want to otherwise prevent the system from applying its activity embedding rules to your app. Set the value to true to permit the system to apply system-defined activity embedding to your app.

Limitations, restrictions, and caveats

  • Only the host app of the task, which is identified as the owner of the root activity in the task, can organize and embed other activities in the task. If activities that support embedding and splits run in a task that belongs to a different application, then embedding and splits will not work for those activities.
  • Activities can only be organized within a single task. Launching an activity in a new task always puts it in a new expanded window outside of any existing splits.
  • Only activities in the same process can be organized and put in a split. The SplitInfo callback only reports activities that belong to the same process, since there is no way of knowing about activities in different processes.
  • Each pair or singular activity rule applies only to activity launches that happen after the rule has been registered. There is currently no way to update existing splits or their visual properties.
  • The split pair filter configuration must match the intents used when launching activities completely. The matching occurs at the point when a new activity is started from the application process, so it might not know about component names that are resolved later in the system process when using implicit intents. If a component name is not known at the time of launch, a wildcard can be used instead ("*/*") and filtering can be performed based on intent action.
  • There is currently no way to move activities between containers or in and out of splits after they were created. Splits are only created by the WindowManager library when new activities with matching rules are launched, and splits are destroyed when the last activity in a split container is finished.
  • Activities can be relaunched when the configuration changes, so when a split is created or removed and activity bounds change, the activity can go through complete destruction of the previous instance and creation of the new one. As a result, app developers should be careful with things like launching new activities from lifecycle callbacks.
  • Devices must include the window extensions interface to support activity embedding. Nearly all large screen devices running Android 12L (API level 32) or higher include the interface. However, some large screen devices that are not capable of running multiple activities don't include the window extensions interface. If a large screen device doesn't support multi-window mode, it might not support activity embedding.

منابع اضافی