最近有个launcher项目,有个修改最近任务的功能。

改成普通安卓手机,按任务栏显示最近打开的任务界面。

可以上下滑动层叠卡片式布局,

卡片式布局可以参考我另一篇文章:Android7最近任务栏UI(DeckView)

一.获取最近任务

taskListLoader.loadTaskList(tasks -> {

try {

Collections.reverse(tasks);

for (Task task : tasks) {

AppBeanInfo appIconInfo = new AppBeanInfo();

task.titleDescription = task.titleDescription;

ComponentName component = task.getTopComponent();

String pkgName = component.getPackageName();

if (pkgName.equals("com.lzui.launcher")) continue;

Log.i("RecentsActivity 1", pkgName + "-" + task.getTopComponent());

try {

ApplicationInfo applicationInfo = pm.getApplicationInfo(pkgName, 0);

String applicationName = (String) pm.getApplicationLabel(applicationInfo);

Drawable icon = pm.getApplicationIcon(applicationInfo);

appIconInfo.setAppName(applicationName);

appIconInfo.setDrawable(icon);

appIconInfo.setPackageName(pkgName);

appIconInfo.setTask(task);

appIconInfoList.add(appIconInfo);

Log.i("RecentsActivity 1", applicationName);

} catch (Exception e) {

e.printStackTrace();

}

}

mDeckView.notifyDataSetChanged();

if(appIconInfoList.size() != 0){

mDeckView.scrollToChild(appIconInfoList.size() - 2);

}

} catch (Exception e) {

e.printStackTrace();

}

});

public void loadTaskList(@Nullable Consumer> onLoadedCallback) {

if (!needsToLoad()) {

if (onLoadedCallback != null) {

onLoadedCallback.accept(mTaskList);

}

return;

}

// TODO: Look into error checking / more robust handling for when things go wrong.

mTaskListChangeId = mRecentsModel.getTasks(loadedTasks -> {

ArrayList tasks = new ArrayList<>(loadedTasks);

// Reverse tasks to put most recent at the bottom of the view

Collections.reverse(tasks);

// Load task content

for (Task task : tasks) {

int loadedPos = mTaskList.indexOf(task);

if (loadedPos == -1) {

continue;

}

Task loadedTask = mTaskList.get(loadedPos);

task.icon = loadedTask.icon;

task.titleDescription = loadedTask.titleDescription;

task.thumbnail = loadedTask.thumbnail;

}

mTaskList = tasks;

onLoadedCallback.accept(tasks);

});

}

TaskListLoader在源码

public final class TaskListLoader {

private final RecentsModel mRecentsModel;

private ArrayList mTaskList = new ArrayList<>();

private int mTaskListChangeId;

public TaskListLoader(Context context) {

mRecentsModel = RecentsModel.INSTANCE.get(context);

}

/**

* Returns the current task list as of the last completed load (see {@link #loadTaskList}) as a

* read-only list. This list of tasks is not guaranteed to have all content loaded.

*

* @return the current list of tasks

*/

public List getCurrentTaskList() {

return Collections.unmodifiableList(mTaskList);

}

/**

* Whether or not the loader needs to load data to be up to date. This can return true if the

* task list is already up to date OR there is already a load in progress for the task list to

* become up to date.

*

* @return true if already up to date or load in progress, false otherwise

*/

public boolean needsToLoad() {

return !mRecentsModel.isTaskListValid(mTaskListChangeId);

}

/**

* Fetches the most recent tasks and updates the task list asynchronously. This call does not

* provide guarantees the task content (icon, thumbnail, label) are loaded but will fill in

* what it has. May run the callback immediately if there have been no changes in the task

* list since the start of the last load.

*

* @param onLoadedCallback callback to run when task list is loaded

*/

public void loadTaskList(@Nullable Consumer> onLoadedCallback) {

if (!needsToLoad()) {

if (onLoadedCallback != null) {

onLoadedCallback.accept(mTaskList);

}

return;

}

// TODO: Look into error checking / more robust handling for when things go wrong.

mTaskListChangeId = mRecentsModel.getTasks(loadedTasks -> {

ArrayList tasks = new ArrayList<>(loadedTasks);

// Reverse tasks to put most recent at the bottom of the view

Collections.reverse(tasks);

// Load task content

for (Task task : tasks) {

int loadedPos = mTaskList.indexOf(task);

if (loadedPos == -1) {

continue;

}

Task loadedTask = mTaskList.get(loadedPos);

task.icon = loadedTask.icon;

task.titleDescription = loadedTask.titleDescription;

task.thumbnail = loadedTask.thumbnail;

}

mTaskList = tasks;

onLoadedCallback.accept(tasks);

});

}

/**

* Load task icon and label asynchronously if it is not already loaded in the task. If the task

* already has an icon, this calls the callback immediately.

*

* @param task task to update with icon + label

* @param onLoadedCallback callback to run when task has icon and label

*/

public void loadTaskIconAndLabel(Task task, @Nullable Runnable onLoadedCallback) {

mRecentsModel.getIconCache().updateIconInBackground(task,

loadedTask -> onLoadedCallback.run());

}

/**

* Load thumbnail asynchronously if not already loaded in the task. If the task already has a

* thumbnail or if the thumbnail is cached, this calls the callback immediately.

*

* @param task task to update with the thumbnail

* @param onLoadedCallback callback to run when task has thumbnail

*/

public void loadTaskThumbnail(Task task, @Nullable Runnable onLoadedCallback) {

mRecentsModel.getThumbnailCache().updateThumbnailInBackground(task,

thumbnail -> onLoadedCallback.run());

}

/**

* Removes the task from the current task list.

*/

public void removeTask(Task task) {

mTaskList.remove(task);

}

/**

* Clears the current task list.

*/

public void clearAllTasks() {

mTaskList.clear();

}

}

