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

اگر برنامه شما از چندین اکتیویتی تشکیل شده است، تعبیه اکتیویتی به شما این امکان را میدهد که تجربه کاربری بهتری را در تبلتها، دستگاههای تاشو و دستگاههای 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(); } }); }
متغیرهایی
فعالیتهای جاینگهدار، فعالیتهای ثانویه خالی هستند که بخشی از یک تقسیمبندی فعالیت را اشغال میکنند. در نهایت قرار است با فعالیت دیگری که حاوی محتوا است جایگزین شوند. برای مثال، یک فعالیت جاینگهدار میتواند سمت ثانویه یک تقسیمبندی فعالیت را در یک طرحبندی لیست-جزئیات اشغال کند تا زمانی که یک آیتم از لیست انتخاب شود، در آن نقطه یک فعالیت حاوی اطلاعات جزئی برای آیتم لیست انتخاب شده جایگزین جاینگهدار میشود.
به طور پیشفرض، سیستم فقط زمانی که فضای کافی برای تقسیم فعالیت وجود داشته باشد، متغیرهایی را نمایش میدهد. متغیرهایی که به طور خودکار زمانی که اندازه نمایش به عرض یا ارتفاعی بسیار کوچک برای نمایش تقسیم تغییر میکند، پایان مییابند. وقتی فضا اجازه دهد، سیستم متغیر را با حالت مقداردهی اولیه مجدد راهاندازی میکند.

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

تغییر اندازه پنجره
وقتی تغییرات پیکربندی دستگاه، عرض پنجره وظیفه را کاهش میدهد، به طوری که برای طرحبندی چند پنجرهای به اندازه کافی بزرگ نباشد (برای مثال، وقتی یک دستگاه تاشو با صفحه نمایش بزرگ از اندازه تبلت به اندازه تلفن تا میشود یا اندازه پنجره برنامه در حالت چند پنجرهای تغییر میکند)، فعالیتهای غیرمتصل به مکان در پنجره ثانویه پنجره وظیفه، روی فعالیتهای پنجره اصلی انباشته میشوند.
فعالیتهای جاینگهدار فقط زمانی نمایش داده میشوند که عرض نمایشگر برای تقسیمبندی کافی باشد. در صفحه نمایشهای کوچکتر، جاینگهدار بهطور خودکار حذف میشود. وقتی ناحیه نمایش دوباره به اندازه کافی بزرگ شود، جاینگهدار دوباره ایجاد میشود. (به بخش جاینگهدارها مراجعه کنید.)
انباشت فعالیتها امکانپذیر است زیرا WindowManager فعالیتهای پنل ثانویه را بالای فعالیتهای پنل اصلی به صورت z-order مرتب میکند.
چندین فعالیت در صفحه ثانویه
فعالیت B، فعالیت C را در جای خود و بدون هیچ پرچم intent اضافی آغاز میکند:

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

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

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

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

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

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

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

