熟悉Android framework的同学都清楚Activity 进出栈是依靠ActivityStack.java(android/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java)进行管理,但是,对ActivityStack不熟悉的同学或者没有接触的,请参考下面的文章.
最近做项目,有一个简单的需求就是当手机空间不足时,需要弹出对话框进行提示用户并且当在非删除文件的Activity界面也需要立即弹出对话框提示。换句通俗的话,是当储存空间不足时,只显示可以删除文件的Activity,其他Activity都必须在提示对话框的后面。android 本身自带检测存储检测:frameworks/base/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java 中的
/**
* Core logic that checks the storage state of every mounted private volume.
* Since this can do heavy I/O, callers should invoke indirectly using
* {@link #MSG_CHECK}.
*/
@WorkerThread
private void check() {
final StorageManager storage = getContext().getSystemService(StorageManager.class);
final int seq = mSeq.get();
for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
final File file = vol.getPath();
final long fullBytes = storage.getStorageFullBytes(file);
final long lowBytes = storage.getStorageLowBytes(file);
// Automatically trim cached data when nearing the low threshold;
// when it's within 150% of the threshold, we try trimming usage
// back to 200% of the threshold.
if (file.getUsableSpace() < (lowBytes * 3) / 2) {
final PackageManagerService pms = (PackageManagerService) ServiceManager
.getService("package");
try {
pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
} catch (IOException e) {
Slog.w(TAG, e);
}
}
// Send relevant broadcasts and show notifications based on any
// recently noticed state transitions.
final UUID uuid = StorageManager.convert(vol.getFsUuid());
final State state = findOrCreateState(uuid);
final long totalBytes = file.getTotalSpace();
final long usableBytes = file.getUsableSpace();
int oldLevel = state.level;
int newLevel;
if (mForceLevel != State.LEVEL_UNKNOWN) {
// When in testing mode, use unknown old level to force sending
// of any relevant broadcasts.
oldLevel = State.LEVEL_UNKNOWN;
newLevel = mForceLevel;
} else if (usableBytes <= fullBytes) {
newLevel = State.LEVEL_FULL;
} else if (usableBytes <= lowBytes) {
newLevel = State.LEVEL_LOW;
} else if (StorageManager.UUID_DEFAULT.equals(uuid) && !isBootImageOnDisk()
&& usableBytes < BOOT_IMAGE_STORAGE_REQUIREMENT) {
newLevel = State.LEVEL_LOW;
} else {
newLevel = State.LEVEL_NORMAL;
}
// Log whenever we notice drastic storage changes
if ((Math.abs(state.lastUsableBytes - usableBytes) > DEFAULT_LOG_DELTA_BYTES)
|| oldLevel != newLevel) {
EventLogTags.writeStorageState(uuid.toString(), oldLevel, newLevel,
usableBytes, totalBytes);
state.lastUsableBytes = usableBytes;
}
//若LEVEL_LOW,则显示Notification.
updateNotifications(vol, oldLevel, newLevel);
//进行发各种广播进行通知
updateBroadcasts(vol, oldLevel, newLevel, seq);
state.level = newLevel;
}
}
}
但是,此方法缺陷是周期性检查,而不是立刻可以反馈出来。
针对此疑问思考和分析:
[1]根据之前的工作经验,发现event log 可以实时监听那个Activity 是resume的。
譬如:
1]使用adb logcat命令并过滤:
adb logcat -b events | grep "resume"
(1)从launcher进入Camera界面:
10-24 03:32:22.631 846 1654 I am_set_resumed_activity: [0,com.mediatek.camera/.CameraLauncher,minimalResumeActivityLocked]
10-24 03:32:23.327 8799 8799 I am_on_resume_called: [0,com.mediatek.camera.CameraLauncher,LAUNCH_ACTIVITY]
(2)Camera 进入photos:
10-24 03:33:00.272 846 5182 I am_set_resumed_activity: [0,com.google.android.apps.photos/.pager.HostPhotoPagerActivity,minimalResumeActivityLocked]
10-24 03:33:01.124 8908 8908 I am_on_resume_called: [0,com.google.android.apps.photos.pager.HostPhotoPagerActivity,LAUNCH_ACTIVITY]
(3)从Photos 退出到Camera:
10-24 03:33:52.308 846 6191 I am_set_resumed_activity: [0,com.mediatek.camera/.CameraLauncher,resumeTopActivityInnerLocked]
10-24 03:33:52.319 846 6191 I am_resume_activity: [0,113921636,107,com.mediatek.camera/.CameraLauncher]
10-24 03:33:52.470 8799 8799 I am_on_resume_called: [0,com.mediatek.camera.CameraLauncher,RESUME_ACTIVITY]
(4)再退出到launcher:
10-24 03:34:47.892 846 2056 I am_set_resumed_activity: [0,com.android.launcher3/.Launcher,resumeTopActivityInnerLocked]
10-24 03:34:47.901 846 2056 I am_resume_activity: [0,159438434,89,com.android.launcher3/.Launcher]
10-24 03:34:47.919 1663 1663 I am_on_resume_called: [0,com.android.launcher3.Launcher,RESUME_ACTIVITY]
[2]发现每次activity 栈顶切换时,都会出现 am_set_resumed_activity 和 am_resume_activity 的log,
根据这两个关键字在源码中搜索:
发现只有一个文件中显示:
android/frameworks/base/services/core/java/com/android/server/am/EventLogTags.logtags
# Activity set to resumed
30043 am_set_resumed_activity (User|1|5),(Component Name|3),(Reason|3)
# An activity has been resumed and is now in the foreground:
30007 am_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
[3]去掉关键字的"_"在源码中搜索出现的地方:
很遗憾"amresumeactivity"没有搜索到,有可能是打包到其他文件中。
但是,在中android/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 搜索到amsetresumedactivity关键字.
/**
* Update AMS states when an activity is resumed. This should only be called by
* {@link ActivityStack#onActivityStateChanged(ActivityRecord, ActivityState, String)} when an
* activity is resumed.
*/
@GuardedBy("this")
void setResumedActivityUncheckLocked(ActivityRecord r, String reason) {
final TaskRecord task = r.getTask();
if (task.isActivityTypeStandard()) {
if (mCurAppTimeTracker != r.appTimeTracker) {
// We are switching app tracking. Complete the current one.
if (mCurAppTimeTracker != null) {
mCurAppTimeTracker.stop();
mHandler.obtainMessage(
REPORT_TIME_TRACKER_MSG, mCurAppTimeTracker).sendToTarget();
mStackSupervisor.clearOtherAppTimeTrackers(r.appTimeTracker);
mCurAppTimeTracker = null;
}
if (r.appTimeTracker != null) {
mCurAppTimeTracker = r.appTimeTracker;
startTimeTrackingFocusedActivityLocked();
}
} else {
startTimeTrackingFocusedActivityLocked();
}
} else {
r.appTimeTracker = null;
}
// TODO: VI Maybe r.task.voiceInteractor || r.voiceInteractor != null
// TODO: Probably not, because we don't want to resume voice on switching
// back to this activity
if (task.voiceInteractor != null) {
startRunningVoiceLocked(task.voiceSession, r.info.applicationInfo.uid);
} else {
finishRunningVoiceLocked();
if (mLastResumedActivity != null) {
final IVoiceInteractionSession session;
final TaskRecord lastResumedActivityTask = mLastResumedActivity.getTask();
if (lastResumedActivityTask != null
&& lastResumedActivityTask.voiceSession != null) {
session = lastResumedActivityTask.voiceSession;
} else {
session = mLastResumedActivity.voiceSession;
}
if (session != null) {
// We had been in a voice interaction session, but now focused has
// move to something different. Just finish the session, we can't
// return to it and retain the proper state and synchronization with
// the voice interaction service.
finishVoiceTask(session);
}
}
}
if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
mUserController.sendForegroundProfileChanged(r.userId);
}
updateResumedAppTrace(r);
mLastResumedActivity = r;
mWindowManager.setFocusedApp(r.appToken, true);
applyUpdateLockStateLocked(r);
applyUpdateVrModeLocked(r);
EventLogTags.writeAmSetResumedActivity(
r == null ? -1 : r.userId,
r == null ? "NULL" : r.shortComponentName,
reason);
}
[4]我们找到打印log的地方,其实,我们只要打印log的下方写上自己逻辑即可,但是,我们想再深究一下。搜索是谁调用setResumedActivityUncheckLocked方法,发现是:
android/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
/**
* This should be called when an activity in a child task changes state. This should only
* be called from
* {@link TaskRecord#onActivityStateChanged(ActivityRecord, ActivityState, String)}.
* @param record The {@link ActivityRecord} whose state has changed.
* @param state The new state.
* @param reason The reason for the change.
*/
void onActivityStateChanged(ActivityRecord record, ActivityState state, String reason) {
if (record == mResumedActivity && state != RESUMED) {
setResumedActivity(null, reason + " - onActivityStateChanged");
}
if (state == RESUMED) {
if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:"
+ reason);
setResumedActivity(record, reason + " - onActivityStateChanged");
mService.setResumedActivityUncheckLocked(record, reason);
mStackSupervisor.mRecentTasks.add(record.getTask());
}
}
此方法注释写的比较清楚,当Activity的栈发生改变的时候,就触发此方法。若继续深入,必须了解ActivityRecord、TaskRecord、ActivityStack 之间的关系,网上介绍这块的内容比较多,可以耐心的查找阅读。
本文仅仅介绍遇到问题顺藤摸瓜的定位和解决方案.