loadTaskList:加载最近任务列表

loadTaskThumbnail:加载最近任务缩略图

removeTask:移除最近任务列表

clearAllTasks:清楚全部最近任务

二、获取最近任务缩略图

private void loadViewDataInternal(final WeakReference> weakView, final AppBeanInfo appBeanInfo) {

if (Build.VERSION.SDK_INT >= 26) {

taskListLoader.loadTaskThumbnail(appBeanInfo.getTask(), () -> {

weakView.get().onDataLoaded(appBeanInfo, appBeanInfo.getTask().thumbnail.thumbnail,

appBeanInfo.getDrawable(), appBeanInfo.getAppName(), Color.DKGRAY);

});

}

}

DeckView.Callback deckViewCallback = new DeckView.Callback() {

@Override

public ArrayList getData() {

return appIconInfoList;

}

@Override

public void loadViewData(WeakReference> dcv, AppBeanInfo item) {

llNullContent.setVisibility(View.GONE);

llCleanAll.setVisibility(View.VISIBLE);

loadViewDataInternal(dcv, item);

}

@Override

public void unloadViewData(AppBeanInfo item) {

}

@Override

public void onViewDismissed(AppBeanInfo item) {

ActivityManagerWrapper.getInstance().removeTask(item.getTask().key.id);

taskListLoader.removeTask(item.getTask());

appIconInfoList.remove(item);

mDeckView.notifyDataSetChanged();

}

@Override

public void onItemClick(AppBeanInfo item) {

// 进入此程序

try {

Intent intent = new Intent();

//通过包名启动

PackageManager packageManager = getPackageManager();

intent = packageManager.getLaunchIntentForPackage(item.getPackageName());

if (null != intent) {

startActivity(intent);

}

} catch (Exception e) {

e.printStackTrace();

}

}

@Override

public void onNoViewsToDeck() {

llNullContent.setVisibility(View.VISIBLE);

llCleanAll.setVisibility(View.GONE);

}

@Override

public void onFinsh() {

RecentsActivity.this.finish();

}

};

三、清除全部任务

ActivityManagerWrapper.getInstance().removeAllRecentTasks();

taskListLoader.clearAllTasks();

appIconInfoList.clear();

appIconInfoList = null;

mDeckView = null;

单个清除

ActivityManagerWrapper.getInstance().removeTask(item.getTask().key.id);

taskListLoader.removeTask(item.getTask());

appIconInfoList.remove(item);

四、拿出10.0的源码修改成launcher用的。

代码有点多,我整理成资源,供大家下载,又不懂的欢迎留言。

主要代码类:

RecentsModel

