anroid动态更新UI界面

背景

在android中,一成不变的UI布局可能会使用户厌烦(现在基本上都是ViewPager+ListView的方式),那么有没有什么方式实现动态更新UI布局提高用户的体验呢?答案是肯定的,本文就是介绍一种方式实现动态更新UI布局的方式。

技术途径

动态实现类补丁这篇文章中,我实现了动态加载类,它可以实现dalvik动态更新类(art原生支持文章提到方式),结合这篇文章我们可以很清楚明白,在实现动态更新类的时候,同时替换布局xml文件也是可以得。这个时候我们需要将dex文件,layout等资源文件一起打包生成APK。具体实现是在Activity setContentView():
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mContext = this;
    String apkPath = HookManager.getInstance().getPatchDir(mContext).getAbsolutePath() + File.separator + "DexTest.apk";
    PatchResource patchResource = ResourceManager.getInstance().getPatchResource(mContext, apkPath);
    int resId = patchResource.getResApkLayoutId("activity_main");
    if (resId <= 0) {
        setContentView(R.layout.activity_main);
    } else {
        setContentView(resId);
    }
    ....
}
而PatchResource类主要是对patch中的资源文件进行提取:具体实现:
/**
 * 获取apk里面的资源文件
 * Created by Jarlene on 2015/11/23.
 */
public class PatchResource {

    public static final String TAG = PatchResource.class.getSimpleName();

    private Resources res;// 获取的资源apk里面的res
    private String apkPackageName;// 资源apk里面的包名
    private PatchContext mPatchContext;

    public PatchResource(Context context, String apkPatch) {
        mPatchContext = new PatchContext(context, apkPatch);
        res = mPatchContext.getResources();
        apkPackageName = ApkUtils.getPackageInfo(context, apkPatch).packageName;
    }


    public PatchResource(Resources res, String apkPackageName) {
        this.res = res;
        this.apkPackageName = apkPackageName;
    }

    /**
     * 获取layout文件中的id号
     *
     * @param layoutName
     *            layout名
     */
    public int getResApkLayoutId(String layoutName) {
        Log.d(TAG, "getResApkLayoutId");
        return res.getIdentifier(layoutName, "layout", apkPackageName);
    }

    /**
     * 获取布局layout文件
     *
     * @param context
     *            上下文
     * @params layoutName
     * @return view
     */
    public View getResApkLayoutView(Context context, String layoutName) {
        Log.d(TAG,"getResApkLayoutView");
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        return inflater.inflate(res.getLayout(getResApkLayoutId(layoutName)), null);
    }

    /**
     * 获取控件view的id号
     *
     * @param widgetName
     *            控件名
     */
    public int getResApkWidgetViewID(String widgetName) {
        Log.d(TAG,"getResApkWidgetViewID");
        return res.getIdentifier(widgetName, "id", apkPackageName);
    }

    /**
     * 获取布局文件中的控件
     *
     * @params layout,资源apk中的布局(view)
     * @params widgetName 控件名称
     * @return widgetView
     */
    public View getResApkWidgetView(View layout, String widgetName) {
        Log.d(TAG,"getResApkWidgetView");
        return layout.findViewById(getResApkWidgetViewID(widgetName));
    }

    /**
     * 获取drawable文件的id
     *
     * @param imgName
     *            图片名字
     */
    public int getDrawableId(String imgName) {
        Log.d(TAG,"getDrawableId");
        return res.getIdentifier(imgName, "drawable", apkPackageName);
    }

    /**
     * 获取图片资源
     *
     * @param imgName
     * @return drawable
     */
    public Drawable getResApkDrawable(String imgName) {
        Log.d(TAG,"getResApkDrawable");
        return res.getDrawable(getDrawableId(imgName));
    }

    /**
     * 获取string文件中的id号
     *
     * @param stringName
     *            字符串在String文件中的名字
     */
    public int getResApkStringId(String stringName) {
        Log.d(TAG,"getResApkStringId");
        return res.getIdentifier(stringName, "string", apkPackageName);
    }

    /**
     * 获取String字符串
     *
     * @param stringName
     * @return string
     */
    public String getResApkString(String stringName) {
        Log.d(TAG,"getResApkString");
        return res.getString(getResApkStringId(stringName));
    }

    /**
     * 获取anim文件中的id号
     *
     * @param animationName
     */
    public int getResApkAnimId(String animationName) {
        Log.d(TAG,"getResApkAnimId");
        return res.getIdentifier(animationName, "anim", apkPackageName);
    }

