文章目录

  • 对话
  • Conversation Space
  • Bubbles
    • 通知中心的Bubble
    • 如何弹出Bubble(app端相关)
    • 系统是如何弹出Bubble的(源码相关)

Android R 通知新特性—人与对话(气泡窗)

对话

google在之前的版本就提出了一个“people and conversations”的概念,目的是在手机界面中增加交流类UI的比重,因为毕竟用户使用手机,有很大一部分行为都是基于社交去进行,社交是人与社会沟通的基础。所以google在Android 11中增加了很大一部分的改动用于支持这一行为。且将很多之前版本的新增改动串联起来

Conversation Space

在Android 11上,下拉状态栏提供了一个专属的区域用来显示这些具有会话交互类的通知

可以看到左图:正常的通知是显示在通知区域的,其中飞书的通知也是显示在,可是在我们的认知中,飞书的消息通知也是属于对话类的通知,可是为何未显示在通知栏的对话区域,是因为显示在该区域还需要如下设置:

  • 通知使用 MessagingStyle
  • 只有在应用以 Android 11 或更高版本为目标平台时适用)通知与Shortcut关联。通知可以通过调用 setShortcutIdsetShortcutInfo 来设置此关联。如果应用以 Android 10 或更低版本为目标平台,通知就不必与Shortcut关联,但UI则会恢复为老版本。

而在右图中:可以看到,在通知区域之上,还有一块名为“对话”的区域,用于显示专属的对话通知。
此空间内的通知在外观和行为上不同于手机上的非对话通知:

  • 首先在UI样式上就存在很多不同,对话通知以大图标头像、Person名称、message内容为显示重点,可以看一下右图的Title区域:Parrot • People • 现在 。其中应用的名称并不是Parrot,而是People,Parrot为当前会话的对象名称,也就是Person名称
  • 除了一些之前版本就存在的交互逻辑之外,还在右下角有多出一个按钮,用于将当前对话通知以Bubble气泡的形式去悬浮显示,这个我们在下文做详细解释

  • 点按通知即可在应用中打开对话(如果对话此前以Bubble形式显示,则会以Bubble的形式中打开),点按文字插入点即可将通知栏中的新消息展开到完整篇幅。
  • 提供了对话专用的操作(某些操作通过长按来执行): (1)将此对话标记为优先
    (2)将此对话提升为对话泡(仅当应用支持对话泡时才会显示)
    (3)将此对话的通知设为静音
    (4)为此对话设置自定义提示音或振动

Bubbles

在Android 10上 google就发布了这个叫Bubbles的新特性,一个Bubble(气泡)可以悬浮在其它应用内容之上,可以展开为一个Activity(实际上为一个AvtivityView),也可以收起后随意拖动到屏幕的任意位置。
在Android 10上,电话就已经使用了Bubbles的方式去实现,当用户在拨打电话时切换到其它应用,就会缩小成一个小巧的气泡悬浮在屏幕边缘。
而Android11 上由于ActivityView 功能的增加,bubble的功能也加强了。

通知中心的Bubble

在Android 11上,可通过点击通知中心的对话区域的对话通知,启动对应的对话气泡(Bubble),当然这也需要一定的条件:只有当前对话通知关联了相关的Shortcut快捷方式,才可启动相应的对话气泡。且如果对话在通知栏中标记为“优先”或触发了对话泡显示方式,系统将自动以对话泡形式显示这些对话。

气泡会悬浮并磁吸于屏幕边缘,层级位于状态栏之下,应用之上,用户可以随意拖动吸附并删除
点击则会弹出bubble对应的Activity

注:Bubble相关代码位于frameworks/base/packages/SystemUI/src/com/android/systemui/bubbles包内

BubbleController.java:

/** Adds the BubbleStackView to the WindowManager if it's not already there. */
private void addToWindowManagerMaybe() {// If the stack is null, or already added, don't add it.if (mStackView == null || mAddedToWindowManager) {return;}mWmLayoutParams = new WindowManager.LayoutParams(// Fill the screen so we can use translation animations to position the bubble// stack. We'll use touchable regions to ignore touches that are not on the bubbles// themselves.ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY,// Start not focusable - we'll become focusable when expanded so the ActivityView// can use the IME.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,PixelFormat.TRANSLUCENT);mWmLayoutParams.setFitInsetsTypes(0);mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;mWmLayoutParams.token = new Binder();mWmLayoutParams.setTitle("Bubbles!");mWmLayoutParams.packageName = mContext.getPackageName();mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;try {mAddedToWindowManager = true;mWindowManager.addView(mStackView, mWmLayoutParams);} catch (IllegalStateException e) {// This means the stack has already been added. This shouldn't happen, since we keep// track of that, but just in case, update the previously added view's layout params.e.printStackTrace();updateWmFlags();}
}

add的mStackView为BubbleStackView类

/*** BubbleStackView is lazily created by this method the first time a Bubble is added. This* method initializes the stack view and adds it to the StatusBar just above the scrim.*/
private void ensureStackViewCreated() {if (mStackView == null) {mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,this::hideCurrentInputMethod);mStackView.addView(mBubbleScrim);if (mExpandListener != null) {mStackView.setExpandListener(mExpandListener);}mStackView.setUnbubbleConversationCallback(key -> {final NotificationEntry entry =mNotificationEntryManager.getPendingOrActiveNotif(key);if (entry != null) {onUserChangedBubble(entry, false /* shouldBubble */);}});}addToWindowManagerMaybe();
}
/*** Renders bubbles in a stack and handles animating expanded and collapsed states.*/
public class BubbleStackView extends FrameLayoutimplements ViewTreeObserver.OnComputeInternalInsetsListener

可以看到 BubbleStackView继承自FrameLayout,且实现了ViewTreeObserver.OnComputeInternalInsetsListener接口,此接口是在window布局完成时用于计算并设置当前window的TouchRegion属性的接口

/*** Interface definition for a callback to be invoked when layout has* completed and the client can compute its interior insets.* * We are not yet ready to commit to this API and support it, so* @hide*/
public interface OnComputeInternalInsetsListener {/*** Callback method to be invoked when layout has completed and the* client can compute its interior insets.** @param inoutInfo Should be filled in by the implementation with* the information about the insets of the window.  This is called* with whatever values the previous OnComputeInternalInsetsListener* returned, if there are multiple such listeners in the window.*/public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
}

我们回到BubbleStackView类中的具体实现:onComputeInternalInsets

@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);mTempRect.setEmpty();getTouchableRegion(mTempRect);inoutInfo.touchableRegion.set(mTempRect);
}

可以看到的确在回调的时候去计算region并设置,具体计算我们就不进去细看,这也是为什么只有气泡位置可以响应点击事件,但可以全屏拖动的原因。

如何弹出Bubble(app端相关)

Bubble(气泡)是通过 Notification API 创建的,因此可以照常发送通知。Bubble是通知系统的一部分,如果用户锁屏或者打开了息屏显示(always-on-display),bubble会以普通的通知展示。如果希望让通知显示为气泡,则需要为其附加一些额外的数据。
在语法上,创建bubble和创建通知几乎一模一样。
气泡的展开视图是根据选择的 Activity 创建的。此 Activity 需要经过配置才能正确显示为气泡。此 Activity 必须可以 resizeable 且是 embedded 的。只要 Activity 不满足其中任何一项要求,都会显示为通知。
这些属性需要在AndroidManifest文件中进行配置:

<activity  android:name=".bubbles.BubbleActivity"  android:theme="@style/AppTheme.NoActionBar"  android:label="@string/title_activity_bubble"  android:allowEmbedded="true"  android:resizeableActivity="true"/>

如需发送气泡,请按照以下步骤操作:
按照常规方式创建通知。
调用 BubbleMetadata.Builder(PendingIntent, Icon)BubbleMetadata.Builder(String) 以创建 BubbleMetadata 对象。
使用 setBubbleMetadata 将元数据添加到通知中。