public class RecentsModel extends TaskStackChangeListener {

private static final String TAG = "RecentsModel";

// We do not need any synchronization for this variable as its only written on UI thread.

public static final MainThreadInitializedObject INSTANCE =

new MainThreadInitializedObject<>(c -> new RecentsModel(c));

private final List mThumbnailChangeListeners = new ArrayList<>();

private final Context mContext;

private ISystemUiProxy mSystemUiProxy;

private final RecentTasksList mTaskList;

private final TaskIconCache mIconCache;

private final TaskThumbnailCache mThumbnailCache;

private RecentsModel(Context context) {

mContext = context;

HandlerThread loaderThread = new HandlerThread("TaskThumbnailIconCache",

Process.THREAD_PRIORITY_BACKGROUND);

loaderThread.start();

mTaskList = new RecentTasksList(context);

mIconCache = new TaskIconCache(context, loaderThread.getLooper());

mThumbnailCache = new TaskThumbnailCache(context, loaderThread.getLooper());

ActivityManagerWrapper.getInstance().registerTaskStackListener(this);

}

public TaskIconCache getIconCache() {

return mIconCache;

}

public TaskThumbnailCache getThumbnailCache() {

return mThumbnailCache;

}

/**

* Fetches the list of recent tasks.

*

* @param callback The callback to receive the task plan once its complete or null. This is

* always called on the UI thread.

* @return the request id associated with this call.

*/

public int getTasks(Consumer> callback) {

return mTaskList.getTasks(false /* loadKeysOnly */, callback);

}

/**

* @return The task id of the running task, or -1 if there is no current running task.

*/

public static int getRunningTaskId() {

ActivityManager.RunningTaskInfo runningTask =

ActivityManagerWrapper.getInstance().getRunningTask();

return runningTask != null ? runningTask.id : -1;

}

/**

* @return Whether the provided {@param changeId} is the latest recent tasks list id.

*/

public boolean isTaskListValid(int changeId) {

return mTaskList.isTaskListValid(changeId);

}

/**

* Finds and returns the task key associated with the given task id.

*

* @param callback The callback to receive the task key if it is found or null. This is always

* called on the UI thread.

*/

public void findTaskWithId(int taskId, Consumer callback) {

mTaskList.getTasks(true /* loadKeysOnly */, (tasks) -> {

for (Task task : tasks) {

if (task.key.id == taskId) {

callback.accept(task.key);

return;

}

}

callback.accept(null);

});

}

@Override

public void onTaskStackChangedBackground() {

if (!mThumbnailCache.isPreloadingEnabled()) {

// Skip if we aren't preloading

return;

}

int currentUserId = Process.myUserHandle().getIdentifier();

if (!TaskUtils.checkCurrentOrManagedUserId(currentUserId, mContext)) {

// Skip if we are not the current user

return;

}

// Keep the cache up to date with the latest thumbnails

int runningTaskId = RecentsModel.getRunningTaskId();

mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), tasks -> {

for (Task task : tasks) {

if (task.key.id == runningTaskId) {

// Skip the running task, it's not going to have an up-to-date snapshot by the

// time the user next enters overview

continue;

}

mThumbnailCache.updateThumbnailInCache(task);

}

});

}

@Override

public void onTaskSnapshotChanged(int taskId, ThumbnailData snapshot) {

mThumbnailCache.updateTaskSnapShot(taskId, snapshot);

for (int i = mThumbnailChangeListeners.size() - 1; i >= 0; i--) {

Task task = mThumbnailChangeListeners.get(i).onTaskThumbnailChanged(taskId, snapshot);

if (task != null) {

task.thumbnail = snapshot;

}

}

}

@Override

public void onTaskRemoved(int taskId) {

Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);

mThumbnailCache.remove(dummyKey);

}

public void setSystemUiProxy(ISystemUiProxy systemUiProxy) {

mSystemUiProxy = systemUiProxy;

}

public ISystemUiProxy getSystemUiProxy() {

return mSystemUiProxy;

}

public void onTrimMemory(int level) {

if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {

mThumbnailCache.getHighResLoadingState().setVisible(false);

}

if (level == ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {

// Clear everything once we reach a low-mem situation

mThumbnailCache.clear();

mIconCache.clear();

}

}

public void onOverviewShown(boolean fromHome, String tag) {

if (mSystemUiProxy == null) {

return;

}

try {

mSystemUiProxy.onOverviewShown(fromHome);

} catch (RemoteException e) {

Log.w(tag,

"Failed to notify SysUI of overview shown from " + (fromHome ? "home" : "app")

+ ": ", e);

}

}

public void addThumbnailChangeListener(TaskThumbnailChangeListener listener) {

mThumbnailChangeListeners.add(listener);

}

public void removeThumbnailChangeListener(TaskThumbnailChangeListener listener) {

mThumbnailChangeListeners.remove(listener);

}

public interface TaskThumbnailChangeListener {

Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData);

}

public RecentTasksList getmTaskList() {

return mTaskList;

}

}

TaskThumbnailCache

