Android Fragment 生命周期问题(onActivityResult 上的 NullPointerException)

Posted

技术标签:

【中文标题】Android Fragment 生命周期问题(onActivityResult 上的 NullPointerException)【英文标题】:Android Fragment lifecycle issue (NullPointerException on onActivityResult) 【发布时间】:2014-06-08 09:40:56 【问题描述】:

我遇到了一个问题,我找不到任何解释。 我有一个使用 TabManager 显示片段的 FragmentActivity,如下所示:

public class WorkOrderFormTabFragmentActivity extends FragmentActivity 
TabHost mTabHost;
TabManager mTabManager;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.work_order_form_tab_new);
    mTabHost = (TabHost)findViewById(android.R.id.tabhost);
    mTabHost.setup();

    mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent);

    mTabManager.addTab(mTabHost.newTabSpec("form").setIndicator("Form"),
            WorkOrderFormFragment.class, null);
    mTabManager.addTab(mTabHost.newTabSpec("pictures").setIndicator("Pictures"),
            PictureListFragment.class, null);

    if (savedInstanceState != null) 
        mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
    


@Override
protected void onSaveInstanceState(Bundle outState) 
    super.onSaveInstanceState(outState);
    outState.putString("tab", mTabHost.getCurrentTabTag());


public static class TabManager implements TabHost.OnTabChangeListener 
    private final FragmentActivity mActivity;
    private final TabHost mTabHost;
    private final int mContainerId;
    private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
    TabInfo mLastTab;

    static final class TabInfo 
        private final String tag;
        private final Class<?> clss;
        private final Bundle args;
        private Fragment fragment;

        TabInfo(String _tag, Class<?> _class, Bundle _args) 
            tag = _tag;
            clss = _class;
            args = _args;
        
    

    static class DummyTabFactory implements TabHost.TabContentFactory 
        private final Context mContext;

        public DummyTabFactory(Context context) 
            mContext = context;
        

        @Override
        public View createTabContent(String tag) 
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        
    

    public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) 
        mActivity = activity;
        mTabHost = tabHost;
        mContainerId = containerId;
        mTabHost.setOnTabChangedListener(this);
    

    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) 
        tabSpec.setContent(new DummyTabFactory(mActivity));
        String tag = tabSpec.getTag();

        TabInfo info = new TabInfo(tag, clss, args);

        info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag);
        if (info.fragment != null && !info.fragment.isDetached()) 
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            ft.detach(info.fragment);
            ft.commit();
        

        mTabs.put(tag, info);
        mTabHost.addTab(tabSpec);
    

    @Override
    public void onTabChanged(String tabId) 
        TabInfo newTab = mTabs.get(tabId);
        if (mLastTab != newTab) 
            FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction();
            if (mLastTab != null) 
                if (mLastTab.fragment != null) 
                    //ft.detach(mLastTab.fragment);
                    ft.hide(mLastTab.fragment);
                
            
            if (newTab != null) 
                if (newTab.fragment == null) 
                    newTab.fragment = Fragment.instantiate(mActivity,
                            newTab.clss.getName(), newTab.args);
                    ft.add(mContainerId, newTab.fragment, newTab.tag);
                 else 
                    //ft.attach(newTab.fragment);
                    ft.show(newTab.fragment);
                
            

            mLastTab = newTab;
            ft.commit();
            mActivity.getSupportFragmentManager().executePendingTransactions();
        
    

在此 FragmentActivity 的第二个选项卡中,用户可以管理图片列表并使用相机添加更多图片。

片段代码:

public class PictureListFragment extends Fragment 

static final int TAKE_PICTURE_ACTIVITY = 1;
static final int EDIT_PICTURE_ACTIVITY = 2;

