Android | java.lang.IllegalStateException: The specified child already has a parent. 解决方案

在进行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.


进一步阅读API说明文档,有如下说明:

//	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上被移除了,因此也就没有了显示效果。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值