作者其他文章
- AOSP | Android 9 控制导航栏的隐藏与显示
- AOSP | Android 11 Settings 开发(01) 环境搭建
- AOSP | Android 11 Framework 修改记录(持续更新)
- AOSP | Android 9 Framework 修改记录(持续更新)
- AOSP | Android 9 由壁纸切换带来的手机主题变更及问题出现)
- AOSP | Android 9 APP源码移植到系统源码中进行编译
- 后端 | 沙箱环境下实现支付宝网站支付
- 前端 | vue-echarts渲染时视图模糊的解决办法
问题来源
在进行新Settings App 核心功能取代 旧Settings App的尾声阶段,碰到的问题就是,有一些服务不得不调用到旧Settings的功能、服务。在保留旧Settings App的情况下(毕竟有的功能、服务,新Settings App无法取代),最大化减少新、旧Settings App带来的突兀感,一是隐藏掉旧Settings App的桌面图标,二是在一的基础上,从Recents(最近使用列表)不显示旧Settings App的相关Activity。
第一个方法很好解决,只需在framework/base/package/Settings/AndroidManifest.xml中,将Settings注册的activity中,把<category android:name="android.intent.category.LAUNCHER" />
注释掉即可,这样子桌面就不会有Settings图标了。
第二个方法也很好解决,只需在framework/base/package/Settings/AndroidManifest.xml中,将注册的Activity加上android:excludeFromRecents="true"
即可。
可是问题在于,AndroidManifest.xml中,注册的Activity数量过于多,如果我们一行一行加上上述代码,虽然需求很简单解决,但实在过于浪费时间。
于是,我们可以想到,从Recents源码下手,来解决我们的需求。
问题解析
当我们点击导航栏的Recents时候,根据Log,我们会发现,触发了framework/base/package/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java中的onRecentsTouch(View v, MotionEvent event)事件
// additional optimization when we have software system buttons - start loading the recent
// tasks on touch down
private boolean onRecentsTouch(View v, MotionEvent event) {
int action = event.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_DOWN) {
mCommandQueue.preloadRecentApps();
} else if (action == MotionEvent.ACTION_CANCEL) {
mCommandQueue.cancelPreloadRecentApps();
} else if (action == MotionEvent.ACTION_UP) {
if (!v.isPressed()) {
mCommandQueue.cancelPreloadRecentApps();
}
}
return false;
}
上述代码中,调用了mCommandQueue.preloadRecentApps();
framework/base/package/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
case MSG_PRELOAD_RECENT_APPS:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).preloadRecentApps();
}
break;
public interface Callbacks {
preloadRecentApps();
}
很显然,子类实现了CommandQueue.Callback接口,调用了preloadRecentApps();
查找哪些类实现该接口
find ./ -name "*.java" | xargs grep "CommandQueue.Callbacks"
结果:
find ./ -iname "*.java" | xargs grep "CommandQueue.Callbacks"
./SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java:import com.android.systemui.statusbar.CommandQueue.Callbacks;
./SystemUI/src/com/android/systemui/pip/PipUI.java:public class PipUI extends SystemUI implements CommandQueue.Callbacks {
./SystemUI/src/com/android/systemui/recents/Recents.java: implements RecentsComponent, CommandQueue.Callbacks {
./SystemUI/src/com/android/systemui/qs/QSFragment.java:public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks {
./SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java:import com.android.systemui.statusbar.CommandQueue.Callbacks;
./SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java:public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks {
./SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java:import com.android.systemui.statusbar.CommandQueue.Callbacks;
./SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java: // ----- CommandQueue Callbacks -----
./SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java: ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
./SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java: OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
./SystemUI/src/com/android/systemui/statusbar/phone/LightBarTransitionsController.java:import com.android.systemui.statusbar.CommandQueue.Callbacks;
./SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java:import com.android.systemui.statusbar.CommandQueue.Callbacks;
./SystemUI/src/com/android/systemui/statusbar/policy/Clock.java:public class Clock extends TextView implements DemoMode, Tunable, CommandQueue.Callbacks,
./SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java:import com.android.systemui.statusbar.CommandQueue.Callbacks;
./SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java:public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks {
./SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogImpl.java:public class FingerprintDialogImpl extends SystemUI implements CommandQueue.Callbacks {
./SystemUI/src/com/android/systemui/util/Utils.java: public static class DisableStateTracker implements CommandQueue.Callbacks,
./SystemUI/src/com/android/systemui/util/Utils.java: * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}.
搜索到的结果比较多,通过猜测,个人第一反应觉得**./SystemUI/src/com/android/systemui/recents/Recents.java: implements RecentsComponent, CommandQueue.Callbacks {** 是我们要找的类了。
public class Recents extends SystemUI implements RecentsComponent, CommandQueue.Callbacks {
private RecentsImpl mImpl;
@Override
public void preloadRecentApps() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
if (mOverviewProxyService.getProxy() != null) {
// TODO: Proxy to Launcher
return;
}
int currentUser = sSystemServicesProxy.getCurrentUser();
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.preloadRecents();
} else {
if (mSystemToUserCallbacks != null) {
IRecentsNonSystemUserCallbacks callbacks =
mSystemToUserCallbacks.getNonSystemUserRecentsForUser(currentUser);
if (callbacks != null) {
try {
callbacks.preloadRecents();
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
} else {
Log.e(TAG, "No SystemUI callbacks found for user: " + currentUser);
}
}
}
}
}
通过日志打印,发现调用了mImpl.preloadRecents();
查看framework/base/package/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
public void preloadRecents() {
if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
return;
}
// Skip preloading recents when keyguard is showing
final StatusBar statusBar = getStatusBar();
if (statusBar != null && statusBar.isKeyguardShowing()) {
return;
}
// Preload only the raw task list into a new load plan (which will be consumed by the
// RecentsActivity) only if there is a task to animate to. Post this to ensure that we
// don't block the touch feedback on the nav bar button which triggers this.
mHandler.post(() -> {
SystemServicesProxy ssp = Recents.getSystemServices();
if (!ssp.isRecentsActivityVisible(null)) {
ActivityManager.RunningTaskInfo runningTask =
ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTask == null) {
return;
}
RecentsTaskLoader loader = Recents.getTaskLoader();
sInstanceLoadPlan = new RecentsTaskLoadPlan(mContext);
loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
if (stack.getTaskCount() > 0) {
preloadIcon(runningTask.id);
updateHeaderBarLayout(stack, null /* window rect override*/);
}
}
});
}
调用了loader.preloadTasks(sInstanceLoadPlan, runningTask.id);
查看framework/base/package/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
}
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
int currentUserId) {
try {
Trace.beginSection("preloadPlan");
plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
} finally {
Trace.endSection();
}
}
调用了plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
查看framework/base/package/SystemUI/shared/src/com/android/systemui/shared/recents/model/RecentsTaskLoaderPlan.java
public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
int currentUserId) {
Resources res = mContext.getResources();
ArrayList<Task> allTasks = new ArrayList<>();
if (mRawTasks == null) {
mRawTasks = ActivityManagerWrapper.getInstance().getRecentTasks(
ActivityManager.getMaxRecentTasksStatic(), currentUserId);
// Since the raw tasks are given in most-recent to least-recent order, we need to reverse it
Collections.reverse(mRawTasks);
}
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
final ComponentName sourceComponent = t.origActivity != null
// Activity alias if there is one
? t.origActivity
// The real activity if there is no alias (or the target if there is one)
: t.realActivity;
final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
TaskKey taskKey = new TaskKey(t.persistentId, windowingMode, t.baseIntent,
sourceComponent, t.userId, t.lastActiveTime);
boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
boolean isStackTask = !isFreeformTask;
boolean isLaunchTarget = taskKey.id == runningTaskId;
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
if (info == null) {
continue;
}
// Load the title, icon, and color
String title = opts.loadTitles
? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
: "";
String titleDescription = opts.loadTitles
? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
: "";
Drawable icon = isStackTask
? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, false)
: null;
ThumbnailData thumbnail = loader.getAndUpdateThumbnail(taskKey,
false /* loadIfNotCached */, false /* storeInCache */);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription);
int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
boolean isSystemApp = (info != null) &&
((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
// TODO: Refactor to not do this every preload
if (mTmpLockedUsers.indexOfKey(t.userId) < 0) {
mTmpLockedUsers.put(t.userId, mKeyguardManager.isDeviceLocked(t.userId));
}
boolean isLocked = mTmpLockedUsers.get(t.userId);
// Add the task to the stack
Task task = new Task(taskKey, icon,
thumbnail, title, titleDescription, activityColor, backgroundColor,
isLaunchTarget, isStackTask, isSystemApp, t.supportsSplitScreenMultiWindow,
t.taskDescription, t.resizeMode, t.topActivity, isLocked);
allTasks.add(task);
}
// Initialize the stacks
mStack = new TaskStack();
mStack.setTasks(allTasks, false /* notifyStackChanges */);
}
可以注意到,在for循环中,遍历了任务栈,将符合条件的最近添加到allTasks中,于是我们就找到了我们需要改动的地方,通过添加条件筛选,过滤掉我们不需要的旧Settings App
public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
int currentUserId) {
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
ActivityInfo info = loader.getAndUpdateActivityInfo(taskKey);
if (info == null) {
continue;
}
+ /// M: modify by wayhoi, hidden Settings App
+ if (info.packageName.equals("com.android.settings")) {
+ continue;
+ }
}
}
最后编译刷机测试,发现效果是我们需要的。
总结
在拥有系统源码的情况下,通过添加几行代码,从而替代添加数以百计(毕竟Setting/AndroidManifest.xml中几千行代码,当中注册的Activity不数便知很多)的同一行代码,显然修改源码的方式更值得推荐。