最近发现Android P上锁屏界面,日期不显示,发现从P开始后,出现了Slice来允许应用以模块化,可交互的方式,插入多个使用场景。Android P的system UI 也使用到了这一特性,表现为锁屏时间,日期,勿扰图标,闹钟等。
首先,我们打开System UI 的mk 文件,可以看到以下代码:
LOCAL_STATIC_ANDROID_LIBRARIES := \
SystemUIPluginLib \
SystemUISharedLib \
android-support-car \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
android-support-v7-mediarouter \
android-support-v7-palette \
android-support-v14-preference \
android-support-v17-leanback \
android-slices-core \
android-slices-view \
android-slices-builders \
android-arch-core-runtime \
android-arch-lifecycle-extensions \
android-slices-core, view ,builders,就是实现Slice特性的依赖,至于Android studio工程,则需要在gradle中添加以下依赖,
implementation 'androidx.slice:slice-core:1.0.0'
implementation 'androidx.slice:slice-builders:1.0.0'
implementation 'androidx.slice:slice-view:1.0.0'
Slice 是一个集合其他模块,共同展示信息的功能,为了实现跨进程间的信息传递,它采用了provider来更新slice,而contentProvider需要在Manifest中注册,如SystemUI:
通过查看KeyguardSliceProvider.java,可以看到它继承了 SliceProvider,而SliceProvider又继承了ContentProvider。这里关注两个函数onCreateSliceProvider()、onBindSlice()。onCreateSliceProvider()是SliceProvider初始化的时候调用的:
@Override
public final boolean onCreate() {
if (!BuildCompat.isAtLeastP()) {
mCompat = new SliceProviderCompat(this,
onCreatePermissionManager(mAutoGrantPermissions), getContext());
}
return onCreateSliceProvider();
}
o’nCreateSliceProvider中做了如下的操作:
@Override
public boolean onCreateSliceProvider() {
mAlarmManager = getContext().getSystemService(AlarmManager.class);
mContentResolver = getContext().getContentResolver();
mNextAlarmController = new NextAlarmControllerImpl(getContext());
mNextAlarmController.addCallback(this);
mZenModeController = new ZenModeControllerImpl(getContext(), mHandler);
mZenModeController.addCallback(this);
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
KeyguardSliceProvider.sInstance = this;
registerClockUpdate();
updateClock();
return true;
}
主要是对几个需要显示的模块的监听初始化,包括Alarm,ZenMode,Clock;
注册之后是怎么进行刷新的呢?既然是provider,那么肯定有地方进行notifyChange的,通过对该类的阅读,发现在注册的一些callback中就有notifyChange,如下面的ZenMode:
public class KeyguardSliceProvider extends SliceProvider implements
NextAlarmController.NextAlarmChangeCallback, ZenModeController.Callback
复写了ZenMode的onZenChanged,
@Override
public void onZenChanged(int zen) {
mContentResolver.notifyChange(mSliceUri, null /* observer */);
}
那么在notifyChange之后,接下来就会进行刚刚说到的onBindSlice():
@Override
public Slice onBindSlice(Uri sliceUri) {
Trace.beginSection("KeyguardSliceProvider#onBindSlice");
ListBuilder builder = new ListBuilder(getContext(), mSliceUri);
builder.addRow(new RowBuilder(builder, mDateUri).setTitle(mLastText));
addNextAlarm(builder);
addZenMode(builder);
addPrimaryAction(builder);
Slice slice = builder.build();
Trace.endSection();
return slice;
}
这边和notification的创建类似,就是各个模块传递过来的icon(Row),title之类的信息。
SystemUi从KeyguardSliceProvider获取数据,那么如何刷新界面呢,其主要操作就是在KeyguardSliceView这里。
在KeyguardStatusView.java中:
mKeyguardSlice = findViewById(R.id.keyguard_status_area);
mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
接下来看keyguard_status_area的布局:
可以看到其包含了两个自定义控件,着重关注Row控件,日期,闹钟,勿扰等图标都是在Row中的。
Slice是如何监听,并且刷新到UI的呢,我们从KeyguardSliceView的初始化看起,可以看到,它实现了 TunerService.Tunable接口,所以初始化的时候就会调用onTuningChanged:
@Override
public void onTuningChanged(String key, String newValue) {
setupUri(newValue);
}
public void setupUri(String uriString) {
if (uriString == null) {
uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
}
boolean wasObserving = false;
if (mLiveData != null && mLiveData.hasActiveObservers()) {
wasObserving = true;
mLiveData.removeObserver(this);
}
mKeyguardSliceUri = Uri.parse(uriString);
mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
if (wasObserving) {
mLiveData.observeForever(this);
}
}
这里要重点注意,和bug有关,uriString 在KeyguardSliceView初始化时,默认是null,但会立刻被填充 KeyguardSliceProvider.KEYGUARD_SLICE_URI,随后,会被保存到mLiveData中。
private LiveData mLiveData;
可以看到mLiveData是一个slice 集合类,Android 给它的解释是LiveData is a data holder class that can be observed within a given lifecycle.,意思是LiveData是一个可被观察的数据持有类,不同于其他Observer,LiveData是对生命周期有感知的,它会遵循App组件的生命周期,如Activity,Fragment,Service等。
它的优点在于:
它的注册和反注册:
*
* @param observer The observer that will receive the events
*/
@MainThread
public void observeForever(@NonNull Observer super T> observer) {
assertMainThread("observeForever");
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
wrapper.activeStateChanged(true);
}
/**
* Removes the given observer from the observers list.
*
* @param observer The Observer to receive events.
*/
@MainThread
public void removeObserver(@NonNull final Observer super T> observer) {
assertMainThread("removeObserver");
ObserverWrapper removed = mObservers.remove(observer);
if (removed == null) {
return;
}
removed.detachObserver();
removed.activeStateChanged(false);
}
上面提到,Livedata感知生命周期,因此它的注册与反注册需要跟着观察者的生命周期走,如KeyguardSliceView中,它的注册与反注册,就是在onAttachedToWindow() 和onDetachedFromWindow() 中处理的,如下:
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Make sure we always have the most current slice
mLiveData.observeForever(this);
Dependency.get(ConfigurationController.class).addCallback(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mLiveData.removeObserver(this);
Dependency.get(ConfigurationController.class).removeCallback(this);
}
因KeyguardSliceView实现了Observer,所以一旦slice控件发生变化,就会回调到onChange(),从而触发刷新页面。
/**
* LiveData observer lifecycle.
* @param slice the new slice content.
*/
@Override
public void onChanged(Slice slice) {
mSlice = slice;
showSlice();
}
最后看showSlice()的代码:
private void showSlice() {
Trace.beginSection("KeyguardSliceView#showSlice");
if (mPulsing || mSlice == null) {
mTitle.setVisibility(GONE);
mRow.setVisibility(GONE);
if (mContentChangeListener != null) {
mContentChangeListener.run();
}
return;
}
ListContent lc = new ListContent(getContext(), mSlice);
mHasHeader = lc.hasHeader();
List subItems = new ArrayList();
for (int i = 0; i < lc.getRowItems().size(); i++) { //逐条解析mSlice中的slice
SliceItem subItem = lc.getRowItems().get(i);
String itemUri = subItem.getSlice().getUri().toString();
// Filter out the action row
if (!KeyguardSliceProvider.KEYGUARD_ACTION_URI.equals(itemUri)) {
subItems.add(subItem); //如果KeyguardSliceProvider中存在相应的Uri,上面有提及初始化的情况,个人理解就是需要显示slice控件的app,需要在provider中注册
}
}
if (!mHasHeader) {
mTitle.setVisibility(GONE);
} else {
mTitle.setVisibility(VISIBLE);
// If there's a header it'll be the first subitem
RowContent header = new RowContent(getContext(), subItems.get(0),
true /* showStartItem */);
SliceItem mainTitle = header.getTitleItem();
CharSequence title = mainTitle != null ? mainTitle.getText() : null;
mTitle.setText(title);
}
mClickActions.clear();
final int subItemsCount = subItems.size();
final int blendedColor = getTextColor();
final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
for (int i = startIndex; i < subItemsCount; i++) {
SliceItem item = subItems.get(i);
RowContent rc = new RowContent(getContext(), item, true /* showStartItem */);
final Uri itemTag = item.getSlice().getUri();
// Try to reuse the view if already exists in the layout
KeyguardSliceButton button = mRow.findViewWithTag(itemTag); //将获取的URi解析后,逐条填充到KeyguardSliceButton,这也是一个自定义控件
if (button == null) {
button = new KeyguardSliceButton(mContext);
button.setTextColor(blendedColor);
button.setTag(itemTag);
final int viewIndex = i - (mHasHeader ? 1 : 0);
mRow.addView(button, viewIndex);
}
PendingIntent pendingIntent = null;
if (rc.getPrimaryAction() != null) {
pendingIntent = rc.getPrimaryAction().getAction(); //可跳转操作
}
mClickActions.put(button, pendingIntent);
final SliceItem titleItem = rc.getTitleItem();
button.setText(titleItem == null ? null : titleItem.getText());
button.setContentDescription(rc.getContentDescription());
Drawable iconDrawable = null;
SliceItem icon = SliceQuery.find(item.getSlice(),
android.app.slice.SliceItem.FORMAT_IMAGE);
if (icon != null) {
iconDrawable = icon.getIcon().loadDrawable(mContext);
final int width = (int) (iconDrawable.getIntrinsicWidth()
/ (float) iconDrawable.getIntrinsicHeight() * mIconSize);
iconDrawable.setBounds(0, 0, Math.max(width, 1), mIconSize);
}
button.setCompoundDrawables(iconDrawable, null, null, null); //这里填充slice icon,就是我们见到的显示的slice 图标,如闹钟。
button.setOnClickListener(this);
button.setClickable(pendingIntent != null);
}
// Removing old views
for (int i = 0; i < mRow.getChildCount(); i++) {
View child = mRow.getChildAt(i);
if (!mClickActions.containsKey(child)) {
mRow.removeView(child);
i--;
}
}
if (mContentChangeListener != null) {
mContentChangeListener.run();
}
Trace.endSection();
}
整个流程分析结束,现在回过头来看bug:为什么锁屏上的date会消失呢。
看上方showSlice()代码,mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);,我们在前文看到,mRow实际上就是显示日期,闹钟,勿扰图标的,那么在不显示的情况下,可以确定subItemsCount = 0的,那么往上追,它的count值,是由onChange时,获取到的mLiveDate的条目数决定的,而liveDate中的slice,是由StringUri解析而来:
mKeyguardSliceUri = Uri.parse(uriString);
mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
因此,可以确定,在某种情况下, mKeyguardSliceUri 解析出了问题,此时,我们打印出 mKeyguardSliceUri 的值,发现值为:
content://com.android.systemui.keyguard/main
这个值,在KeyguardSliceProvider.java中,定义为:
public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
这时,我们发现,KEYGUARD_SLICE_URI正是在setupUri时,默认填充了,所以Slice组件就认为此处无任何row图标,因此只要默认填充date的URI,就可以发现,每次date都是默认填充的了,查看KeyguardSliceProvider.java,它的值是:
public static final String KEYGUARD_DATE_URI = "content://com.android.systemui.keyguard/date";
因此,代码修改为:
public void setupUri(String uriString) {
if (uriString == null) {
//uriString = KeyguardSliceProvider.KEYGUARD_SLICE_URI;
uriString = KeyguardSliceProvider.KEYGUARD_DATE_URI ;
}
boolean wasObserving = false;
if (mLiveData != null && mLiveData.hasActiveObservers()) {
wasObserving = true;
mLiveData.removeObserver(this);
}
mKeyguardSliceUri = Uri.parse(uriString);
mLiveData = SliceLiveData.fromUri(mContext, mKeyguardSliceUri);
if (wasObserving) {
mLiveData.observeForever(this);
}
}
编译后,问题解决。