概述:
本文针对用户对手机、平板等移动环境下应用的时空连续性需求,以及移动平台(android和 ios)自身的特点,提出一种移动应用的设计策略,来弥补目前移动应用设计的不足。希望达到的目标是当应用不是在用户的意志下关闭时(包括但不限于应用切换到后台被系统杀死、因自身的bug导致CRASH),当下次用户再次打开应用,这个应用应该展现出关闭前正常时的状态。
问题的提出:
1. 百度手机浏览器:当通过浏览器查阅新闻或者阅读博客时,如果有别的事情中断,我们会把浏览器切换到后台。回复一下QQ,发条短信,查看地图等,或许过了几个小时之后,我们想起来继续阅读之前的内容,此时打开浏览器,发现它又是重新启动,一片空白如图2。我需要到历史记录中打开,然后重新访问网络加载之前阅读的页面。
图1 阅读的技术博客 图2 切换到后台被系统杀死后再次打开
期望:当我们第二次打开时,希望它显示出来我上次把它切换到后台时阅读的页面以及当时阅读的位置。如果我们打开了多个页面,这些页面都应该还存在。并且在手机没有连接网络时,要从本地读取,如果手机有连接网络,才去判断是否从网络加载更新的内容。
解决方案:在设计时考虑移动应用的时空连续性
目前移动应用的设计方案主要是借鉴桌面应用的设计思路,一个应用的主要功能是完成了。但是我们再移动设备上使用的时候总会发现有些不便。关键在于桌面环境和移动环境的变化,引起人们对应用的需求有所变化。本文突出强调在移动环境下人们主要利用不连续时间去使用一个应用:
只要用户没有明确要退出程序而程序本身却被系统终止了,它都应该保存程序的状态,下次用户无论在哪里,在什么时间再次启动时,它都要恢复到程序被结束前的状态,就像它未曾被系统关闭过。
图3
两个应用相互切换的流程:
![](https://img-my.csdn.net/uploads/201302/21/1361437584_2898.png)
保存现场的时机有两个: onPause 和 onStop。从下面的Activity生命周期中可以看到Activity被切换到后台会先调用onPause 然后调用onStop。官方的说法onPause执行完成第二个Activity的onResume 才能够回调,所以不能再onPause 中加入太多的操作,很快执行。因此保存文件、写数据库的操作就要放到 onStop 中或者在 onSaveinstanceState中,如图6。数据的恢复则是在 onCreate或者 onRestoreInstanceState。
具体参考http://developer.android.com/guide/components/activities.html
图5
图7 官方文档说 onSaveInstanceState() 会在 onStop 前执行,但不保证它和 onPause 哪个先执行。我写了一段小程序发现调用顺序是 onPause->onSaveInstanceState->onStop。 所以大家可以默认是这样的处理过程,但既然官方说不保证,那么我们还是不要把有顺序依赖关系的代码写在这两个方法里面了。
参考设计和代码
图7
onPause 中保存当前tab 并暂停其他部件的执行
- @Override
- public void onPause() {
- ...
- mActivityPaused = true;
- Tab tab = mTabControl.getCurrentTab();
- if (tab != null) {
- tab.pause();
- if (!pauseWebViewTimers(tab)) {
- if (mWakeLock == null) {
- PowerManager pm = (PowerManager) mActivity
- .getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Browser");
- }
- mWakeLock.acquire();
- mHandler.sendMessageDelayed(mHandler
- .obtainMessage(RELEASE_WAKELOCK), WAKELOCK_TIMEOUT);
- }
- }
- mUi.onPause();
- mNetworkHandler.onPause();
- ...
- }
onSaveInstanceState() 中调用如下 方法来保存tab 的状态
- /**
- * save the tab state:
- * current position
- * position sorted array of tab ids
- * for each tab id, save the tab state
- * @param outState
- * @param saveImages
- */
- void saveState(Bundle outState) {
- final int numTabs = getTabCount();
- if (numTabs == 0) {
- return;
- }
- long[] ids = new long[numTabs];
- int i = 0;
- for (Tab tab : mTabs) {
- Bundle tabState = tab.saveState();
- if (tabState != null) {
- ids[i++] = tab.getId();
- String key = Long.toString(tab.getId());
- if (outState.containsKey(key)) {
- // Dump the tab state for debugging purposes
- for (Tab dt : mTabs) {
- Log.e(LOGTAG, dt.toString());
- }
- throw new IllegalStateException(
- "Error saving state, duplicate tab ids!");
- }
- outState.putBundle(key, tabState);
- } else {
- ids[i++] = -1;
- // Since we won't be restoring the thumbnail, delete it
- tab.deleteThumbnail();
- }
- }
- if (!outState.isEmpty()) {
- outState.putLongArray(POSITIONS, ids);
- Tab current = getCurrentTab();
- long cid = -1;
- if (current != null) {
- cid = current.getId();
- }
- outState.putLong(CURRENT, cid);
- }
- }
在 onCreate 或者 onRestoreInstanceState 来恢复之前保存的状态
- mTabControl.restoreState(icicle, currentTabId, restoreIncognitoTabs,
- mUi.needsRestoreAllTabs());
- List<Tab> tabs = mTabControl.getTabs();
- ArrayList<Long> restoredTabs = new ArrayList<Long>(tabs.size());
- for (Tab t : tabs) {
- restoredTabs.add(t.getId());
- }
- BackgroundHandler.execute(new PruneThumbnails(mActivity, restoredTabs));
- if (tabs.size() == 0) {
- openTabToHomePage();
- }
- mUi.updateTabs(tabs);
- // TabControl.restoreState() will create a new tab even if
- // restoring the state fails.
- setActiveTab(mTabControl.getCurrentTab());
- // Intent is non-null when framework thinks the browser should be
- // launching with a new intent (icicle is null).
- if (intent != null) {
- mIntentHandler.onNewIntent(intent);
- }
总结
当然对于普通Android应用来说需要保存的东西不太多,流程也比较明晰,但对于浏览器类的应用来说,要考虑到多 tab 页面,每个页面用户阅读到什么地方,可能会比较复杂。但无论如何笔者认为这是一个比较强烈的用户需求。
移动应用主要是占据大家的碎片时间,如果我们能够把碎片时间连续起来,这将能够大大的提升用户体验。
相反,如果时空不连续,每次打开都是从零开始,用户将会越来越不喜欢使用我们的应用。