    /**
     * 获取anim文件 XmlPullParser
     *
     * @param animationName
     * @return XmlPullParser
     */
    public XmlPullParser getResApkAnimXml(String animationName) {
        Log.d(TAG,"getResApkAnimXml");
        return res.getAnimation(getResApkAnimId(animationName));
    }

    /**
     * 获取动画anim
     *
     * @params animationName
     * @param context
     */
    public Animation getResApkAnim(Context context, String animationName) {
        Log.d(TAG,"getResApkAnim");
        Animation animation = null;
        XmlPullParser parser = getResApkAnimXml(animationName);
        AttributeSet attrs = Xml.asAttributeSet(parser);
        try {
            animation = createAnimationFromXml(context, parser, null, attrs);
        } catch (XmlPullParserException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return animation;
    }

    /**
     * 获取anim动画
     */
    private Animation createAnimationFromXml(Context c, XmlPullParser parser,
                                             AnimationSet parent, AttributeSet attrs)
            throws XmlPullParserException, IOException {
        Log.d(TAG,"createAnimationFromXml");
        Animation anim = null;
        int type;
        int depth = parser.getDepth();
        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            String name = parser.getName();
            if (name.equals("set")) {
                anim = new AnimationSet(c, attrs);
                createAnimationFromXml(c, parser, (AnimationSet) anim, attrs);
            } else if (name.equals("alpha")) {
                anim = new AlphaAnimation(c, attrs);
            } else if (name.equals("scale")) {
                anim = new ScaleAnimation(c, attrs);
            } else if (name.equals("rotate")) {
                anim = new RotateAnimation(c, attrs);
            } else if (name.equals("translate")) {
                anim = new TranslateAnimation(c, attrs);
            } else {
                throw new RuntimeException("Unknown animation name: "+ parser.getName());
            }
            if (parent != null) {
                parent.addAnimation(anim);
            }
        }
        return anim;
    }

    /**
     * 获取 color文件中的id号
     *
     * @param colorName
     */
    public int getResApkColorId(String colorName) {
        Log.d(TAG,"getResApkColorId");
        return res.getIdentifier(colorName, "color", apkPackageName);
    }

    /**
     * 获取color 值
     *
     * @param colorName
     * @return int
     */

    public int getResApkColor(String colorName) {
        Log.d(TAG,"getResApkColor");
        return res.getColor(getResApkColorId(colorName));
    }

    /**
     * 获取 dimens文件中的id号
     *
     * @param dimenName
     */
    public int getResApkDimensId(String dimenName) {
        Log.d(TAG,"getResApkDimensId");
        return res.getIdentifier(dimenName, "dimen", apkPackageName);
    }

    /**
     * 获取dimens文件中值
     *
     * @param dimenName
     * @return float
     */
    public float getResApkDimens(String dimenName) {
        Log.d(TAG,"getResApkDimens");
        return res.getDimension(getResApkDimensId(dimenName));
    }
}
里面的PatchContext主要是代理实现Context,具体如下:
/**
 * 主要为patch apk实现资源提取(伪Context)
 * Created by Jarlene on 2015/12/1.
 */
public class PatchContext extends ContextThemeWrapper {

    private AssetManager mAssetManager;
    private Resources mResources;
    private Resources      mProxyResource;
    private Context mContext;
    private String mPatchPath;

    public PatchContext(Context base, String apkPath) {
        super(base, 0);
        this.mContext = base;
        this.mProxyResource = base.getResources();
        this.mPatchPath = apkPath;

    }

    @Override
    public Resources getResources() {
        if (mResources == null) {
            mResources = new Resources(getAssets(), mProxyResource.getDisplayMetrics(),
                    mProxyResource.getConfiguration());
        }
        return mResources;
    }

    @Override
    public AssetManager getAssets() {
        if (mAssetManager == null) {
            mAssetManager = (AssetManager) newInstanceObject(AssetManager.class);
            invokeMethod(mAssetManager, "addAssetPath", new Class[]{String.class}, new Object[]{mPatchPath});
        }
        return mAssetManager;
    }

    private Object invokeMethod(Object obj, String methodName, Class[] valueType, Object[] values) {
        try {
            Class<?> clazz = obj.getClass();
            Method method = clazz.getDeclaredMethod(methodName, valueType);
            method.setAccessible(true);
            return method.invoke(obj, values);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Object newInstanceObject(Class<?> clazz){
        try {
            return clazz.getConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
到此为止就将patch中的资源提取出来了,同时伴随着Activity类一起加载。实现UI动态更新。
至于怎么生成APK,网上有很多教程,这里不再详细叙述。



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值