FormPictureListAdapter lvAdapter;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup viewgrp,
    Bundle savedInstanceState) 
    // Inflate the layout for this fragment
    View cont = inflater.inflate(R.layout.form_picture_list, viewgrp, false);

    LinearLayout container = (LinearLayout)cont.findViewById(R.id.formPictureListLayout);
    try

        final Context context = getActivity();
        ListView ls2 = new ListView(context);
        // clear previous results in the LV
        ls2.setAdapter(null);
        // populate
        ArrayList<MFPicture> pictures = new ArrayList<MFPicture>();
        //pictures.add(0, new MFPicture());
        pictures.addAll(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());

        lvAdapter = new FormPictureListAdapter(context, pictures);
        ls2.setAdapter(lvAdapter);
        LinearLayout.LayoutParams Params = new LinearLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT, 0, 1f);
        ls2.setLayoutParams(Params);
        ls2.setOnItemClickListener(new OnItemClickListener() 
          public void onItemClick(AdapterView<?> parent, View view,
                    int position, long id) 
              final MFPicture picture = ((MFPictureView)view).getPicture();
              final int idx = position;
              DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() 
                  @Override
                  public void onClick(DialogInterface dialog, int which) 
                      switch (which)
                      case DialogInterface.BUTTON_POSITIVE:
                          //Edit picture
                          EditPictureActivity.setPicture(picture);
                          Intent configurationOpen = new Intent(getActivity(), EditPictureActivity.class);
                          startActivityForResult(configurationOpen, EDIT_PICTURE_ACTIVITY);
                          break;

                      case DialogInterface.BUTTON_NEGATIVE:
                          //Delete picture
                          ((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures().remove(idx);  
                          MFUtils.deleteFile(picture.getPath());
                          lvAdapter.updatePictureList(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());
                          lvAdapter.notifyDataSetChanged();
                          break;
                      
                  
              ;
              AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
              builder.setMessage(getResources().getString(R.string.wo_bem_regie_list_el_action)).setPositiveButton(getResources().getString(R.string.wo_bem_regie_list_el_edit), dialogClickListener)
                    .setNegativeButton(getResources().getString(R.string.wo_bem_regie_list_el_delete), dialogClickListener).show();
          
         );
        container.addView(ls2);

        LayoutInflater layoutInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View view=layoutInflater.inflate(R.layout.add_btn_bottom,container);
        view.setBackgroundResource(R.drawable.list_selector_even);
        TextView text = (TextView)view.findViewById(R.id.title);
        text.setText(getResources().getString(R.string.wo_picturelist_add));
        view.setOnClickListener(new OnClickListener() 
            @Override
            public void onClick(View v)
                v.setBackgroundResource(R.drawable.list_selector_even);
                String pictureFile = ((MFApplication)getActivity().getApplication()).getNextPictureFile();
                String picPath = MFUtils.MF_STORAGE_PATH+"/"+pictureFile;
                Log.e("FormPictureListActivity", "PicturePath : "+picPath);
                //setBackgroundResource(android.R.drawable.list_selector_background);
                try 
                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(picPath)));
                    startActivityForResult(intent, TAKE_PICTURE_ACTIVITY);
                 catch (ActivityNotFoundException e) 
                    Log.e("FormPictureListActivity", "Call failed", e);
                
            
      );
    
    catch(Exception e)
      e.printStackTrace();
      Log.e("FormPictureListActivity", "Error:", e);
    

    return cont;


