IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> SystemUI介绍 -> 正文阅读

[移动开发]SystemUI介绍

1.启动流程

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
/**
 ? ? * The main entry point from zygote.
 ? ? */
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
/**
 ? ? * Makes sure that all the SystemUI services are running. If they are already running, this is a
 ? ? * no-op. This is needed to conditinally start all the services, as we only need to have it in
 ? ? * the main process.
 ? ? * <p>This method must only be called from the main thread.</p>
 ? ? */
?
public void startServicesIfNeeded() {
 ? ?String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
 ? ?startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}

这个Service的列表是从SystemUIFactory里面取到的,

//文件路径
//./frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
/** Returns the list of system UI components that should be started. */
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
<!-- SystemUI Services: The classes of the stuff to start. -->
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.util.NotificationChannels</item>//通知
<item>com.android.systemui.volume.VolumeUI</item>//声音
<item>com.android.systemui.stackdivider.Divider</item>//分屏
<item>com.android.systemui.statusbar.tv.TvStatusBar</item>//电视状态栏
<item>com.android.systemui.usb.StorageNotification</item>//存储通知
<item>com.android.systemui.power.PowerUI</item>//电量界面
<item>com.android.systemui.media.RingtonePlayer</item>//铃声播放器
<item>com.android.systemui.keyboard.KeyboardUI</item>//键盘界面
<item>com.android.systemui.pip.PipUI</item>//图片窗口中的图片
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>//快捷分发器
<item>@string/config_systemUIVendorServiceComponent</item>//供应商服务
<item>com.android.systemui.SliceBroadcastRelayHandler</item>//允许打开设置app
<item>com.android.systemui.SizeCompatModeActivityController</item>//允许重启activity
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>//即时应用程序通知
<item>com.android.systemui.toast.ToastUI</item>//弹窗提示
</string-array>

需要启动的服务大概如上述所示,后面详细说明这些服务,

//文件路径:
//./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
/**
 ? ? * Register a dumpable to be called during a bug report. The dumpable will be called during the
 ? ? * CRITICAL section of the bug report, so don't dump an excessive amount of stuff here.
 ? ? *
 ? ? * @param name The name to register the dumpable under. This is typically the qualified class
 ? ? * name of the thing being dumped (getClass().getName()), but can be anything as long as it
 ? ? * doesn't clash with an existing registration.
 ? ? */
@Synchronized
fun registerDumpable(name: String, module: Dumpable) {
 ? ?if (!canAssignToNameLocked(name, module)) {
 ? ? ? ?throw IllegalArgumentException("'$name' is already registered")
 ?  }
?
 ? ?dumpables[name] = RegisteredDumpable(name, module)
}

这个里面存放的是MutableMap<String, RegisteredDumpable<Dumpable>>类型的map数据,其中key在存储时表示的SystemUI的className,里面记录的是config.xml里面service,这些service的顶层父类都是SystemUI,SystemUI是一个实现了Dumpable接口的抽象类,这个map的value是这些service的实体对象,就是SystemUIApplication中通过Provider中拿到的对象。

其中Dump接口代码如下:

//文件路径
//./frameworks/base/services/core/java/com/android/server/soundtrigger_middleware/Dumpable.java
/**
 * Implemented by classes who want to be in:
 * ? {@code adb shell dumpsys activity service com.android.systemui}
 *
 * @see DumpManager
 */