// Create bubble intent
Intent target = new Intent(mContext, BubbleActivity.class);
PendingIntent bubbleIntent =PendingIntent.getActivity(mContext, 0, target, 0 /* flags */);// Create bubble metadata
Notification.BubbleMetadata bubbleData =new Notification.BubbleMetadata.Builder(bubbleIntent,Icon.createWithResource(context, R.drawable.icon)).setDesiredHeight(600).build();// Create notification
Person chatPartner = new Person.Builder().setName("Chat partner").setImportant(true).build();Notification.Builder builder =new Notification.Builder(mContext, CHANNEL_ID).setContentIntent(contentIntent).setSmallIcon(smallIcon).setBubbleMetadata(bubbleData).addPerson(chatPartner);

其中的Person对象是和Android10上显示bubble相关
更多的气泡通知相关的配置可查阅google官方文档

系统是如何弹出Bubble的(源码相关)

那么我们是如何在接收到Notification的时候去弹出Bubble气泡呢

我们首先dump一下Bubble的window相关信息

可以看到,window宽高为屏幕宽高[1080,2220] (测试机宽高为1080*2220)
层级为2042

public static final int TYPE_TRUSTED_APPLICATION_OVERLAY = FIRST_SYSTEM_WINDOW + 42;

我们通过dump信息中的Window Title -> "Bubbles!"去查找相关代码,发现window的add位置位于BubbleController中:
BubbleController.java:

/** Adds the BubbleStackView to the WindowManager if it's not already there. */
private void addToWindowManagerMaybe() {// If the stack is null, or already added, don't add it.if (mStackView == null || mAddedToWindowManager) {return;}mWmLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.TYPE_TRUSTED_APPLICATION_OVERLAY,// Start not focusable - we'll become focusable when expanded so the ActivityView// can use the IME.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,PixelFormat.TRANSLUCENT);mWmLayoutParams.setFitInsetsTypes(0);mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;mWmLayoutParams.token = new Binder();mWmLayoutParams.setTitle("Bubbles!");mWmLayoutParams.packageName = mContext.getPackageName();mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;try {mAddedToWindowManager = true;mWindowManager.addView(mStackView, mWmLayoutParams);} catch (IllegalStateException e) {e.printStackTrace();updateWmFlags();}
}

add的mStackView对象为BubbleStackView类,我们查看一下赋值的位置:

/*** BubbleStackView is lazily created by this method the first time a Bubble is added. This* method initializes the stack view and adds it to the StatusBar just above the scrim.*/
private void ensureStackViewCreated() {if (mStackView == null) {mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,mSysUiState, this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,this::hideCurrentInputMethod);mStackView.addView(mBubbleScrim);......}addToWindowManagerMaybe();
}

BubbleStackView.java

/*** Renders bubbles in a stack and handles animating expanded and collapsed states.*/
public class BubbleStackView extends FrameLayoutimplements ViewTreeObserver.OnComputeInternalInsetsListener

可以看到 BubbleStackView继承自FrameLayout,且实现了ViewTreeObserver.OnComputeInternalInsetsListener接口,此接口是在window布局完成时用于计算并设置当前window的TouchRegion属性的接口

/*** Interface definition for a callback to be invoked when layout has* completed and the client can compute its interior insets.* * We are not yet ready to commit to this API and support it, so* @hide*/
public interface OnComputeInternalInsetsListener {/*** Callback method to be invoked when layout has completed and the* client can compute its interior insets.** @param inoutInfo Should be filled in by the implementation with* the information about the insets of the window.  This is called* with whatever values the previous OnComputeInternalInsetsListener* returned, if there are multiple such listeners in the window.*/public void onComputeInternalInsets(InternalInsetsInfo inoutInfo);
}

我们回到BubbleStackView类中的具体实现:onComputeInternalInsets

@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);mTempRect.setEmpty();getTouchableRegion(mTempRect);inoutInfo.touchableRegion.set(mTempRect);
}

可以看到的确在回调的时候去计算region并设置,具体计算我们就不进去细看,这也是为什么只有气泡位置可以响应点击事件,但可以全屏拖动的原因。

我们从addWindow的位置继续往上追溯。然后再从上至下分析一下相关的流程:

SystemUI在启动的时候会去初始化BubbleController类,具体位于
SystemUIBinder类中,通过dagger依赖注入的方式去实现

