项目中主页面是四个Tab页面,是singleTop的模式,采用的结构是TabHost+Fragment的结构。用户可以通过OpenURL指定调到主页面的某一个Tab页面。暂定主页面四个TAB标记为A,B,C,D。
要求:
1、用户未登录的情况下,如果切换到D,弹出登录页面,进行登录。如果未登录成功,返回时回到上一个Tab;如果登录成功,显示D。即如果是C--》D,用户未登录,回来时显示C;如果登录成功,显示D。
2、用户可以通过OpenUrl强制调出主页面并且指定显示某个Tab。
问题:
在特殊机型红米1S,发生一个奇怪现象,用户登录成功以后,应该显示D,但是显示的是A,而且很快就发生崩溃了。
原因分析:
通过日志分析,查找到了原因,因为通过OpenUrl调用主页面切换到D时,主页面被销毁掉了,因此自动重新创建,使用TabHost.setCurrentTab设置切换到A(home);紧接着通过OpenUrl又发送了一个onNewIntent事件,解析参数然后也调用TabHost.setCurrentTab设置切换到D(owner)。TabHost初始化切换选项卡出现了死循环,这是TabHost组件的缺陷。
03-24 20:47:52.248 1562-1562/com.jindan.p2p D/MainActivity: onCreate
03-24 20:47:52.288 1562-1562/com.jindan.p2p D/MainActivity: initIntentData currentTab:[home] target tabId:[null]
03-24 20:47:52.578 1562-1562/com.jindan.p2p D/MainActivity: onNewIntent
03-24 20:47:52.578 1562-1562/com.jindan.p2p D/MainActivity: initIntentData currentTab:[home] target tabId:[3]
03-24 20:47:52.638 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[home]
03-24 20:47:52.658 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[ower]
03-24 20:47:52.828 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[home]
03-24 20:47:52.828 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[ower]
03-24 20:47:53.008 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[home]
03-24 20:47:53.008 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[ower]
03-24 20:47:53.198 1562-1562/com.jindan.p2p D/MainActivity: switchFragment tabId:[home]
解决问题的角度和方法:
从两个方面来考虑这个问题:
1)外部的请求是随机的,随时可以调用主页面的TAB,因此,必须要能够保证响应调用请求,又要保证合适的响应。例如1s内,可能收到10个请求,A-->B-->C-->C...-->D,这种情况下,很显然我们不必要响应所有的请求,因为主页面呈现给用户的只有一个TAB,我们只需要响应最新的请求,最终变为目标TAB即可。
2)TabHost初始化选项卡的处理。初始化选项卡时要保证一个初始化完成后,再开始下一次初始化,规避初始化过程中叠加初始化,出现选项卡切换死循环的现象。
解决方法:
1)请求优化策略方面:通过阻塞队列保存所有的请求(put)。读取请求时,我们读取最新的请求(poll),即队列尾部的请求。
2)TabHost选项换初始化方面:记录上一个初始化成功的时间,如果当前的初始化已经大于最小初始化间隔,直接初始化;如果小于,则采用延时初始化。
代码示例:
只摘取了关键代码,不可以直接运行的。代码格式有些乱。
public class MainActivity extends FragmentActivity {
/** Tab选项切换 */ private static final int MAIN_MSG_TAB_CHANGE = 101; /** TabSpec初始化 */ private static final int MAIN_MSG_INIT_TABSPEC = 102; /** TabSpec插入请求 */ private static final int MAIN_MSG_ADD_TABSPEC = 103;
/** TabSpec初始化的时间间隔 */ private static final int TAB_INIT_DURATION = 300;
/** FragmentTabHost各Tab项TAG */ private final String[] TAB_TAGS = {"home", "finance", "discover", "ower"};/** 上一次TAB选项卡切换信息 */ private TabSpecHolder lastTabSpec = new TabSpecHolder();/** 存储需要切换的TABSpec */ private LinkedBlockingQueue<Integer> tabSpecQueue = new LinkedBlockingQueue<Integer>();/** 选项卡的信息 */ class TabSpecHolder { public TabSpecHolder() { tabSpecId = -1; lastSwitchTime = 0; nextSwitchTime = 0; } /** 当前项的索引位置 */ public int tabSpecId; /** 选项卡的切换时间 */ public long lastSwitchTime; /** 下一次预计需要的初始化时间 */ public long nextSwitchTime; }/** * Tab选项控件 * 说明: * 1)FragmentTabHost控件, Fragment嵌入WebView时,有些机型中WebView容易丢失焦点(例如弹出输入法), * 这是FragmentTabHost的BUG。 * 2)采用TabHost,切换选项卡的时候,内容区使用Fragment显示。这种情况下Fragment嵌入WebView * 不会丢失焦点。 */ private TabHost mTabHost;@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);initIntentData(getIntent());}
@Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initIntentData(intent); }
/** * Intent参数处理 * */ private void initIntentData(Intent intent) {
//获取原来的TabId int newTabId = mTabHost.getCurrentTab(); try { String tabId = intent.getStringExtra("tab"); if(!TextUtils.isEmpty(tabId)) { newTabId = Integer.parseInt(tabId); if(newTabId < 0) { newTabId = 0; } else if(newTabId >= TAB_TAGS.length) { newTabId = TAB_TAGS.length - 1; } } } catch (Exception e) { e.printStackTrace(); }//需要切换的目标Tab请求追加到队列末尾 if(newTabId != lastTabSpec.tabSpecId) { //需要进行切换 Message msg = mHandler.obtainMessage(); msg.what = MAIN_MSG_ADD_TABSPEC; msg.arg1 = newTabId; mHandler.sendMessage(msg);}}
private Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case MAIN_MSG_ADD_TABSPEC: //添加一个新的TabSpec addTabSpec(msg.arg1); break; case MAIN_MSG_INIT_TABSPEC: //TabSpec初始化 initTabSpec(); break; case MAIN_MSG_TAB_CHANGE: //Tab切换(选项卡已经切换) switchTab((String)msg.obj); break; default: break; } }; };
/** * 初始化TabSpec * 说明: * 1)获取一个有效的TabSpec进行初始化。如果队列中有很多个请求, * 只是用最后一个有效的TabId。 * 2)进行TabSpec切换时,需要保证两个Tab切换之间的时间间隔,防止出现TabSpec切换出现死循环。 * 若小于最小启动时间间隔,采用延时启动;其他可以直接切换。 */ private synchronized void initTabSpec() { //检测是否已经没有请求了 if (tabSpecQueue.isEmpty()) { return; } int lastTabId = 0; //获取最后一个tabSpec lastTabId = tabSpecQueue.peek().intValue(); while (true) { Integer value = tabSpecQueue.poll(); if (null != value) { lastTabId = value.intValue(); } else { break; } } //调度启动频率防止出现死循环 long currentTime = System.currentTimeMillis(); if (lastTabSpec.tabSpecId < 0) { //初次初始化 lastTabSpec.lastSwitchTime = currentTime; lastTabSpec.nextSwitchTime = lastTabSpec.lastSwitchTime + TAB_INIT_DURATION; mTabHost.setCurrentTab(lastTabId); } else if (currentTime > lastTabSpec.lastSwitchTime) { //如果是死循环式的频换启动 if ((lastTabSpec.nextSwitchTime - currentTime) >= 30 * 1000L) { //30s内死循环式的初始化全部丢弃 } else { //30s以内的初始化请求直接追加 lastTabSpec.nextSwitchTime += TAB_INIT_DURATION; if ((currentTime - lastTabSpec.lastSwitchTime) > TAB_INIT_DURATION) { //当前时间已经超过一个TabSpec初始化时间立即启动 lastTabSpec.lastSwitchTime = currentTime; lastTabSpec.tabSpecId = lastTabId; mTabHost.setCurrentTab(lastTabId); } else { //不足一个启动时间间隔 final int finalLastTabId = lastTabId; mHandler.postDelayed(new Runnable() { //延时初始化 @Override public void run() { long curTime = System.currentTimeMillis(); lastTabSpec.lastSwitchTime = curTime; lastTabSpec.tabSpecId = finalLastTabId; mTabHost.setCurrentTab(finalLastTabId); } }, lastTabSpec.nextSwitchTime - currentTime); }//else-init-duration }//else-30s-request } } /** * 添加一个TabSpec初始化请求 * 说明:因为是同步添加,防止阻塞UI线程,采用消息方式延时添加 * * @param tabId :新的TabSpec ID */ private void addTabSpec(int tabId) { if (tabId >= 0 && tabId < TAB_TAGS.length) { //如果是一个有效的TabId try { tabSpecQueue.put(tabId); mHandler.sendEmptyMessage(MAIN_MSG_INIT_TABSPEC); } catch (InterruptedException e) { e.printStackTrace(); } } }
}