TabHost+Fragment初始化出现奔溃问题

针对TabHost+Fragment结构的应用在特定机型上出现的Tab切换死循环及崩溃问题,本文提出了通过阻塞队列管理和延时初始化的方法来优化Tab切换流程,有效避免了问题的发生。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     项目中主页面是四个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();
      }
   }
}


}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值