Android Fragment控件注意点一则

这是最近写继承自Fragment的自定义控件时遇到的一个问题。其实说来也是自己偷懒没有深入研究Fragment造成的。下面是一段有问题的代码:

public class MainActivity extends Activity implements View.OnClickListener {
    private Button click;
    private MyFragment myFragment;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        prepareFragment();
 
        click = (Button) findViewById(R.id.click);
 
        click.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.click:
                showFragment();
                break;
            default:
                break;
        }
    }
    
    private void prepareFragment() {
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        myFragment = new MyFragment();
        fragmentTransaction.add(R.id.root, myFragment);
        fragmentTransaction.show(myFragment);
        fragmentTransaction.commit();
    }
 
    private void showFragment() {
        if (myFragment == null) {
            prepareFragment();
        }
        
        myFragment.testMethod();
    }
}
 
 
public class MyFragment extends Fragment {
    private TextView textView;
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View root = inflater.inflate(R.layout.fragment_my, container, false);
        textView = (TextView) root.findViewById(R.id.text_view);
        return root;
    }
 
    public void testMethod(String c) {
        textView.setText(c);
    }
 
}

在运行的时候有时点击Button程序会崩溃,报NullPointerException错误,即textView还没有创建。那为什么有时候是正常运行,有时候不能正常运行呢?深入研究Fragment的生命即可知道。下图是一张官方的Fragment生命周期图:

Fragment生命周期

在内存中创建一个MyFragment对象时该对象可能只是进行到onCreate这一步,而并没有执行onCreateView这个生命步骤,所以在这个时候对Fragment中元素进行操作肯定是会报NullPointerException错误的。正确的做法是将这些初始化参数打包成一个Bundle,通过setArguments方法传入Fragment中。当onCreateView执行时对Fragment元素进行初始化。切忌不要从外部使用公共方法来进行初始化。下面时一段修正后的代码:

public class MainActivity extends Activity implements View.OnClickListener {
    private Button click;
    private MyFragment myFragment;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        click = (Button) findViewById(R.id.click);
 
        click.setOnClickListener(this);
    }
 
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.click:
                showFragment();
                break;
            default:
                break;
        }
    }
 
    private void showFragment() {
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        myFragment = new MyFragment();
        Bundle args = new Bundle();
        args.putString("c", "hi fragment");
        myFragment.setArguments(args);
        fragmentTransaction.add(R.id.root, myFragment);
        fragmentTransaction.show(myFragment);
        fragmentTransaction.commit();
    }
}
 
 
public class MyFragment extends Fragment {
    private TextView textView;
 
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View root = inflater.inflate(R.layout.fragment_my, container, false);
        textView = (TextView) root.findViewById(R.id.text_view);
        textView.setText(getArguments().getString("c");
        return root;
    }
}