anroid动态更新UI界面

Posted Jarlene

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,网上有很多教程,这里不再详细叙述。


以上是关于anroid动态更新UI界面的主要内容,如果未能解决你的问题,请参考以下文章

使用Swing进行动态界面设计

游戏UI系统设计

eclipse中anroid adk添加

Unity开发日记--Lua开发游戏UI界面

PostgreSQL 15 中值得关注的“大更新”

Anroid四大组件service之本地服务