public interface Dumpable {
?
 ? ?/**
 ? ? * Called when it's time to dump the internal state
 ? ? * @param fd A file descriptor.
 ? ? * @param pw Where to write your dump to.
 ? ? * @param args Arguments.
 ? ? */
 ? ?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
 ? ? ? ? ? ? ? ?android:id="@+id/volume_dialog_rows"
 ? ? ? ? ? ? ? ?android:layout_width="wrap_content"
 ? ? ? ? ? ? ? ?android:layout_height="wrap_content"
 ? ? ? ? ? ? ? ?android:minWidth="@dimen/volume_dialog_panel_width"
 ? ? ? ? ? ? ? ?android:gravity="center"
 ? ? ? ? ? ? ? ?android:orientation="horizontal"
 ? ? ? ? ? ? ? ?android:paddingRight="@dimen/volume_dialog_stream_padding"
 ? ? ? ? ? ? ? ?android:paddingLeft="@dimen/volume_dialog_stream_padding">
 ? ? ? ? ? ? ? ? ? ?<!-- volume rows added and removed here! :-) -->
 ? ? ? ? ? ?</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<VolumeRow> mRows的对象控制添加的内容,这部分大概是音量UI的View层面的内容,音量这部分内容使用的是MVP的架构,这个dialog属于View层面的内容。

VolumeDialogControllerImpl是Presenter层面的内容,这个类是VolumeDialogController接口的实现,也实现了dump接口用于dump需要输出的内容,在初始化时拿到AudioManager的服务,然后互相通信,AudioManager是音量的具体Model层,他负责音量数据源,他们之间的通信关系如下,

2.3Divider分屏

Divider是处理分屏的业务,构造方法如下:

public Divider(Context context, Optional<Lazy<Recents>> 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 /* transaction */);
 ? ?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 /* transaction */);
 ? ? ? ?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) { }
?
/**
 ? ? ? ? * Called to notify that disable flags are updated.
 ? ? ? ? * @see IStatusBar#disable(int, int, int).
 ? ? ? ? *
 ? ? ? ? * @param displayId The id of the display to notify.
 ? ? ? ? * @param state1 The combination of following DISABLE_* flags:
 ? ? ? ? * @param state2 The combination of following DISABLE2_* flags:
 ? ? ? ? * @param animate {@code true} to show animations.
 ? ? ? ? */
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) { }
?
/**
 ? ? ? ? * Called to notify IME window status changes.
 ? ? ? ? *
 ? ? ? ? * @param displayId The id of the display to notify.
 ? ? ? ? * @param token IME token.
 ? ? ? ? * @param vis IME visibility.
 ? ? ? ? * @param backDisposition Disposition mode of back button. It should be one of below flags:
 ? ? ? ? * @param showImeSwitcher {@code true} to show IME switch button.
 ? ? ? ? */
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<DiskInfo> disks = mStorageManager.getDisks();
 ? ?for (DiskInfo disk : disks) {
 ? ? ? ?onDiskScannedInternal(disk, disk.volumeCount);
 ?  }
?
 ? ?final List<VolumeInfo> 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 /*notifyForDescendants*/,
 ? ? ? ?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 /*notifyForDescendants*/,
 ? ? ? ?new ContentObserver(mHandler) {
 ? ? ? ? ? ?@Override
 ? ? ? ? ? ?public void onChange(boolean selfChange) {
 ? ? ? ? ? ? ? ?doUsbThermalEventListenerRegistration();
 ? ? ? ? ?  }
 ? ? ?  });
 ? ?initThermalEventListeners();
 ? ?mCommandQueue.addCallback(this);
}

初始化时调用showWarnOnThermalShutdown这个方法检查上次关机是不是因为温度太高,注册手机温度过高的回调,doSkinThermalEventListenerRegistration方法做手机温度过高的处理,调用doUsbThermalEventListenerRegistration这个方法处理USB接口温度过高的处理,其中receiver接受做的是电量的逻辑处理,如果需要用到处理UI的相关内容,是通过mWarnings做的界面更新内容,mWarnings是一个抽象接口,

/**
 ? ? * The interface to allow PowerUI to communicate with whatever implementation of WarningsUI
 ? ? * is being used by the system.
 ? ? */
