SystemUI启动是在SystemServer进程之后启动的,android系统启动流程依次是:
从Boot RAM->BootLoader->Kenel->Init->Zygote->SystemServer->Launcher,SystemUI是在SystemServer进程中启动的,SystemServer是Zygote进程fork出来的,SystemServer.java文件查找路径方法,切换到工程目录下,
find ./ -name *SystemServer.java*
找到SystemServer之后,SystemServer的main方法如下:
//文件路径:
//./frameworks/base/services/java/com/android/server/SystemServer.java
public static void main(String[] args) {
new SystemServer().run();
}
这里面直接初始化后,调用run方法,在run方法里面启动的有三个Service的方法
// Start services.
try {
t.traceBegin("StartServices");
startBootstrapServices(t);//启动引导服务
startCoreServices(t);//启动核心服务
startOtherServices(t);//启动其他服务
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
t.traceEnd(); // StartServices
}
SystemUIService的启动是在startOtherService里面,
t.traceBegin("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
t.traceEnd();
调用startSystemUI方法,
private static void startSystemUi(Context context, WindowManagerService windowManager) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
Intent intent = new Intent();
intent.setComponent(pm.getSystemUiServiceComponent());
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
windowManager.onSystemUiStarted();
}
从localService里面拿到Service的名字,然后启动,拿到的Service名字是SystemUIService,SystemUIService执行onCreate方法,
//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
// For debugging RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
throw new RuntimeException();
}
if (Build.IS_DEBUGGABLE) {
// b/71353150 - looking for leaked binder proxies
BinderInternal.nSetBinderProxyCountEnabled(true);
BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
BinderInternal.setBinderProxyCountCallback(
new BinderInternal.BinderProxyLimitListener() {
@Override
public void onLimitReached(int uid) {
Slog.w(SystemUIApplication.TAG,
"uid " + uid + " sent too many Binder proxies to uid "
+ Process.myUid());
}
}, mMainHandler);
}
// Bind the dump service so we can dump extra info during a bug report
startServiceAsUser(
new Intent(getApplicationContext(), SystemUIAuxiliaryDumpService.class),
UserHandle.SYSTEM);
前面是SystemUIService启动的方法,后面都是调试状态下的输出,等于是直接调用SystemUIApplication.java的startServicesIfNeeded方法,
//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
public void startServicesIfNeeded() {
String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
startServicesIfNeeded( "StartServices", names);
}
这个Service的列表是从SystemUIFactory里面取到的,
//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
public String[] getSystemUIServiceComponents(Resources resources) {
return resources.getStringArray(R.array.config_systemUIServiceComponents);
}
从res里面的array里面拿到需要启动的服务,这个服务的数组分为两种,一种是正常的res里面的value,另外一种是tv版的服务数组列表,内容如下:
//文件路径: //frameworks/base/packages/SystemUI/res/values-television/config.xml- com.android.systemui.util.NotificationChannels
//通知- com.android.systemui.volume.VolumeUI
//声音- com.android.systemui.stackdivider.Divider
//分屏- com.android.systemui.statusbar.tv.TvStatusBar
//电视状态栏- com.android.systemui.usb.StorageNotification
//存储通知- com.android.systemui.power.PowerUI
//电量界面- com.android.systemui.media.RingtonePlayer
//铃声播放器- com.android.systemui.keyboard.KeyboardUI
//键盘界面- com.android.systemui.pip.PipUI
//图片窗口中的图片- com.android.systemui.shortcut.ShortcutKeyDispatcher
//快捷分发器- @string/config_systemUIVendorServiceComponent
//供应商服务- com.android.systemui.SliceBroadcastRelayHandler
//允许打开设置app- com.android.systemui.SizeCompatModeActivityController
//允许重启activity- com.android.systemui.statusbar.notification.InstantAppNotifier
//即时应用程序通知- com.android.systemui.toast.ToastUI
//弹窗提示
需要启动的服务大概如上述所示,后面详细说明这些服务,
//文件路径:
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
private void startServicesIfNeeded(String metricsPrefix, String[] services) {
if (mServicesStarted) {
return;
}
mServices = new SystemUI[services.length];
if (!mBootCompleteCache.isBootComplete()) {
// check to see if maybe it was already completed long before we began
// see ActivityManagerService.finishBooting()
if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
mBootCompleteCache.setBootComplete();
if (DEBUG) {
Log.v(TAG, "BOOT_COMPLETED was already sent");
}
}
}
final DumpManager dumpManager = mRootComponent.createDumpManager();
Log.v(TAG, "Starting SystemUI services for user " +
Process.myUserHandle().getIdentifier() + ".");
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
Trace.TRACE_TAG_APP);
log.traceBegin(metricsPrefix);
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
if (DEBUG) Log.d(TAG, "loading: " + clsName);
log.traceBegin(metricsPrefix + clsName);
long ti = System.currentTimeMillis();
try {
SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
if (obj == null) {
Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
obj = (SystemUI) constructor.newInstance(this);
}
mServices[i] = obj;
} catch (ClassNotFoundException
| NoSuchMethodException
| IllegalAccessException
| InstantiationException
| InvocationTargetException ex) {
throw new RuntimeException(ex);
}
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
log.traceEnd();
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
}
//如果android系统已经启动的话,会有回调
if (mBootCompleteCache.isBootComplete()) {
mServices[i].onBootCompleted();
}
//这里是注册dump,用来使用命令dump时使用
dumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
}
mRootComponent.getInitController().executePostInitTasks();
log.traceEnd();
mServicesStarted = true;
}
在这个方法里面启动SystemUI的相关内容的,把存放进数组的SystemUI通过反射的形式初始化对象,然后每个都调用start方法启动,启动相关的SystemUI内容,之后把这些可以服务都通过dumpManager注册dump的服务,启动流程大致如此。
2.注册dump介绍之前的startServicesIfNeeded方法最后一行里面进行了注册,注册的代码如下:
//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@Synchronized
fun registerDumpable(name: String, module: Dumpable) {
if (!canAssignTonameLocked(name, module)) {
throw IllegalArgumentException("'$name' is already registered")
}
dumpables[name] = RegisteredDumpable(name, module)
}
这个里面存放的是MutableMap
其中Dump接口代码如下:
//文件路径
//./frameworks/base/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java
public interface Dumpable {
void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args);
}
代码注释中已经说明,实现这个接口主要是为了通过“adb shell dumpsys activity service com.android.systemui”这个命令dump内容,可以通过cmd控制台输出这个命令看一下输出,这个输出会根据子类重写的这个方法把相关的参数输出出来,从过滤出来的日志能够查到。
3.SystemUI不同的UI内容 1.简述SystemUI是顶层父类,实现了Dumpable接口用于dump相关内容,其他所有的SystemUI全部都是继承自这个类,
public abstract class SystemUI implements Dumpable {
protected final Context mContext;
public SystemUI(Context context) {
mContext = context;
}
//表示启动过程
public abstract void start();
//配置更新
protected void onConfigurationChanged(Configuration newConfig) {
}
//需要dump时使用
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
}
//开机启动后的回调
protected void onBootCompleted() {
}
//通知主题内容修改
public static void overrideNotificationAppName(Context context, Notification.Builder n,
boolean system) {
final Bundle extras = new Bundle();
String appName = system
? context.getString(com.android.internal.R.string.notification_app_name_system)
: context.getString(com.android.internal.R.string.notification_app_name_settings);
extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName);
n.addExtras(extras);
}
}
2.SystemUI分类
2.1通知管理:NotificationChannels
这个类是顶层通知栏类似于推送消息时,展示的内容,调用这个start方法之后,把需要用到的channelID创建出来,
final NotificationManager nm = context.getSystemService(NotificationManager.class);
final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,
context.getString(R.string.notification_channel_battery),
NotificationManager.importANCE_MAX);
final String soundPath = Settings.Global.getString(context.getContentResolver(),
Settings.Global.LOW_BATTERY_SOUND);
batteryChannel.setSound(Uri.parse("file://" + soundPath), new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
.build());
batteryChannel.setBlockable(true);
final NotificationChannel alerts = new NotificationChannel(
alertS,
context.getString(R.string.notification_channel_alerts),
NotificationManager.importANCE_HIGH);
final NotificationChannel general = new NotificationChannel(
GENERAL,
context.getString(R.string.notification_channel_general),
NotificationManager.importANCE_MIN);
final NotificationChannel storage = new NotificationChannel(
STORAGE,
context.getString(R.string.notification_channel_storage),
isTv(context)
? NotificationManager.importANCE_DEFAULT
: NotificationManager.importANCE_LOW);
final NotificationChannel hint = new NotificationChannel(
HINTS,
context.getString(R.string.notification_channel_hints),
NotificationManager.importANCE_DEFAULT);
// No need to bypass DND.
nm.createNotificationChannels(Arrays.asList(
alerts,
general,
storage,
createScreenshotChannel(
context.getString(R.string.notification_channel_screenshot),
nm.getNotificationChannel(SCREENSHOTS_LEGACY)),
batteryChannel,
hint
));
// Delete older SS channel if present.
// Screenshots promoted to heads-up in P, this cleans up the lower priority channel from O.
// This line can be deleted in Q.
nm.deleteNotificationChannel(SCREENSHOTS_LEGACY);
if (isTv(context)) {
// TV specific notification channel for TV PIP controls.
// importance should be {@link NotificationManager#importANCE_MAX} to have the highest
// priority, so it can be shown in all times.
nm.createNotificationChannel(new NotificationChannel(
TVPIP,
context.getString(R.string.notification_channel_tv_pip),
NotificationManager.importANCE_MAX));
}
分别创建了电量,alerts,普通类型的消息,存储,hint,截屏,TVPIP的通知渠道,只是创建好,但是什么时候通知调用?
2.2音量UI
VolumeUI.java 是控制音量弹出框的,调用start方法后,设置是否可用的开关和警告提示的开关,主要流程涉及到的类包括:VolumeDialogComponent.java,VolumeDialog.java,VolumeDialogImpl.java,VolumeDialogController.java,VolumeDialogControllerImpl.java,AudioManager.java,大概流程如下,VolumeDialogComponent是一个dialog的组件,
@Inject
public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
VolumeDialogControllerImpl volumeDialogController) {
mContext = context;
mKeyguardViewMediator = keyguardViewMediator;
mController = volumeDialogController;
mController.setUserActivityListener(this);
// Allow plugins to reference the VolumeDialogController.
Dependency.get(PluginDependencyProvider.class)
.allowPluginDependency(VolumeDialogController.class);
Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class)
.withDefault(this::createDefault)//调用这个方法初始化
.withCallback(dialog -> {
if (mDialog != null) {
mDialog.destroy();
}
mDialog = dialog;
mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);//init设置一些内容
}).build();
applyConfiguration();
Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
VOLUME_SILENT_DO_NOT_DISTURB);
}
调用create创建dialog,
protected VolumeDialog createDefault() {
VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
impl.setStreamimportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
impl.setSilentMode(false);
return impl;
}
创建出来一个VolumeDialogImpl对象,VolumeDialogImpl是VolumeDialog接口的实现类,然后调用dialog的初始化,初始化的代码如下,
public void init(int windowType, Callback callback) {
initDialog();
mAccessibility.init();
mController.addCallback(mControllerCallbackH, mHandler);
mController.getState();
Dependency.get(ConfigurationController.class).addCallback(this);
}
其中initDialog()方法是调用这个dialog初始化的内容,这个里面是比较典型的dialog开发时的代码,不过也非常复杂,setContentView时传入的是R.layout.volume_dialog,这个layout是一个frameLayout和LinearLayout的组合,其中比较重要的地方,
这里面会把不同音量控制的开关添加到这个linearLayout里面,在initDialog中最终会调用addRow()方法,根据是否是单声道,如果不是单声道的话会添加很多的View到这个linearLayout里面,分为STREAM_ACCESSIBILITY,STREAM_MUSIC(音乐大小),STREAM_RING(铃声),STREAM_ALARM(系统铃声),STREAM_VOICE_CALL(通话声音),STREAM_BLUETOOTH_SCO(蓝牙,这个不能用),STREAM_SYSTEM(系统声音),这些需要控制的音量全部都会通过addRow()添加进来,addRow()方法如下,
private void addRow(int stream, int iconRes, int iconMuteRes, boolean important,
boolean defaultStream, boolean dynamic) {
if (D.BUG) Slog.d(TAG, "Adding row for stream " + stream);
VolumeRow row = new VolumeRow();
initRow(row, stream, iconRes, iconMuteRes, important, defaultStream);
mDialogRowsView.addView(row.view);
mRows.add(row);
}
这个里面会先调用初始化,其中VolumeRow这个类是个组合类,他是对这隔单个音量的信息的封装,
private static class VolumeRow {
private View view;
private TextView header;
private ImageButton icon;
private SeekBar slider;
private int stream;
private StreamState ss;
private long userAttempt; // last user-driven slider change
private boolean tracking; // tracking slider touch
private int requestedLevel = -1; // pending user-requested level via progress changed
private int iconRes;
private int iconMuteRes;
private boolean important;
private boolean defaultStream;
private ColorStateList cachedTint;
private int iconState; // from Events
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
private int animTargetProgress;
private int lastAudibleLevel = 1;
private frameLayout dndIcon;
}
在调用init初始化时,row中的view通过inflate(R.layout.volume_dialog_row)的形式构造一个View,xml里面有seekBar,这个SeekBar会注册监听器,是通过VolumeSeekBarChangeListener这个内部类实现的,具体回调的内容包括触摸反馈,状态改变,然后做相应的处理,然后动态的添加到volume_dialog_rows指定的viewGroup里面,还需要有一个List
VolumeDialogControllerImpl是Presenter层面的内容,这个类是VolumeDialogController接口的实现,也实现了dump接口用于dump需要输出的内容,在初始化时拿到AudioManager的服务,然后互相通信,AudioManager是音量的具体Model层,他负责音量数据源,他们之间的通信关系如下,
2.3Divider分屏
Divider是处理分屏的业务,构造方法如下:
public Divider(Context context, Optional> recentsOptionalLazy, DisplayController displayController, SystemWindows systemWindows, DisplayImeController imeController, Handler handler, KeyguardStateController keyguardStateController, TransactionPool transactionPool) { super(context); mDisplayController = displayController; mSystemWindows = systemWindows;//这个参数是当前的window,通过他处理添加分屏,删除分屏的操作 mImeController = imeController; mHandler = handler; mKeyguardStateController = keyguardStateController; mRecentsOptionalLazy = recentsOptionalLazy; mForcedResizableController = new ForcedResizableInfoActivityController(context, this); mTransactionPool = transactionPool; mWindowManagerProxy = new WindowManagerProxy(mTransactionPool, mHandler); mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mHandler); }
其中mSystemWindows是一个SystemWindows类的对象,主要就是通过他来控制分屏处理的,start方法如下:
@Override
public void start() {
mWindowManager = new DividerWindowManager(mSystemWindows);
mDisplayController.addDisplayWindowListener(this);
// Hide the divider when keyguard is showing. Even though keyguard/statusbar is above
// everything, it is actually transparent except for notifications, so we still need to
// hide any surfaces that are below it.
// TODO(b/148906453): Figure out keyguard dismiss animation for divider view.
mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
}
@Override
public void onKeyguardShowingChanged() {
if (!isSplitActive() || mView == null) {
return;
}
mView.setHidden(mKeyguardStateController.isShowing());
if (!mKeyguardStateController.isShowing()) {
mImePositionProcessor.updateAdjustForIme();
}
}
@Override
public void onKeyguardFadingAwayChanged() {
}
});
// Don't initialize the divider or anything until we get the default display.
}
start方法中构造了一个DividerWindowManager的对象,分屏通过这个windowManager操作添加屏幕,或者删除屏幕的操作,这里面还加了一个键盘的回调,从注释上的意思是说隐藏分屏当键盘展示的时候,真正负责处理添加删除的是DividerWindowManager,DividerWindowManager的构造方法入参是mSystemWindows,Divider类中方法比较多,其中addDivider()和removeDivider()的代码如下:
private void addDivider(Configuration configuration) {
Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
mView = (DividerView)
LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
mView.injectDependencies(mWindowManager, mDividerState, this, mSplits, mSplitLayout,
mImePositionProcessor, mWindowManagerProxy);
mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null );
final int size = dctx.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
final int width = landscape ? size : displayLayout.width();
final int height = landscape ? displayLayout.height() : size;
mWindowManager.add(mView, width, height, mContext.getDisplayId());
}
private void removeDivider() {
if (mView != null) {
mView.onDividerRemoved();
}
mWindowManager.remove();
}
其实都是通过windowManager进行的操作,每次add都是inflate出来一个view,通过windowManager添加进去,每次操作时都是先removeDivider然后再重新addDivider(),而removeDivider()操作在多处调用,其中一处如下:
private void update(Configuration configuration) {
final boolean isDividerHidden = mView != null && mKeyguardStateController.isShowing();
removeDivider();
addDivider(configuration);
if (mMinimized) {
mView.setMinimizedDockStack(true, mHomeStackResizable, null );
updateTouchable();
}
mView.setHidden(isDividerHidden);
}
先删除,再添加,其中操作这个VIew的时候,这个VIew的名字是DividerView,这个是个自定义View,里面处理拖拽,触摸等一系列操作。
2.4TvStatusBar电视状态栏
TvStatusBar也是一个状态栏的通知,主要处理隐式意图跳转的逻辑,start方法如下:
@Override
public void start() {
final IStatusBarService barService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mCommandQueue.addCallback(this);
try {
barService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
// If the system process isn't there we're doomed anyway.
}
}
这里面进行了一个绑定服务,添加了一个回调,其中mCommandQueue是构造方法传递的参数,添加完回调后主要是为了方便通知,mCommandQueue是CommandQueue.java的实例对象,这个类比较庞大,里面有一个名为H的handler用于发送消息,TvStatusBar实现的接口是CommandQueue.Callbacks,这个类就是CommandQueue.java的一个接口,定义了非常多的方法用于循环通知回调,列举一部分大概确认下内容:
default void setIcon(String slot, StatusBarIcon icon) { }
default void removeIcon(String slot) { }
default void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
boolean animate) { }
default void animateExpandNotificationsPanel() { }
default void animateCollapsePanels(int flags, boolean force) { }
default void togglePanel() { }
default void animateExpandSettingsPanel(String obj) { }
default void setImeWindowStatus(int displayId, IBinder token, int vis,
@BackDispositionMode int backDisposition, boolean showImeSwitcher) { }
而TvStatusBar只实现了其中两个方法,分别是是animateExpandNotificationsPanel()和startAssist(),animateExpandNotificationsPanel用于启动系统的activity,startAssist用于打开assist里面的内容。在CommandQueue中的H中调用animateExpandNotificationsPanel方法,
case MSG_EXPAND_NOTIFICATIONS:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).animateExpandNotificationsPanel();
}
发现这个MSG_EXPAND_NOTIFICATIONS类型的消息调用的方法在本模块没有被调用到:
public void animateExpandNotificationsPanel() {
synchronized (mLock) {
mHandler.removeMessages(MSG_EXPAND_NOTIFICATIONS);
mHandler.sendEmptyMessage(MSG_EXPAND_NOTIFICATIONS);
}
}
2.5StorageNotification存储通知
StorageNotification.java主要是处理存储相关的通知,其中start方法的内容如下:
@Override
public void start() {
mNotificationManager = mContext.getSystemService(NotificationManager.class);//获取通知服务
mStorageManager = mContext.getSystemService(StorageManager.class);
mStorageManager.registerListener(mListener);//这里注册了一个监听器
mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME),
android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);//
mContext.registerReceiver(mFinishReceiver, new IntentFilter(ACTION_FINISH_WIZARD),
android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null);//完成的广播
// Kick current state into place
final List disks = mStorageManager.getDisks();
for (DiskInfo disk : disks) {
onDiskScannedInternal(disk, disk.volumeCount);
}
final List vols = mStorageManager.getVolumes();
for (VolumeInfo vol : vols) {
onVolumeStateChangedInternal(vol);
}
mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler());//这里也加了个回调
updateMissingPrivateVolumes();
}
主要看注册的监听器,其中mListener是个检测存储状态改变的回调类,
private final StorageEventListener mListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
onVolumeStateChangedInternal(vol);
}
@Override
public void onVolumeRecordChanged(VolumeRecord rec) {
// Avoid kicking notifications when getting early metadata before
// mounted. If already mounted, we're being kicked because of a
// nickname or init'ed change.
final VolumeInfo vol = mStorageManager.findVolumeByUuid(rec.getFsUuid());
if (vol != null && vol.isMountedReadable()) {
onVolumeStateChangedInternal(vol);
}
}
@Override
public void onVolumeForgotten(String fsUuid) {
// Stop annoying the user
mNotificationManager.cancelAsUser(fsUuid, SystemMessage.NOTE_STORAGE_PRIVATE,
UserHandle.ALL);
}
@Override
public void onDiskScanned(DiskInfo disk, int volumeCount) {
onDiskScannedInternal(disk, volumeCount);
}
@Override
public void onDiskDestroyed(DiskInfo disk) {
onDiskDestroyedInternal(disk);
}
};
状态改变的时候会调用onVolumeStateChangedInternal这个方法,继续调用:
private void onVolumeStateChangedInternal(VolumeInfo vol) {
switch (vol.getType()) {
case VolumeInfo.TYPE_PRIVATE:
onPrivateVolumeStateChangedInternal(vol);
break;
case VolumeInfo.TYPE_PUBLIC:
onPublicVolumeStateChangedInternal(vol);
break;
}
}
继续调用onPublicVolumeStateChangedInternal,在这个方法里面会处理各种不同状态的通知:
private void onPublicVolumeStateChangedInternal(VolumeInfo vol) {
Log.d(TAG, "Notifying about public volume: " + vol.toString());
// Volume state change event may come from removed user, in this case, mountedUserId will
// equals to UserHandle.USER_NULL (-10000) which will do nothing when call cancelAsUser(),
// but cause crash when call notifyAsUser(). Here we return directly for USER_NULL, and
// leave all notifications belong to removed user to NotificationManagerService, the latter
// will remove all notifications of the removed user when handles user stopped broadcast.
if (isAutomotive() && vol.getMountUserId() == UserHandle.USER_NULL) {
Log.d(TAG, "Ignore public volume state change event of removed user");
return;
}
final Notification notif;
switch (vol.getState()) {
case VolumeInfo.STATE_UNMOUNTED:
//卸载
notif = onVolumeUnmounted(vol);
break;
case VolumeInfo.STATE_CHECKING:
//检测
notif = onVolumeChecking(vol);
break;
case VolumeInfo.STATE_MOUNTED:
case VolumeInfo.STATE_MOUNTED_READ_ONLY:
//挂载
notif = onVolumeMounted(vol);
break;
case VolumeInfo.STATE_FORMATTING:
//对其
notif = onVolumeFormatting(vol);
break;
case VolumeInfo.STATE_EJECTING:
//拒绝
notif = onVolumeEjecting(vol);
break;
case VolumeInfo.STATE_UNMOUNTABLE:
//不可被卸载的
notif = onVolumeUnmountable(vol);
break;
case VolumeInfo.STATE_REMOVED:
//移除
notif = onVolumeRemoved(vol);
break;
case VolumeInfo.STATE_BAD_REMOVAL:
//错误的移除
notif = onVolumeBadRemoval(vol);
break;
default:
notif = null;
break;
}
if (notif != null) {
mNotificationManager.notifyAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC,
notif, UserHandle.of(vol.getMountUserId()));
} else {
mNotificationManager.cancelAsUser(vol.getId(), SystemMessage.NOTE_STORAGE_PUBLIC,
UserHandle.of(vol.getMountUserId()));
}
}
处理完成之后用notificationManager做通知,后面会有对应的方法做通知的生成,这个类里面也会有很多生成PendingIntent的方法,主要是为了配合生成notificaiton的点击跳转的intent,方法名大致如下:buildInitPendingIntent(),buildUnmountPendingIntent(),buildBrowsePendingIntent(),buildVolumeSettingsPendingIntent(),buildSnoozeIntent(),buildForgetPendingIntent(),buildWizardMigratePendingIntent(),buildWizardMovePendingIntent(),buildWizardReadyPendingIntent(),这些方法名字根据英文翻译大概能确认要生成的intent的内容。
2.6PowerUI电量界面
PowerUI.java是处理关于电量部分得相关内容,都会调用start启用这个服务,其中start的代码如下:
public void start() {
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
mWarnings = Dependency.get(WarningsUI.class);//通过这个更新UI的相关内容
mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
mLastConfiguration.setTo(mContext.getResources().getConfiguration());
ContentObserver obs = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
updateBatteryWarningLevels();
}
};
final ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
false, obs, UserHandle.USER_ALL);
updateBatteryWarningLevels();
mReceiver.init();//调用receiver初始化接收这些状态
// Check to see if we need to let the user know that the phone previously shut down due
// to the temperature being too high.
showWarnonThermalShutdown();
// Register an observer to configure mEnableSkinTemperatureWarning and perform the
// registration of skin thermal event listener upon Settings change.
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SHOW_TEMPERATURE_WARNING),
false ,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
doSkinThermalEventListenerRegistration();
}
});
// Register an observer to configure mEnableUsbTemperatureAlarm and perform the
// registration of usb thermal event listener upon Settings change.
resolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.SHOW_USB_TEMPERATURE_ALARM),
false ,
new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
doUsbThermalEventListenerRegistration();
}
});
initThermalEventListeners();
mCommandQueue.addCallback(this);
}
初始化时调用showWarnOnThermalShutdown这个方法检查上次关机是不是因为温度太高,注册手机温度过高的回调,doSkinThermalEventListenerRegistration方法做手机温度过高的处理,调用doUsbThermalEventListenerRegistration这个方法处理USB接口温度过高的处理,其中receiver接受做的是电量的逻辑处理,如果需要用到处理UI的相关内容,是通过mWarnings做的界面更新内容,mWarnings是一个抽象接口,
public interface WarningsUI {
void update(int batteryLevel, int bucket, long screenOffTime);
void dismissLowBatteryWarning();
void showLowBatteryWarning(boolean playSound);
void dismissInvalidChargerWarning();
void showInvalidChargerWarning();
void updateLowBatteryWarning();
boolean isInvalidChargerWarningShowing();
void dismissHighTemperatureWarning();
void showHighTemperatureWarning();
void showUsbHighTemperatureAlarm();
void showThermalShutdownWarning();
void dump(PrintWriter pw);
void userSwitched();
void updateSnapshot(BatteryStateSnapshot snapshot);
}
他的实现类是PowerNotificationWarnings.java,这个里面做实际的电量界面更新的内容,其中的方法名字比较好理解:
showThermalShutdownWarning() showUsbHighTemperatureAlarm() showUsbHighTemperatureAlarmInternal() updateLowBatteryWarning() dismissLowBatteryWarning() hasBatterySettings() dismissInvalidChargerWarning() showInvalidChargerWarning() dismissAutoSaverSuggestion()
//还有需要比较好理解的方法名,能够直译过来方便理解,仅列举这么多
这个PowerNotificationWarnings的初始化是在PowerUI里面初始化的,通过Dependency拿到的一个单例对象。
2.7RingtonePlayer铃声播放器
RingtonePlayer是处理铃声播放器的类,start方法如下:
@Override
public void start() {
mAsyncPlayer.setUsesWakeLock(mContext);
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
try {
mAudioService.setRingtonePlayer(mCallback);
} catch (RemoteException e) {
Log.e(TAG, "Problem registering RingtonePlayer: " + e);
}
}
通过AIDL获取完AudioService之后设置了一个回调,mCallback处理铃声的相关逻辑:
private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
@Override
public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
throws RemoteException {
playWithVolumeShaping(token, uri, aa, volume, looping, null);
}
@Override
public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
throws RemoteException {
if (LOGD) {
Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+ Binder.getCallingUid() + ")");
}
Client client;
synchronized (mClients) {
client = mClients.get(token);
if (client == null) {
final UserHandle user = Binder.getCallingUserHandle();
client = new Client(token, uri, user, aa, volumeShaperConfig);
token.linkToDeath(client, 0);
mClients.put(token, client);
}
}
client.mRingtone.setLooping(looping);
client.mRingtone.setVolume(volume);
client.mRingtone.play();
}
@Override
public void stop(IBinder token) {
if (LOGD) Log.d(TAG, "stop(token=" + token + ")");
Client client;
synchronized (mClients) {
client = mClients.remove(token);
}
if (client != null) {
client.mToken.unlinkToDeath(client, 0);
client.mRingtone.stop();
}
}
@Override
public boolean isPlaying(IBinder token) {
if (LOGD) Log.d(TAG, "isPlaying(token=" + token + ")");
Client client;
synchronized (mClients) {
client = mClients.get(token);
}
if (client != null) {
return client.mRingtone.isPlaying();
} else {
return false;
}
}
@Override
public void setPlaybackProperties(IBinder token, float volume, boolean looping) {
Client client;
synchronized (mClients) {
client = mClients.get(token);
}
if (client != null) {
client.mRingtone.setVolume(volume);
client.mRingtone.setLooping(looping);
}
// else no client for token when setting playback properties but will be set at play()
}
@Override
public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
if (LOGD) Log.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Async playback only available from system UID.");
}
if (UserHandle.ALL.equals(user)) {
user = UserHandle.SYSTEM;
}
mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);
}
@Override
public void stopAsync() {
if (LOGD) Log.d(TAG, "stopAsync()");
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Async playback only available from system UID.");
}
mAsyncPlayer.stop();
}
@Override
public String getTitle(Uri uri) {
final UserHandle user = Binder.getCallingUserHandle();
return Ringtone.getTitle(getContextForUser(user), uri,
false , false );
}
@Override
public ParcelFileDescriptor openRingtone(Uri uri) {
final UserHandle user = Binder.getCallingUserHandle();
final ContentResolver resolver = getContextForUser(user).getContentResolver();
// only open the requested Uri if it's a well-known ringtone or
// other sound from the platform media store, otherwise this opens
// up arbitrary access to any file on external storage.
if (uri.toString().startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) {
try (Cursor c = resolver.query(uri, new String[] {
MediaStore.Audio.AudioColumns.IS_RINGTONE,
MediaStore.Audio.AudioColumns.IS_ALARM,
MediaStore.Audio.AudioColumns.IS_NOTIFICATION
}, null, null, null)) {
if (c.moveToFirst()) {
if (c.getInt(0) != 0 || c.getInt(1) != 0 || c.getInt(2) != 0) {
try {
return resolver.openFileDescriptor(uri, "r");
} catch (IOException e) {
throw new SecurityException(e);
}
}
}
}
}
throw new SecurityException("Uri is not ringtone, alarm, or notification: " + uri);
}
}
如上所示,这个方法名命名的比较容易理解,当调用play时,从mClients中取到binder对应的client,mClients是一个HashMap
setAudioAttributes() setLooping() setVolume() setUri(Uri uri) play() stop()
但是这个类上层是无法操作的,试过之后,这个类只能在系统里面操作。
2.8KeyboardUI键盘界面
KeyboardUI是在androidTV版配置下才存在的服务,主要是为了处理蓝牙键盘,这个蓝牙键盘在手机上是不可以使用的,里面通过蓝牙连接外设键盘,通过广播的形式处理锁屏,关机的对话框,SystemUIDialog是一个alertDialog,状态改变时会设置不同的window的属性,内部的receiver代码如下:
public static void registerDismissListener(Dialog dialog) {
DismissReceiver dismissReceiver = new DismissReceiver(dialog);
dialog.setonDismissListener(d -> dismissReceiver.unregister());
dismissReceiver.register();
}
private static class DismissReceiver extends BroadcastReceiver {
private static final IntentFilter INTENT_FILTER = new IntentFilter();
static {
INTENT_FILTER.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
INTENT_FILTER.addAction(Intent.ACTION_SCREEN_OFF);
}
private final Dialog mDialog;
private boolean mRegistered;
private final BroadcastDispatcher mBroadcastDispatcher;
DismissReceiver(Dialog dialog) {
mDialog = dialog;
mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
}
void register() {
mBroadcastDispatcher.registerReceiver(this, INTENT_FILTER, null, UserHandle.CURRENT);
mRegistered = true;
}
void unregister() {
if (mRegistered) {
mBroadcastDispatcher.unregisterReceiver(this);
mRegistered = false;
}
}
@Override
public void onReceive(Context context, Intent intent) {
mDialog.dismiss();
}
}
这就是一个简单的广播接收器,收到广播的时候dialog取消,BluetoothDialog继承SystemUIdialog,只是简单的继承,并没有做什么处理,
public class BluetoothDialog extends SystemUIDialog {
public BluetoothDialog(Context context) {
super(context);
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
setShowForAllUsers(true);
}
}
KeyboardUI.java文件里面持有这个BluetoothDialog的对象,用于监听蓝牙的响应做出相应的弹框处理,KeyBoardUI.java和之前的类似,start方法如下:
public void start() {
mContext = super.mContext;
HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mHandler = new KeyboardHandler(thread.getLooper());//创建
mHandler.sendEmptyMessage(MSG_INIT);//发送初始化消息
}
这里面创建了个mHandler,是处理键盘的回调的,实例化的代码如下:
private final class KeyboardHandler extends Handler {
public KeyboardHandler(Looper looper) {
super(looper, null, true );
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
//初始化
case MSG_INIT: {
init();
break;
}
//开机启动回调
case MSG_ON_BOOT_COMPLETED: {
onBootCompletedInternal();
break;
}
//状态改变回调
case MSG_PROCESS_KEYBOARD_STATE: {
processKeyboardState();
break;
}
//蓝牙可用不可用的回调
case MSG_ENABLE_BLUETOOTH: {
boolean enable = msg.arg1 == 1;
if (enable) {
mLocalBluetoothAdapter.enable();
} else {
mState = STATE_USER_CANCELLED;
}
break;
}
//ble扫描
case MSG_BLE_ABORT_SCAN: {
int scanAttempt = msg.arg1;
bleAbortScanInternal(scanAttempt);
break;
}
//蓝牙状态改变
case MSG_ON_BLUETOOTH_STATE_CHANGED: {
int bluetoothState = msg.arg1;
onBluetoothStateChangedInternal(bluetoothState);
break;
}
//蓝牙设备绑定状态改变
case MSG_ON_DEVICE_BOND_STATE_CHANGED: {
CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj;
int bondState = msg.arg1;
onDeviceBondStateChangedInternal(d, bondState);
break;
}
//添加蓝牙设备
case MSG_ON_BLUETOOTH_DEVICE_ADDED: {
BluetoothDevice d = (BluetoothDevice)msg.obj;
CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d);
onDeviceAddedInternal(cachedDevice);
break;
}
//ble扫描失败
case MSG_ON_BLE_SCAN_FAILED: {
onBleScanFailedInternal();
break;
}
//显示错误
case MSG_SHOW_ERROR: {
Pair p = (Pair) msg.obj;
onShowErrorInternal(p.first, p.second, msg.arg1);
}
}
}
}
mHandler创建完成之后,发送了一个初始化的message,走进init方法,init代码如下:
// Shoud only be called on the handler thread
private void init() {
Context context = mContext;
mKeyboardName =
context.getString(com.android.internal.R.string.config_packagedKeyboardName);
if (TextUtils.isEmpty(mKeyboardName)) {
if (DEBUG) {
Slog.d(TAG, "No packaged keyboard name given.");
}
return;
}
LocalBluetoothManager bluetoothManager = Dependency.get(LocalBluetoothManager.class);
if (bluetoothManager == null) {
if (DEBUG) {
Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance");
}
return;
}
mEnabled = true;
mCachedDeviceManager = bluetoothManager.getCachedDeviceManager();
mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter();
mProfileManager = bluetoothManager.getProfileManager();
bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler());
BluetoothUtils.setErrorListener(new BluetoothErrorListener());
InputManager im = context.getSystemService(InputManager.class);
im.registeronTabletModeChangedListener(this, mHandler);
mInTabletMode = im.isInTabletMode();
processKeyboardState();
mUIHandler = new KeyboardUIHandler();//又创建了个更新键盘UI的handler
}
init方法主要拿缓冲设备,蓝牙,输入信息,然后又创建了个mUIHandler用于更新UI相关内容,mUiHandler的代码如下:
private final class KeyboardUIHandler extends Handler {
public KeyboardUIHandler() {
super(Looper.getMainLooper(), null, true );
}
@Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_SHOW_BLUETOOTH_DIALOG: {
if (mDialog != null) {
// Don't show another dialog if one is already present
break;
}
DialogInterface.onClickListener clickListener =
new BluetoothDialogClickListener();
DialogInterface.onDismissListener dismissListener =
new BluetoothDialogDismissListener();
mDialog = new BluetoothDialog(mContext);//dialog是之前的蓝牙dialog
mDialog.setTitle(R.string.enable_bluetooth_title);
mDialog.setMessage(R.string.enable_bluetooth_message);
mDialog.setPositiveButton(
R.string.enable_bluetooth_/confirm/iation_ok, clickListener);
mDialog.setNegativeButton(android.R.string.cancel, clickListener);
mDialog.setonDismissListener(dismissListener);
mDialog.show();
break;
}
case MSG_DISMISS_BLUETOOTH_DIALOG: {
if (mDialog != null) {
mDialog.dismiss();
}
break;
}
}
}
}
这个比较简单,就是做弹框,取消弹框的处理,整体来看这个KeyboardUI里面有很多的回调,大概列举下名字如下:
processKeyboardState() onBootCompletedInternal() showBluetoothDialog() getPairedKeyboard() getDiscoveredKeyboard() getCachedBluetoothDevice() startScanning() stopScanning() bleAbortScanInternal() onDeviceAddedInternal() onBluetoothStateChangedInternal() onBleScanFailedInternal() onShowErrorInternal()
整体来看这些名字全部都和蓝牙有关系。
2.9PipUI图片窗口中的图片
PipUI是android系统处理画中画的一个SystemUI,构造方法中传入commonQueue的对象和mPipManager,mPipManager用于处理画中画的操作,PipUI重写的start方法如下:
@Override
public void start() {
PackageManager pm = mContext.getPackageManager();
boolean supportsPip = pm.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
if (!supportsPip) {
return;
}
// Ensure that we are the primary user's SystemUI.
final int processUser = UserManager.get(mContext).getUserHandle();
if (processUser != UserHandle.USER_SYSTEM) {
throw new IllegalStateException("Non-primary Pip component not currently supported.");
}
mCommandQueue.addCallback(this);
}
拿到packageManager判断是否是主用户的SystemUI,之后把当前对象添加到mCommandQueue的回调中,这个类回调只做了一个处理,
@Override
public void showPictureInPictureMenu() {
mPipManager.showPictureInPictureMenu();
}
public void expandPip() {
mPipManager.expandPip();
}
显示画中画的菜单,下面的收起画中画的操作,都是通过pipManager做的操作的。
2.10ShortcutKeyDispatcher快捷分发器
ShortcutKeyDispatcher这个类主要是提供快捷功能的,构造方法如下:
@Inject
public ShortcutKeyDispatcher(Context context, Divider divider, Recents recents) {
super(context);
mDivider = divider;
mRecents = recents;
}
传入分屏对象,最近使用的app进程对象,在start方法中注册内容:
@Override
public void start() {
registerShortcutKey(SC_DOCK_LEFT);
registerShortcutKey(SC_DOCK_RIGHT);
}
注册方法如下:
public void registerShortcutKey(long shortcutCode) {
try {
mWindowManagerService.registerShortcutKey(shortcutCode, mShortcutKeyServiceProxy);
} catch (RemoteException e) {
// Do nothing
}
}
通过wms注册快捷进入的功能,当点击的时候触发下面的方法:
@Override
public void onShortcutKeyPressed(long shortcutCode) {
int orientation = mContext.getResources().getConfiguration().orientation;
if ((shortcutCode == SC_DOCK_LEFT || shortcutCode == SC_DOCK_RIGHT)
&& orientation == Configuration.ORIENTATION_LANDSCAPE) {
handleDockKey(shortcutCode);
}
}
继续调用handleDockKey方法:
private void handleDockKey(long shortcutCode) {
if (mDivider == null || !mDivider.isDividerVisible()) {
// Split the screen
mRecents.splitPrimaryTask((shortcutCode == SC_DOCK_LEFT)
? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
: SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
} else {
// If there is already a docked window, we respond by resizing the docking pane.
DividerView dividerView = mDivider.getView();
DividerSnapAlgorithm snapAlgorithm = dividerView.getSnapAlgorithm();
int dividerPosition = dividerView.getCurrentPosition();
DividerSnapAlgorithm.SnapTarget currentTarget =
snapAlgorithm.calculateNonDismissingSnapTarget(dividerPosition);
DividerSnapAlgorithm.SnapTarget target = (shortcutCode == SC_DOCK_LEFT)
? snapAlgorithm.getPreviousTarget(currentTarget)
: snapAlgorithm.getNextTarget(currentTarget);
dividerView.startDragging(true , false );
dividerView.stopDragging(target.position, 0f, false ,
true );
}
}
判断分屏状态,如果当前分屏是空,或者分屏不可见的情况下,做分屏处理,其他的情况重新测量这个view做处理。
2.11VendorServices供应商服务
这个里面是空实现,是为了给供应商留空白的位置,暂时什么都没有。
2.12SliceBroadcastRelayHandler允许打开设置App
SliceBroadcastRelayHandler.java,这个类是通过广播实现的,注释里面的意思是允许设置注册某些广播来启动设置的app,start方法如下:
@Override
public void start() {
if (DEBUG) Log.d(TAG, "Start");
IntentFilter filter = new IntentFilter(SliceBroadcastRelay.ACTION_REGISTER);
filter.addAction(SliceBroadcastRelay.ACTION_UNREGISTER);
mBroadcastDispatcher.registerReceiver(mReceiver, filter);
}
这里面同光广播分发器注册了一个receiver,其中receiver的handleIntent()代码如下:
// This does not use BroadcastDispatcher as the filter may have schemas or mime types.
@VisibleForTesting
void handleIntent(Intent intent) {
if (SliceBroadcastRelay.ACTION_REGISTER.equals(intent.getAction())) {
Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
ComponentName receiverClass =
intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_RECEIVER);
IntentFilter filter = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_FILTER);
if (DEBUG) Log.d(TAG, "Register " + uri + " " + receiverClass + " " + filter);
getOrCreateRelay(uri).register(mContext, receiverClass, filter);
} else if (SliceBroadcastRelay.ACTION_UNREGISTER.equals(intent.getAction())) {
Uri uri = intent.getParcelableExtra(SliceBroadcastRelay.EXTRA_URI);
if (DEBUG) Log.d(TAG, "Unregister " + uri);
BroadcastRelay relay = getAndRemoveRelay(uri);
if (relay != null) {
relay.unregister(mContext);
}
}
}
里面做了两个处理,第一个是注册,第二个是取消注册,通过uri从mRelays里面拿到一个BroadcastRelay类型的对象,做注册和取消注册注册的操作,BroadcastRelay也是一个广播其中的onReceive的代码如下:
@Override
public void onReceive(Context context, Intent intent) {
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
for (ComponentName receiver : mReceivers) {
intent.setComponent(receiver);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, mUri.toString());
if (DEBUG) Log.d(TAG, "Forwarding " + receiver + " " + intent + " " + mUserId);
context.sendBroadcastAsUser(intent, mUserId);
}
}
这里面做的操作比较简单,就是把这个广播全部都发送出去,多添加了一个EXTRA_URI的标记。
2.13SizeCompatModeActivityController允许重新启动activity的控制器
SizeCompatModeActivityController这个类主要是做重新启动activity的作用,构造方法中的处理如下:
@VisibleForTesting
@Inject
SizeCompatModeActivityController(Context context, ActivityManagerWrapper am,
CommandQueue commandQueue) {
super(context);
mCommandQueue = commandQueue;
am.registerTaskStackListener(new TaskStackChangeListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
// Note the callback already runs on main thread.
updateRestartButton(displayId, activityToken);
}
});
}
传入commandQueue,am中注册重新启动activity的按钮,任务栈发生改变的监听,updateRestartButton会更新一个Button或者创建一个button,这个button的onClick事件如下:
@Override
public void onClick(View v) {
try {
ActivityTaskManager.getService().restartActivityProcessIfVisible(
mLastActivityToken);
} catch (RemoteException e) {
Log.w(TAG, "Unable to restart activity", e);
}
}
拿到taskManager服务重启activity,入参是mLastActivityToken,这个参数就是上面需要变动的activityToken,SizeCompatModeActivityController的start方法如下:
@Override
public void start() {
mCommandQueue.addCallback(this);
}
跟之前的操作类似,添加自身作为回调,重写的方法有两个:
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
RestartActivityButton button = mActiveButtons.get(displayId);
if (button == null) {
return;
}
boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0;
int newVisibility = imeShown ? View.GONE : View.VISIBLE;
// Hide the button when input method is showing.
if (button.getVisibility() != newVisibility) {
button.setVisibility(newVisibility);
}
}
@Override
public void onDisplayRemoved(int displayId) {
mDisplayContextCache.remove(displayId);
removeRestartButton(displayId);
}
这两个方法都是在控制button显示或者不显示。
2.14InstantAppNotifier即时应用程序通知
InstantAppNotifier是即时应用程序的通知,即时应用程序的概念和微信小程序类似, “快应用”标准是九家手机厂商基于硬件平台共同推出的新型应用生态,快应用使用前端技术栈开发,原生渲染,同时具备H5页面和原生应用的双重优点。用户无需下载安装,即点即用,享受原生应用的性能体验。
-
快应用是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的快应用联盟联合制定。
-
快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台,以平台化的生态模式对个人开发者和企业开发者全品类开放。
-
快应用具备传统APP完整的应用体验,无需安装、即点即用。
简单的说就是允许用户免安装直接打开网页做对应的用户操作,打开android应用商店搜索“快应用”能够直接搜索到, 主要是针对小型app,大型app在这个快应用中心并没有,本身针对的体量就比较小,快应用开发参考文档地址:
[快应用开发参考文档] https://www.w3cschool.cn/quickapp/quickapp-6ams2o7m.html no
这个类的start方法中对键盘,分屏,通知做了初始化,代码如下:
@Override
public void start() {
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
// listen for user / profile change.
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchListener, TAG);
} catch (RemoteException e) {
// Ignore
}
mCommandQueue.addCallback(this);
mKeyguardStateController.addCallback(this);
mDivider.registerInSplitScreenListener(
exists -> {
mDockedStackExists = exists;
updateForegroundInstantApps();
});
// Clear out all old notifications on startup (only present in the case where sysui dies)
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
for (StatusBarNotification notification : noMan.getActiveNotifications()) {
if (notification.getId() == SystemMessage.NOTE_INSTANT_APPS) {
noMan.cancel(notification.getTag(), notification.getId());
}
}
}
后面调用通知的方法名是:
updateForegroundInstantApps//(通知前台快应用的app) checkAndPostForPrimaryScreen//(检查通知主屏幕) checkAndPostForStack//(检查并通知栈)
2.15ToastUI弹窗提示
ToastUI是系统的弹窗提示,继承自SystemUI实现CommandQueue.Callbacks接口,调用start方法后,把当前对象的回调传给mCommandQueue,代码如下:
@VisibleForTesting
ToastUI(Context context, CommandQueue commandQueue, INotificationManager notificationManager,
@Nullable IAccessibilityManager accessibilityManager) {
super(context);
mCommandQueue = commandQueue;
mNotificationManager = notificationManager;
mAccessibilityManager = accessibilityManager;
Resources resources = mContext.getResources();
mGravity = resources.getInteger(R.integer.config_toastDefaultGravity);
mY = resources.getDimensionPixelSize(R.dimen.toast_y_offset);
}
@Override
public void start() {
mCommandQueue.addCallback(this);
}
@Override
@MainThread
public void showToast(int uid, String packageName, IBinder token, CharSequence text,
IBinder windowToken, int duration, @Nullable ITransientNotificationCallback callback) {
if (mPresenter != null) {
hideCurrentToast();
}
Context context = mContext.createContextAsUser(UserHandle.getUserHandleForUid(uid), 0);
View view = ToastPresenter.getTextToastView(context, text);
mCallback = callback;
mPresenter = new ToastPresenter(context, mAccessibilityManager, mNotificationManager,
packageName);
mPresenter.show(view, token, windowToken, duration, mGravity, 0, mY, 0, 0, mCallback);
}
@Override
@MainThread
public void hideToast(String packageName, IBinder token) {
if (mPresenter == null || !Objects.equals(mPresenter.getPackageName(), packageName)
|| !Objects.equals(mPresenter.getToken(), token)) {
Log.w(TAG, "Attempt to hide non-current toast from package " + packageName);
return;
}
hideCurrentToast();
}
@MainThread
private void hideCurrentToast() {
mPresenter.hide(mCallback);
mPresenter = null;
}
初始化时赋值mCommandQueue,这个队列是实现消息回传的一个类,ToastUI中的showToast和hideToast都加的有主线程的注解,这个充当Model层和Presenter层的中间层,数据源发生变化,需要调用showToast时,通过new Presenter构造prensenter后调用presenter层的show方法,也是MVP的架构,presenter的主要代码如下:
构造方法:
public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
INotificationManager notificationManager, String packageName) {
mContext = context;
mResources = context.getResources();
mWindowManager = context.getSystemService(WindowManager.class);
mNotificationManager = notificationManager;
mPackageName = packageName;
// We obtain AccessibilityManager manually via its constructor instead of using method
// AccessibilityManager.getInstance() for 2 reasons:
// 1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
// 2. getInstance() caches the instance for the process even if we pass a different
// context to it. This is problematic for multi-user because callers can pass a context
// created via Context.createContextAsUser().
mAccessibilityManager = new AccessibilityManager(context, accessibilityManager,
context.getUserId());
mParams = createLayoutParams();
}
show和hide方法:
public void show(View view, IBinder token, IBinder windowToken, int duration, int gravity,
int xOffset, int yOffset, float horizontalMargin, float verticalMargin,
@Nullable ITransientNotificationCallback callback) {
checkState(mView == null, "only one toast at a time is allowed, call hide() first.");
mView = view;
mToken = token;
adjustLayoutParams(mParams, windowToken, duration, gravity, xOffset, yOffset,
horizontalMargin, verticalMargin);
if (mView.getParent() != null) {
mWindowManager.removeView(mView);
}
try {
mWindowManager.addView(mView, mParams);
} catch (WindowManager.BadTokenException e) {
// Since the notification manager service cancels the token right after it notifies us
// to cancel the toast there is an inherent race and we may attempt to add a window
// after the token has been invalidated. Let us hedge against that.
Log.w(TAG, "Error while attempting to show toast from " + mPackageName, e);
return;
}
trySendAccessibilityEvent(mView, mPackageName);
if (callback != null) {
try {
callback.onToastShown();
} catch (RemoteException e) {
Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastShow()", e);
}
}
}
public void hide(@Nullable ITransientNotificationCallback callback) {
checkState(mView != null, "No toast to hide.");
if (mView.getParent() != null) {
mWindowManager.removeViewImmediate(mView);
}
try {
mNotificationManager.finishToken(mPackageName, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Error finishing toast window token from package " + mPackageName, e);
}
if (callback != null) {
try {
callback.onToastHidden();
} catch (RemoteException e) {
Log.w(TAG, "Error calling back " + mPackageName + " to notify onToastHide()", e);
}
}
mView = null;
mToken = null;
}
presenter层直接通过windowManager的addView和removeView方法更新界面。



