在进行android应用开发过程中,常会遇到这样的错误:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
(译:指定的这个view已经有一个parent了,你必须在此view之前先调用此view的parent的removeView()方法。)
异常信息中的"child's parent"实质上是一个ViewParent对象,可以使用此方法得到这个对象:
ViewParent vp = view.getParent();
ViewParent类简介:
package android.view;
/**
* Defines the responsibilities for a class that will be a parent of a View.
* This is the API that a view sees when it wants to interact with its parent.
*
*/
public interface ViewParent {...}
ViewParent是一个接口,它位于android.view包下。这个接口定义了可作为一个view的parent的规则。当view想要与其parent进行交互时,可以通过此接口来完成。
查看View源码,发现View类中有成员变量"protected ViewParent mParent;",到这里也就明白了view是如何“与其parent进行交互”的了。
继续来看getParent()函数的注释:
//Gets the parent of this view. Note that the parent is a ViewParent and not necessarily a View.
// the indirect subclasses of the ViewParent
// AbsListView, AbsSpinner, AbsoluteLayout, AdapterView<T extends Adapter>,
// AppWidgetHostView, DatePicker, DialerFilter, ExpandableListView, FrameLayout,
// Gallery, GestureOverlayView, GridView, HorizontalScrollView, ImageSwitcher, LinearLayout,
// ListView, MediaController, RadioGroup, RelativeLayout, ScrollView, SlidingDrawer, Spinner,
// TabHost, TabWidget, TableLayout, TableRow, TextSwitcher, TimePicker, TwoLineListItem, ViewAnimator,
// ViewFlipper, ViewGroup, ViewSwitcher, WebView, ZoomControls
这里列举了多种间接实现ViewParent接口的View类,例如熟悉的ViewGroup。既然ViewGroup实现了此接口,那么LinearLayout、FrameLayout就无需赘言了。
但是当我调用view.getParent()方法时,文档注明:Gets the parent of this view. Note that the parent is a ViewParent and not necessarily a View.
(译:获取此view的parent,需要注意,parent是ViewParent类,并且parent对象不能保证肯定属于View类)
也就是说,虽然现阶段ViewParent的接口实现类都是view类,但是不能够保证今后扩展的接口实现类一定会继承于View,这一点需要注意。
下面是示例代码,用以说明在开发AlertDialog的过程中,什么情形下会出现此bug。
public class MainActivity extends Activity implements View.OnClickListener {
private static final String TAG = "tag";
Button button1;
Button button2;
Button button3;
View view;
AlertDialog alertDialog1 = null;
AlertDialog alertDialog2 = null;
AlertDialog alertDialog3 = null;
ViewGroup vg = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
view = View.inflate(this,R.layout.dialog_layout,null);
button1 = (Button) this.findViewById(R.id.button1);
button2 = (Button) this.findViewById(R.id.button2);
button3 = (Button) this.findViewById(R.id.button3);
button1.setOnClickListener(this);
button2.setOnClickListener(this);
button3.setOnClickListener(this);
}
<span style="white-space:pre"> </span>//button1和button2的响应事件做为对比,button3为解决方案。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
alertDialog1 = new AlertDialog.Builder(this)
.setTitle("title of the dialog 1")
.setMessage("content of the dialog 1")
.setIcon(R.drawable.ic_launcher)
.create();
alertDialog1.show();
break;
case R.id.button2:
alertDialog2 = new AlertDialog.Builder(this)
.setView(view)
.setTitle("title of the dialog 2")
.setMessage("content of the dialog 2")
.setIcon(R.drawable.ic_launcher)
.create();
alertDialog2.show();
break;
case R.id.button3:
//method 1.
//this method is available
// view = View.inflate(this,R.layout.dialog_layout,null);
//method 2.
//this method is available
if(view != null && view.getParent() != null){
Log.d(TAG, "view != null && view.getParent() != null");
ViewParent vp = view.getParent();
if(vp instanceof ViewGroup){
vg = (ViewGroup)vp;
vg.removeAllViews();
}
}
alertDialog3 = new AlertDialog.Builder(this)
.setView(view)
.setTitle("title of the dialog 3")
.setMessage("content of the dialog 3")
.setIcon(R.drawable.ic_launcher)
.create();
alertDialog3.show();
//method 3.
//this method is available, but you cant see the view in alertDialog3
// ViewParent vp = view.getParent();//Gets the parent of this view. Note that the parent is a ViewParent and not necessarily a View.
// Log.d(TAG, "vp.toString() is " + vp.toString());
// if(vp instanceof ViewGroup){
// vg = (ViewGroup)vp;
// vg.removeAllViews();
// }
break;
}
}
}
一、对比说明:button1和button2的动作事件只有一点不同:button2中的alertDialog2多设置了setView(view),这造成了第一次点击button2时正常,第二次点击就fatal了。
二、原因分析:第一次点击button2,view重新被setView给alertDialog2,view中的mViewParent由null变为特定值,由于view是成员变量,引用的内存不变,当第二次点击button2时,还是这个view,但被setView给新的AlertDialog,ViewParent发生改变,
根据android的规定,一个view只能拥有0或1个parent,当给已经具有parent的view重新设定新的parent时,需要先用旧parent remove掉view,否则抛出IllegalStateException异常。
三、解决方案:解决方案有2种,1.重新new一个view;2.在setView()之前调用view的parent的removeAllViews()方法,见method2,需要注意的是,removeAllViews()方法是ViewGroup才拥有的方法,View类不具有此方法。
我在使用method 3 时,虽然不会出现此错误,但是,由于view在其parent上被移除了,因此也就没有了显示效果。