@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,KeyguardModule.class})

BubbleModule.java:

static BubbleController newBubbleController(......) {return new BubbleController(......);
}
public BubbleController(......) {......mBubbleData = data;mBubbleData.setListener(mBubbleDataListener);......mNotificationEntryManager = entryManager;mNotificationGroupManager = groupManager;mNotifPipeline = notifPipeline;if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {setupNEM();} else {setupNotifPipeline();}......
}

主要代码位于上方:
首先将一个BubbleData.Listener设置在BubbleData中。
之后在下方调用了setupNEM() 方法
此方法主要是设置通知的监听:

private void setupNEM() {mNotificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {@Overridepublic void onPendingEntryAdded(NotificationEntry entry) {onEntryAdded(entry);}@Overridepublic void onPreEntryUpdated(NotificationEntry entry) {onEntryUpdated(entry);}@Overridepublic void onEntryRemoved(NotificationEntry entry,@android.annotation.Nullable NotificationVisibility visibility,boolean removedByUser,int reason) {BubbleController.this.onEntryRemoved(entry);}@Overridepublic void onNotificationRankingUpdated(RankingMap rankingMap) {onRankingUpdated(rankingMap);}});mNotificationEntryManager.addNotificationRemoveInterceptor(new NotificationRemoveInterceptor() {@Overridepublic boolean onNotificationRemoveRequested(String key,NotificationEntry entry,int dismissReason) {......}});mNotificationGroupManager.addOnGroupChangeListener(new NotificationGroupManager.OnGroupChangeListener() {@Overridepublic void onGroupSuppressionChanged(NotificationGroupManager.NotificationGroup group,boolean suppressed) {......}});......
}

当通知发生改变的时候就会回调这些接口

例如当来了一条新通知,就会先去判定是否需要弹出bubble,如果需要,则调用到onEntryAdded方法

private void onEntryAdded(NotificationEntry entry) {if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)&& entry.isBubble()&& canLaunchInActivityView(mContext, entry)) {updateBubble(entry);}
}
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {// If this is an interruptive notif, mark that it's interruptedif (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {notif.setInterruption();}Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */);inflateAndAdd(bubble, suppressFlyout, showInShade);
}

getOrCreateBubble方法将传入的NotificationEntry转换为Bubble,NotificationEntry中封装了此条通知的相关信息。

之后再调用inflateAndAdd方法,将转化出的bubble对象解析并在界面显示,我们继续看inflateAndAdd方法的具体实现

void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {// Lazy init stack view when a bubble is createdensureStackViewCreated();bubble.setInflateSynchronously(mInflateSynchronously);bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
}

首先调用ensureStackViewCreated方法将StackView inflate并显示出来,这就是我们在前面看到的

然后调用Bubble的inflate方法去做Bubble的加载

void inflate(BubbleViewInfoTask.Callback callback,Context context,BubbleStackView stackView,BubbleIconFactory iconFactory,boolean skipInflation) {if (isBubbleLoading()) {mInflationTask.cancel(true /* mayInterruptIfRunning */);}mInflationTask = new BubbleViewInfoTask(this,context,stackView,iconFactory,skipInflation,callback);if (mInflateSynchronously) {mInflationTask.onPostExecute(mInflationTask.doInBackground());} else {mInflationTask.execute();}
}

可以看到在inflate的时候创建了一个BubbleViewInfoTask类,此类继承自AsyncTask类,专门用于实现异步加载Bubble的行为

public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo>
@Override
protected BubbleViewInfo doInBackground(Void... voids) {return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble,mSkipInflation);
}@Override
protected void onPostExecute(BubbleViewInfo viewInfo) {if (viewInfo != null) {mBubble.setViewInfo(viewInfo);if (mCallback != null && !isCancelled()) {mCallback.onBubbleViewsReady(mBubble);}}
}

在doBackground中调用BubbleViewInfo的静态方法populate,此方法是用于解析Bubble并生成BubbleViewInfo对象,此对象封装了包括View对象、Bubble数据在内的各种属性,具体内容可以在populate的解析行为中略知一二