public class TaskThumbnailCache {

private final Handler mBackgroundHandler;

private final MainThreadExecutor mMainThreadExecutor;

private final int mCacheSize;

private final ThumbnailCache mCache;

private final HighResLoadingState mHighResLoadingState;

public static class HighResLoadingState {

private boolean mIsLowRamDevice;

private boolean mVisible;

private boolean mFlingingFast;

private boolean mHighResLoadingEnabled;

private ArrayList mCallbacks = new ArrayList<>();

public interface HighResLoadingStateChangedCallback {

void onHighResLoadingStateChanged(boolean enabled);

}

private HighResLoadingState(Context context) {

ActivityManager activityManager =

(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);

mIsLowRamDevice = activityManager.isLowRamDevice();

}

public void addCallback(HighResLoadingStateChangedCallback callback) {

mCallbacks.add(callback);

}

public void removeCallback(HighResLoadingStateChangedCallback callback) {

mCallbacks.remove(callback);

}

public void setVisible(boolean visible) {

mVisible = visible;

updateState();

}

public void setFlingingFast(boolean flingingFast) {

mFlingingFast = flingingFast;

updateState();

}

public boolean isEnabled() {

return mHighResLoadingEnabled;

}

private void updateState() {

boolean prevState = mHighResLoadingEnabled;

mHighResLoadingEnabled = !mIsLowRamDevice && mVisible && !mFlingingFast;

if (prevState != mHighResLoadingEnabled) {

for (int i = mCallbacks.size() - 1; i >= 0; i--) {

mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);

}

}

}

}

public TaskThumbnailCache(Context context, Looper backgroundLooper) {

mBackgroundHandler = new Handler(backgroundLooper);

mMainThreadExecutor = new MainThreadExecutor();

mHighResLoadingState = new HighResLoadingState(context);

Resources res = context.getResources();

mCacheSize = 3;

mCache = new ThumbnailCache(mCacheSize);

}

/**

* Synchronously fetches the thumbnail for the given {@param task} and puts it in the cache.

*/

public void updateThumbnailInCache(Task task) {

Preconditions.assertUIThread();

// Fetch the thumbnail for this task and put it in the cache

if (task.thumbnail == null) {

updateThumbnailInBackground(task.key, true /* reducedResolution */,

t -> task.thumbnail = t);

}

}

/**

* Synchronously updates the thumbnail in the cache if it is already there.

*/

public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {

Preconditions.assertUIThread();

mCache.updateIfAlreadyInCache(taskId, thumbnail);

}

/**

* Asynchronously fetches the icon and other task data for the given {@param task}.

*

* @param callback The callback to receive the task after its data has been populated.

* @return A cancelable handle to the request

*/

public ThumbnailLoadRequest updateThumbnailInBackground(

Task task, Consumer callback) {

Preconditions.assertUIThread();

boolean reducedResolution = !mHighResLoadingState.isEnabled();

if (task.thumbnail != null && (!task.thumbnail.reducedResolution || reducedResolution)) {

// Nothing to load, the thumbnail is already high-resolution or matches what the

// request, so just callback

callback.accept(task.thumbnail);

return null;

}

return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {

task.thumbnail = t;

callback.accept(t);

});

}

private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean reducedResolution,

Consumer callback) {

Preconditions.assertUIThread();

ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);

if (cachedThumbnail != null && (!cachedThumbnail.reducedResolution || reducedResolution)) {

// Already cached, lets use that thumbnail

callback.accept(cachedThumbnail);

return null;

}

ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,

reducedResolution) {

@Override

public void run() {

ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(

key.id, reducedResolution);

if (isCanceled()) {

// We don't call back to the provided callback in this case

return;

}

mMainThreadExecutor.execute(() -> {

mCache.put(key, thumbnail);

callback.accept(thumbnail);

onEnd();

});

}

};

Utilities.postAsyncCallback(mBackgroundHandler, request);

return request;

}

/**

* Clears the cache.

*/

public void clear() {

mCache.evictAll();

}

/**

* Removes the cached thumbnail for the given task.

*/

public void remove(TaskKey key) {

mCache.remove(key);

}

/**

* @return The cache size.

*/

public int getCacheSize() {

return mCacheSize;

}

/**

* @return The mutable high-res loading state.

*/

public HighResLoadingState getHighResLoadingState() {

return mHighResLoadingState;

}

/**

* @return Whether to enable background preloading of task thumbnails.

*/

public boolean isPreloadingEnabled() {

return !mHighResLoadingState.mIsLowRamDevice && mHighResLoadingState.mVisible;

}

public static abstract class ThumbnailLoadRequest extends HandlerRunnable {

public final boolean reducedResolution;

ThumbnailLoadRequest(Handler handler, boolean reducedResolution) {

super(handler, null);

this.reducedResolution = reducedResolution;

}

}

private static class ThumbnailCache extends TaskKeyLruCache {

public ThumbnailCache(int cacheSize) {

super(cacheSize);

}

/**

* Updates the cache entry if it is already present in the cache

*/

public void updateIfAlreadyInCache(int taskId, ThumbnailData thumbnailData) {

ThumbnailData oldData = getCacheEntry(taskId);

if (oldData != null) {

putCacheEntry(taskId, thumbnailData);

}

}

}

}

还需要一个10.0的jar包,一起在资源里面了。

SystemUISharedLib.jar

10.0获取最近任务缩略图链接:10.0任务列表获取缩略图-Android文档类资源-CSDN下载