public interface WarningsUI {
?
 ? ?/**
 ? ? ? ? * Updates battery and screen info for determining whether to trigger battery warnings or
 ? ? ? ? * not.
 ? ? ? ? * @param batteryLevel The current battery level
 ? ? ? ? * @param bucket The current battery bucket
 ? ? ? ? * @param screenOffTime How long the screen has been off in millis
 ? ? ? ? */
 ? ?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();
?
 ? ?/**
 ? ? ? ? * Display USB port overheat alarm
 ? ? ? ? */
 ? ?void showUsbHighTemperatureAlarm();
?
 ? ?void showThermalShutdownWarning();
?
 ? ?void dump(PrintWriter pw);
?
 ? ?void userSwitched();
?
 ? ?/**
 ? ? ? ? * Updates the snapshot of battery state used for evaluating battery warnings
 ? ? ? ? * @param snapshot object containing relevant values for making battery warning decisions.
 ? ? ? ? */
 ? ?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 /*followSettingsUri*/, false /*allowRemote*/);
 ?  }
?
 ? ?@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<IBinder, Client>,用于保存和客户端的连接的对象,Client里面通过mRingtone做声音的处理,Ringtone就是快速设置音量的类,里面的方法名如下:

setAudioAttributes()
setLooping()
setVolume()
setUri(Uri uri)
play()
stop()

但是这个类上层是无法操作的,试过之后,这个类只能在系统里面操作。

2.8KeyboardUI键盘界面

KeyboardUI是在androidTV版配置下才存在的服务,主要是为了处理蓝牙键盘,这个蓝牙键盘在手机上是不可以使用的,里面通过蓝牙连接外设键盘,通过广播的形式处理锁屏,关机的对话框,SystemUIDialog是一个alertDialog,状态改变时会设置不同的window的属性,内部的receiver代码如下:

/**
 ? ? * Registers a listener that dismisses the given dialog when it receives
 ? ? * the screen off / close system dialogs broadcast.
 ? ? * <p>
 ? ? * <strong>Note:</strong> Don't call dialog.setOnDismissListener() after
 ? ? * calling this because it causes a leak of BroadcastReceiver.
 ? ? *
 ? ? * @param dialog The dialog to be associated with the listener.
 ? ? */
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 /*async*/);
 ?  }
?
 ? ?@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<Context, String> p = (Pair<Context, String>) 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 /*async*/);
 ?  }
 ? ?@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_confirmation_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);
}

注册方法如下:

/**
 * Registers a shortcut key to window manager.
 * @param shortcutCode packed representation of shortcut key code and meta information
*/
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 /* animate */, false /* touching */);
 ? ? ? ?dividerView.stopDragging(target.position, 0f, false /* avoidDismissStart */,
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? true /* logMetrics */);
 ?  }
}

判断分屏状态,如果当前分屏是空,或者分屏不可见的情况下,做分屏处理,其他的情况重新测量这个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页面和原生应用的双重优点。用户无需下载安装,即点即用,享受原生应用的性能体验。

  1. 快应用是基于手机硬件平台的新型应用形态,标准是由主流手机厂商组成的快应用联盟联合制定。

  2. 快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台,以平台化的生态模式对个人开发者和企业开发者全品类开放。

  3. 快应用具备传统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方法:

/**
 ? ? * Shows the toast in {@code view} with the parameters passed and callback {@code callback}.
 ? ? */
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);
 ? ? ?  }
 ?  }
}
?
/**
 ? ? * Hides toast that was shown using {@link #show(View, IBinder, IBinder, int,
 ? ? * int, int, int, float, float, ITransientNotificationCallback)}.
 ? ? *
 ? ? * <p>This method has to be called on the same thread on which {@link #show(View, IBinder,
 ? ? * IBinder, int, int, int, int, float, float, ITransientNotificationCallback)} was called.
 ? ? */
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方法更新界面。

结论:看不懂没关系,兄弟我看不懂的代码多了去了,不差这一点。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:42:39  更:2021-11-12 19:43:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 3:18:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码