Android应用插件化开发介绍了基于DexClassLoader和Fragment的android插件开发基本想法.宿主应用提供FragmentActivity框架用于加载每个F插件中提供的诸多Fragment,插件的DexClassLoader以及Context、Resources都可以在FragmentActivity、Fragment内部统一构造处理(具体可以参考Activity和Fragment生命周期自行设计,源码涉及到公司隐私)。
我们在开发过程中,多个Fragment插件都要公用的基础插件,它可以是纯代码的apk,也可以是自定义View的(带资源的)apk。对于纯代码的基础插件,直接使用ClassLoader加载即可,没有任何问题。但对于类似自定义View的公共基础插件apk,此时就会存在两个android平台限制的问题。
1.自定义View一般都会在资源中添加自定义属性,即在/values/attrs.xml文件中添加如下自定义属性。下面的例子是CircleImageView的自定义属性。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleImageView">
<attr name="border_width" format="dimension" />
<attr name="border_color" format="color" />
</declare-styleable>
</resources>
可以使用这些自定义属性达到简便的布局复用,
<com.test.widget.CircleImageView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/iv_head_image"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_avatar"
app:border_color="#f7f7f7f7"
app:border_width="2dp" />
如果要使用 CircleImageView的自定义属性,就需要将其attrs.xml中生命的CircleImageView的自定义属性拷贝到对应的Fragment插件工程中。不然无法找到border_color和border_width两个属性的定义。
然后问题又来了,如果拷贝了CircleImageView的自定义attrs属性后,Fragment插件和CircleImageView自定义View基础插件,在分别打包apk,其中border_color和border_width两个属性所定义的id值很可能会不一样。也就是说Fragment插件中设置的border_color和border_width两个属性,在CircleImageView插件中无法正确解析出来,导致设置失效。
对于较为简单的Fragment插件,其中没有其他attrs属性定义的话,此时Fragment插件和CircleImageView自定义View基础插件中CircleImageView的自定义属性对应的id值是相同的,可以正常使用。但复杂的Fragment插件怎么办啊?
2.CircleImageView相对来说很简单,没有复杂的内部布局。如果某个自定义View组件,很复杂,其中不仅仅有attrs属性,而且其内部布局也较为复杂,其中包含较多内部view id。假设该自定义View名称为CustomViewGroup,在它顶部是如下布局。
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<FrameLayout
android:id="@+id/fl_inner"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:background="@color/bg_color"
android:paddingBottom="@dimen/header_footer_top_bottom_padding"
android:paddingLeft="@dimen/header_footer_left_right_padding"
android:paddingRight="@dimen/header_footer_left_right_padding"
android:paddingTop="@dimen/header_footer_top_bottom_padding" >
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<ProgressBar
android:id="@+id/progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>
</merge>
而 某个Fragment插件的Fragment使用该 CustomViewGroup 构造其界面,并在 CustomViewGroup中一些布局
<com.test.widget.CustomViewGroup
xmlns:ptr="http://schemas.android.com/apk/res-auto"
android:id="@+id/pull_refresh_scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="1000dp" >
<LinearLayout
android:id="@+id/ll_first_layout"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp" />
</LinearLayout>
<com.test.widget.CustomViewGroup />
此时CustomViewGroup中包含了一个FrameLayout(id为R.id.fl_inner)和一个LinearLayout(id为R.id.ll_first_layout),由于这两个id值的R文件定义分别位于两个apk中( CustomViewGroup自定义View基础插件和Fragment插件),此时 存在一定可能性会出现R.id.fl_inner与R.id.ll_first_layout相同的场景。尼玛,然后各种奇葩问题就来了。
(1)首先我门在Fragment中使用findViewById(R.id.ll_first_layout)的时候就会出现ClassCastException(无法将Framelayout强制转换为LinearLayout);
(2)其次,onRestoreInstanceState()也会出错,两个View的状态恢复错乱了,即使用LinearLayout的保存状态来恢复FrameLayout。。。凡此种种啊。
看着这些个问题,立马就蛋疼了。
对于上面说的问题1和2,尝试去寻找解决方法。
首先想到的是android.jar的使用,所有android工程都要依赖android.jar,且android.R文件也在该jar包中。而且,我们使用系统控件时,其属性都是可以正常使用的,比如andorid::layout_width属性就在android.R文件中有申明。
留带后续补充吧。