@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) 
    //super.onActivityResult(requestCode, resultCode, data);
    String pictureFile = ((MFApplication)getActivity().getApplication()).getPictureFile();
    Log.d("FormPictureListActivity", "ActivityResult:"+resultCode);
    Log.d("FormPictureListActivity", "ActivityResult-picFile:"+pictureFile);
    if (requestCode == TAKE_PICTURE_ACTIVITY)
        if(resultCode == getActivity().RESULT_OK)
            Log.d("FormPictureListActivity", "ActivityResult:OK");
            MFPicture picture = new MFPicture(MFPicture.TYPE_PICTURE, MFUtils.MF_STORAGE_PATH+"/"+pictureFile);
            ((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures().add(picture);
            lvAdapter.updatePictureList(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());
            lvAdapter.notifyDataSetChanged();
            EditPictureActivity.setPicture(picture);
            Intent configurationOpen = new Intent(getActivity(), EditPictureActivity.class);
            startActivityForResult(configurationOpen, EDIT_PICTURE_ACTIVITY);
        
    
    else if (requestCode == EDIT_PICTURE_ACTIVITY)
        EditPictureActivity.getPicture().setComment(EditPictureActivity.getPicture().getCommentUIValue());
        lvAdapter.updatePictureList(((MFApplication)getActivity().getApplication()).getCurrentForm().getPictures());
        lvAdapter.notifyDataSetChanged();
    


在我的测试设备(Nexus 5、Galaxy Nexus、Galaxy Mini 2)上一切正常,但我不时收到来自我无法访问的其他设备(主要是运行 Android 4.0.4 的设备)的错误:

java.lang.RuntimeException: Unable to resume activity 

com.mf.mobile.android/com.mf.mobile.android.WorkOrderFormTabFragmentActivity: java.lang.RuntimeException: Failure delivering result ResultInfowho=null, request=131073, result=-1, data=null to activity com.mf.mobile.android/com.mf.mobile.android.WorkOrderFormTabFragmentActivity: java.lang.NullPointerException
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2616)
at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:2644)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2130)
at android.app.ActivityThread.access$600(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1248)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4645)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:809)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:576)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: Failure delivering result ResultInfowho=null, request=131073, result=-1, data=null to activity com.mf.mobile.android/com.mf.mobile.android.WorkOrderFormTabFragmentActivity: java.lang.NullPointerException
at android.app.ActivityThread.deliverResults(ActivityThread.java:3156)
at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2599)
... 12 more
Caused by: java.lang.NullPointerException
at com.timewise.mobile.android.fragments.PictureListFragment.onActivityResult(PictureListFragment.java:138)
at android.support.v4.app.FragmentActivity.onActivityResult(FragmentActivity.java:166)
at android.app.Activity.dispatchActivityResult(Activity.java:4662)
at android.app.ActivityThread.deliverResults(ActivityThread.java:3152)
... 13 more

NPE 出现在这行代码中: lvAdapter.updatePictureList(((MframeApplication)getActivity().getApplication()).getCurrentForm().getPictures());

这意味着此时 lvAdapter 变量为空。但是这个变量应该已经在 Fragment 的 onCreateView 中设置了......这让我觉得,在某些时候 Fragment 可能已经在没有调用 onCreateView 的情况下被重新创建。

我找不到有关此问题的任何解释。你能帮我解决这个问题吗?

非常感谢

【问题讨论】:

【参考方案1】:

好的,我认为如果用户打开您的应用然后点击主页按钮,然后点击显示正在运行的应用程序按钮(主页按钮右侧的那个)或按住主页按钮,就会发生这种情况某些手机​​上的按钮,然后选择您的应用程序以返回到它。然后,如果您查看生命周期,它将调用 onResume(即上面的堆栈跟踪中的内容),而无需再次调用 onCreateView。

Fragment Lifecycle

现在,您应该能够通过进入 Nexus 5 上的开发者选项并选择不保留活动来模仿这种情况以查看它的发生。然后打开您的应用程序,转到该片段,点击主页按钮,然后点击正在运行的应用程序按钮并选择您的应用程序,我认为它会向您显示该异常。

【讨论】:

你好 Kaediil,首先'坦克你!让我重现这个NPE!所以在 onResume 函数中处理 ListView 适配器才是正确的处理方式,对吧?但是为什么 'lvAdapter' 变量在恢复时变为空? 它变为 null 因为 onDestroy 被调用,否则底层活动已被操作系统销毁以回收内存。作为 Activity 生命周期的参考资料,但是当有人在低内存设备上离开您的应用程序时,它会成为被回收/杀死的候选对象,从而从您的身下消失。 如果对您有帮助,请给答案投票并将其标记为正确吗?

以上是关于Android Fragment 生命周期问题(onActivityResult 上的 NullPointerException)的主要内容,如果未能解决你的问题,请参考以下文章

Android基础——控件的混合生命周期

Android Fragment生命周期及静态加载

Android之——Fragment生命周期(日志截图版)

Android Fragment 生命周期问题(onActivityResult 上的 NullPointerException)

Android 片段生命周期

android fragment相互切换的时候生命周期怎么走