همیشه ویژگی android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED را به فایل مانیفست برنامه خود اضافه کنید تا به دستگاهها اطلاع دهید که برنامه شما از تعبیه فعالیت پشتیبانی میکند (به بخش پیکربندی Split مراجعه کنید). سپس دستگاههای سفارشیشده توسط OEM میتوانند تعیین کنند که آیا فعالیتهای با پرتره ثابت را به صورت Letterbox نمایش دهند یا خیر.
پیکربندی تقسیم شده
قوانین تقسیم، تقسیمبندی فعالیتها را پیکربندی میکنند. شما میتوانید قوانین تقسیمبندی را در یک فایل پیکربندی XML یا با فراخوانیهای API مربوط به Jetpack WindowManager تعریف کنید.
در هر صورت، برنامه شما باید به کتابخانه WindowManager دسترسی داشته باشد و به سیستم اطلاع دهد که برنامه، تعبیه فعالیت (activity embedding) را پیادهسازی کرده است.
موارد زیر را انجام دهید:
آخرین وابستگی کتابخانه WindowManager را به فایل
build.gradleدر سطح ماژول برنامه خود اضافه کنید، برای مثال:implementation 'androidx.window:window:1.1.0-beta02'کتابخانه WindowManager تمام اجزای مورد نیاز برای تعبیه فعالیت را فراهم میکند.
به سیستم اطلاع دهید که برنامه شما قابلیت تعبیه فعالیت (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 از تعبیه فعالیت، مراحل زیر را انجام دهید:
یک فایل منبع 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>یک مقداردهی اولیه ایجاد کنید.
کامپوننت WindowManager
RuleControllerفایل پیکربندی XML را تجزیه و تحلیل میکند و قوانین را در دسترس سیستم قرار میدهد. یک کتابخانهی راهاندازی JetpackInitializerفایل XML را در هنگام راهاندازی برنامه در دسترسRuleControllerقرار میدهد تا قوانین با شروع هر فعالیتی اعمال شوند.برای ایجاد یک مقدار اولیه، موارد زیر را انجام دهید:
آخرین وابستگی کتابخانه Jetpack Startup را به فایل
build.gradleدر سطح ماژول خود اضافه کنید، برای مثال:implementation 'androidx.startup:startup-runtime:1.1.1'یک کلاس ایجاد کنید که رابط
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(); } }
یک ارائه دهنده محتوا برای تعاریف قانون ایجاد کنید.
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 انجام دهید تا مطمئن شوید که قوانین قبل از اجرای هرگونه فعالیتی اعمال میشوند.
برای ایجاد تقسیمبندی فعالیتها به صورت برنامهنویسی، موارد زیر را انجام دهید:
یک قانون تقسیم ایجاد کنید:
یک
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 );
فیلتر را به یک مجموعه فیلتر اضافه کنید:
```کاتلین
val filterSet = setOf(splitPairFilter)
جاوا
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
ایجاد ویژگیهای طرحبندی برای تقسیمبندی:
کاتلین
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(): نحوه چیدمان کانتینرهای اکتیویتی نسبت به یکدیگر را مشخص میکند، به طوری که کانتینر اصلی در اولویت باشد.
-
ساخت یک
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مشخص میکند که فعالیتهای جدید روی فعالیتهای موجود در کانتینر ثانویه قرار میگیرند.
-
نمونهی تکلایه از WindowManager
RuleControllerرا دریافت کنید و قانون زیر را به آن اضافه کنید:کاتلین
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
جاوا
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
وقتی محتوا در دسترس نیست، یک placeholder برای کانتینر ثانویه ایجاد کنید:
یک
ActivityFilterایجاد کنید که فعالیتی را که placeholder با آن تقسیم پنجره وظیفه را به اشتراک میگذارد، شناسایی کند:کاتلین
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
جاوا
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
فیلتر را به یک مجموعه فیلتر اضافه کنید:
کاتلین
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
جاوا
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
یک
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 برای اولین بار به صورت تقسیمشده با حداقل عرض کافی ظاهر شد، در نمایشگرهای کوچک در بالای پشته فعالیت ظاهر میشود یا خیر.
-
این قانون را به WindowManager
RuleControllerاضافه کنید:کاتلین
ruleController.addRule(splitPlaceholderRule)
جاوا
ruleController.addRule(splitPlaceholderRule);
فعالیتهایی را مشخص کنید که هرگز نباید بخشی از یک تقسیمبندی باشند:
یک
ActivityFilterایجاد کنید که فعالیتی را شناسایی کند که همیشه باید کل ناحیه نمایش وظیفه را اشغال کند:کاتلین
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
جاوا
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
فیلتر را به یک مجموعه فیلتر اضافه کنید:
کاتلین
val expandedActivityFilterSet = setOf(expandedActivityFilter)
جاوا
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
ایجاد یک
ActivityRule:کاتلین
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
جاوا
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Builderقانون را ایجاد و پیکربندی میکند:-
expandedActivityFilterSet: شامل فیلترهای فعالیت است که با شناسایی فعالیتهایی که میخواهید از تقسیمبندیها مستثنی شوند، زمان اعمال قانون را تعیین میکنند. -
setAlwaysExpand(): مشخص میکند که آیا فعالیت باید کل پنجره وظیفه را پر کند یا خیر.
-
این قانون را به 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 دو وظیفه را نشان میدهد: وظیفهی تقسیمشده و وظیفهی جدید.

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

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.

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

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.

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.

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:


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>


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>


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>


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>


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:

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.

Finishing the bottom (root) activity of the secondary container does not remove the activities on top of it; and so, also retains the 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>

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>



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:
Create an instance of
DividerAttributesCustomize the divider attributes:
color: The color of the draggable pane separator.widthDp: The width of the draggable pane separator. Set toWIDTH_SYSTEM_DEFAULTto 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_DEFAULTto 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:
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>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
SplitInfocallback 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.
منابع اضافی
- Codelabs:
- Learning pathway — Activity embedding
- Sample app — activity-embedding