在解析完成后,onPostExecute会调用传入callback的onBubbleViewsReady方法,并将解析完成的BubbleViewInfo对象传入,从前面的代码可知,此时会进入BubbleData的notificationEntryUpdated方法中

/*** When this method is called it is expected that all info in the bubble has completed loading.* @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,* BubbleStackView, BubbleIconFactory).*/
void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {......if (prevBubble == null) {// Create a new bubblebubble.setSuppressFlyout(suppressFlyout);doAdd(bubble);trim();} else {// Updates an existing bubblebubble.setSuppressFlyout(suppressFlyout);doUpdate(bubble);}......dispatchPendingChanges();
}

首先会判定是否为新的bubble,如果是新bubble,则调用doAdd方法,如果是现存的bubble,则调用doUpdate方法去更新此bubble,最后调用dispatchPendingChanges方法去提交此次更新

我们不妨先去看doAdd方法

private void doAdd(Bubble bubble) {mBubbles.add(0, bubble);mStateChange.addedBubble = bubble;......
}

重点为标红位置,将当前需要添加的bubble add到一个List中,然后把此bubble赋值给addedBubble
然后我们查看dispatch方法

private void dispatchPendingChanges() {if (mListener != null && mStateChange.anythingChanged()) {mListener.applyUpdate(mStateChange);}mStateChange = new Update(mBubbles, mOverflowBubbles);
}

anythingChanged函数会判定当前是否有需要更新的改变,例如是否有展开bubble的操作、是否有添加或更新操作、bubble之间的顺序更改等等

boolean anythingChanged() {return expandedChanged|| selectionChanged|| addedBubble != null|| updatedBubble != null|| !removedBubbles.isEmpty()|| orderChanged;
}

最后调用mListener的applyUpdate方法,将此次更新传入
那这个mBubbleData的私有变量mListener又是从哪赋值的呢?
我们别忘了最开始在BubbleController初始化的时候的一个行为

public BubbleController(......) {......mBubbleData = data;mBubbleData.setListener(mBubbleDataListener);......mNotificationEntryManager = entryManager;mNotificationGroupManager = groupManager;mNotifPipeline = notifPipeline;if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {setupNEM();} else {setupNotifPipeline();}......
}

mBubbleDataListener的具体实现位于BubbleController类中

private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {@Overridepublic void applyUpdate(BubbleData.Update update) {ensureStackViewCreated();......// Collapsing? Do this first before remaining steps.if (update.expandedChanged && !update.expanded) {mStackView.setExpanded(false);mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);}// Do removals, if any.for (Pair<Bubble, Integer> removed : removedBubbles) {......if (mStackView != null) {mStackView.removeBubble(bubble);}......if (update.addedBubble != null && mStackView != null) {mDataRepository.addBubble(mCurrentUserId, update.addedBubble);mStackView.addBubble(update.addedBubble);}if (update.updatedBubble != null && mStackView != null) {mStackView.updateBubble(update.updatedBubble);}// At this point, the correct bubbles are inflated in the stack.// Make sure the order in bubble data is reflected in bubble row.if (update.orderChanged && mStackView != null) {mDataRepository.addBubbles(mCurrentUserId, update.bubbles);mStackView.updateBubbleOrder(update.bubbles);}......// Expanding? Apply this last.if (update.expandedChanged && update.expanded) {if (mStackView != null) {mStackView.setExpanded(true);......}}......}
};

可以看到,在applyUpdate中,会根据不同的判定,对mStackView进行不同的操作,具体的UI相关操作我们就暂时先不细细分析,之后补上。
bubble的展开效果:
上述代码中mStackView.setExpanded(true);会触发bubble的展开,而展开后的View在BubbleExpandedView.java中。很明显的可以看到,该View里包含了一个ActivityView的实例。

void populateExpandedView() {if (DEBUG_BUBBLE_EXPANDED_VIEW) {Log.d(TAG, "populateExpandedView: "+ "bubble=" + getBubbleKey());}if (usingActivityView()) {mActivityView.setCallback(mStateCallback);} else {Log.e(TAG, "Cannot populate expanded view.");}
}private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {@Overridepublic void onActivityViewReady(ActivityView view) {if (DEBUG_BUBBLE_EXPANDED_VIEW) {Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus+ " bubble=" + getBubbleKey());}switch (mActivityViewStatus) {case INITIALIZING:case INITIALIZED:// Custom options so there is no activity transition animationActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),0 /* enterResId */, 0 /* exitResId */);options.setTaskAlwaysOnTop(true);options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);// Post to keep the lifecycle normalpost(() -> {if (DEBUG_BUBBLE_EXPANDED_VIEW) {Log.d(TAG, "onActivityViewReady: calling startActivity, "+ "bubble=" + getBubbleKey());}if (mActivityView == null) {mBubbleController.removeBubble(getBubbleKey(),BubbleController.DISMISS_INVALID_INTENT);return;}try {if (!mIsOverflow && mBubble.hasMetadataShortcutId()&& mBubble.getShortcutInfo() != null) {options.setApplyActivityFlagsForBubbles(true);// 1mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),options, null /* sourceBounds */);} else {Intent fillInIntent = new Intent();fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);if (mBubble != null) {mBubble.setIntentActive();}// 1mActivityView.startActivity(mPendingIntent, fillInIntent, options);}} catch (RuntimeException e) {Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()+ ", " + e.getMessage() + "; removing bubble");mBubbleController.removeBubble(getBubbleKey(),BubbleController.DISMISS_INVALID_INTENT);}});mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED;break;case ACTIVITY_STARTED:// 3post(() -> mActivityManager.moveTaskToFront(mTaskId, 0));break;}}@Overridepublic void onActivityViewDestroyed(ActivityView view) {if (DEBUG_BUBBLE_EXPANDED_VIEW) {Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus+ " bubble=" + getBubbleKey());}mActivityViewStatus = ActivityViewStatus.RELEASED;}@Overridepublic void onTaskCreated(int taskId, ComponentName componentName) {if (DEBUG_BUBBLE_EXPANDED_VIEW) {Log.d(TAG, "onTaskCreated: taskId=" + taskId+ " bubble=" + getBubbleKey());}// 2mTaskId = taskId;}@Overridepublic void onTaskRemovalStarted(int taskId) {if (DEBUG_BUBBLE_EXPANDED_VIEW) {Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId+ " mActivityViewStatus=" + mActivityViewStatus+ " bubble=" + getBubbleKey());}if (mBubble != null) {// Must post because this is called from a binder thread.post(() -> mBubbleController.removeBubble(mBubble.getKey(),BubbleController.DISMISS_TASK_FINISHED));}}
};

上述代码可以看出,BubbleExpadnedView会对ActivityView设置一个回调。根据回调状态的不同来处理

  1. 当onActivityViewReady回调过来时:
    根据配置信息,使用ActivityView startActivity / startShortcutActivity。实际是ActivityView里的TaskEmbedder进行操作。关于TaskEmbedder 可以看这篇更专业的文档梦幻联动。 这里不再讲解。
  2. onTaskCreated回调会记录创建的taskId。
  3. onActivityViewReady再次调用时,mActivityViewStatus已经修改为ACTIVITY_STARTED,所以直接将步骤2保存的Task移动到前台。就将应用设置的Activity显示出来了。

Android R 通知新特性—人与对话(气泡窗)相关推荐

  1. android oreo 老机型,Android Oreo 通知新特性,这坑老夫先踩了

    前些天性致脖脖地点进入了developer.android.com,想看下通知这块内容,你懂的.首先映入眼帘的就是下面这玩意儿,翻译速度阔以哦!!! image 通知渠道?啥玩意儿,啊,走过路过,千万 ...

  2. Android 4.0新特性(中文)

    Android 4.0新特性(中文) 转自http://www.eoeandroid.com/thread-103300-1-1.html android4.0 SDK发布有一段时间了,在eoe上找到 ...

  3. Atitit.android  jsbridge v1新特性

    Atitit.android  jsbridge v1新特性 1. Java代码调用js并传参其实是通过WebView的loadUrl方法去调用的.只是参数url的写法不一样而已1 2. 三.JAVA ...

  4. 《Android群英传》读书笔记 (5) 第十一章 搭建云端服务器 + 第十二章 Android 5.X新特性详解 + 第十三章 Android实例提高...

    第十一章 搭建云端服务器 该章主要介绍了移动后端服务的概念以及Bmob的使用,比较简单,所以略过不总结. 第十三章 Android实例提高 该章主要介绍了拼图游戏和2048的小项目实例,主要是代码,所 ...

  5. Android群英传读书笔记——第十二章:Android 5.X新特性详解

    第十二章目录 12.1 Android5.X UI设计初步 12.1.1 材料的形态模拟 12.1.2 更加真实的动画 12.1.3 大色块的使用 12.2 Material Design主题 12. ...

  6. 安卓扁平化之路专题(一)Android 4.4新特性

    Android从3.0版本开始走上了扁平化设计的道路,在Android3.0之后,Google对UI导航设计上进行了一系列的改革,其中有一个非常好用的新功能就是引入的ActionBar,他用于取代3. ...

  7. Android RecyclerView(五)封装Holder与Adapter(Android 5.0 新特性)

    Android RecyclerView(五)封装Holder与Adapter(Android 5.0 新特性) 1 效果 2 BaseHolder的封装 public class BaseViewH ...

  8. RecyclerView(四)设置分割线样式(Android 5.0 新特性)

    Android RecyclerView(四)设置分割线样式(Android 5.0 新特性) 样式一 在这里,其实是设置了每一个 条目布局中的子布局的android:layout_margin = ...

  9. RecyclerView(三)实现聊天窗口样式(Android 5.0 新特性)

    Android RecyclerView(三)实现聊天窗口样式(Android 5.0 新特性) 效果 1 聊天窗口子视图布局文件 1.1 左边消息视图布局文件 使用到的背景图片 <?xml v ...

最新文章

  1. python调用百度地图画轨迹图_[python]百度地图API,正/逆地理编码,路线规划接口的调用,实现输出出行的距离和......
  2. 51NOD 1001 数组中和等于K的数对
  3. Python 操作 Excel,总有一个模块适合自己
  4. nginx的502问题
  5. php怎么根据接口文档实现功能,CodeIgniter+swagger实现 PHP API接口文档自动生成功能...
  6. 01.神经网络和深度学习 W3.浅层神经网络(作业:带一个隐藏层的神经网络)
  7. 从零开始学前端:grid布局和音频 --- 今天你学习了吗?(CSS:Day24)
  8. 目标检测(七)--Fast R-CNN
  9. 论文管理软件 Zotero 备忘
  10. 10大城市硬科技指数发布,“硬科技+在大西安”高峰论坛圆满落幕
  11. matlab计算天线方向性系数,天线方向图(Antenna Pattern)的设计解析思路
  12. 高淇Java300集
  13. Encoded Strings I 模拟(2021.11.沈阳)
  14. 简单高效的图片降噪方法
  15. /oa/web应用程序中的服务器错误修复,如何处理OA系统在线阅读或编辑文档时weboffice控件提示“文件存取错误”的问题?...
  16. Linux的Sed命令详解
  17. Atmega16 AVR 单片机 电子琴 proteus 仿真
  18. 外国asp空间常见问题解答
  19. 在iphone上设置邮箱
  20. [iOS微博项目 - 2.1] - 获得新浪授权接口

热门文章

  1. [SAE]免费服务器:新浪云服务器SAE的注册与使用
  2. 颈椎病的成因及治疗预防方法
  3. Android连接逍遥模拟器
  4. 使用python Telegram 机器人推送消息
  5. 2022-2027年中国卫星遥感市场竞争态势及行业投资前景预测报告
  6. 复制url直接能跳过验证_爬虫黑科技-绕开百度人机验证
  7. Squared Error 数学
  8. mysql单表瓶颈_mysql单表性能瓶颈_优化系列 | 实例解析MySQL性能瓶颈排查定位-云栖社区-阿里云...
  9. mui赋值_mui input用法
  10. [深度学习]动手学深度学习笔记-12