Android插件开发之-换肤功能前篇

来源:互联网 发布:app软件测试工程师 编辑:程序博客网 时间:2024/05/16 12:28

1. 前言


这人一闲着没事干就喜欢作,昨天研究了一天的换肤效果,今天总算有点眉目了,我不会录屏所以只能截个图了,效果效果来来来:

     这里写图片描述

     这里写图片描述

那么如何使用呢?

    // 1.将资源插件打成apk,成功后将其改名为skin_plugin.skin,后缀名也可以随便取名,    // 将其copy到手机根目录下面,实际项目中肯定从网上下载    // 2.建一个Application,在onCrate()中初始化皮肤管理    SkinManager.getInstance().init(this);    // 3.新建一个BaseActivity extends Activity , 其他Activity继承它    public abstract class BaseActivity extends Activity implements OnClickListener {        @Override        protected void onCreate(Bundle savedInstanceState) {            super.onCreate(savedInstanceState);            // 捕捉闪退信息            CustomCrashHandler.getInstance().setCustomCrashHanler(this);            // 统一管理Activity            AppManagerUtil.instance().addActivity(this);            // 设置布局主布局            int contentViewId = getContentViewId();            if (contentViewId == 0) {                throw new IllegalArgumentException("get content view id is zero!");            }            setContentView(contentViewId);            // 注册皮肤管理            SkinManager.getInstance().register(this);            // 初始化头部            initTitle();            // 初始化界面            initView();            // 初始化数据            initData();    }        protected abstract int getContentViewId();        public abstract void initTitle();        public abstract void initView();        public abstract void initData();        @Override        protected void onDestroy() {            super.onDestroy();            SkinManager.getInstance().unregister(this);        }    }    // 4.在布局中,我们给View添加tag : 规则是这样的skin:main_bg:background ->    // 开头:资源id:类型 = 以skin开头 + 插件中的资源Id名称 + 操作类型是src 或者 textColor ...    // 5.调用换肤的方法    SkinManager.getInstance().changeSkin(mSkinPkgPath,         "com.imooc.skin_plugin", new ISkinChangingCallback()                {                    @Override                    public void onStart()                    {                    }                    @Override                    public void onError(Exception e)                    {                        Toast.makeText(MenuActivity.this, "换肤失败", Toast.LENGTH_SHORT).show();                    }                    @Override                    public void onComplete()                    {                        Toast.makeText(MenuActivity.this, "换肤成功", Toast.LENGTH_SHORT).show();                    }                });    }

2.分析和实现


2.1分析:

根据效果能想到的就这么几种方式,不知道有没有更好的实现方式,先这么搞吧:

1. 建一个资源插件项目,该项目中只存放资源,没有Activity,将其在后台运行起来,当我们在主程序中需要的换肤的时候去检测运行的插件apk,通过sharedUserId加上反射去获取资源,达到换肤的效果,这种方式需要运行插件apk;
2. 资源内置在主apk中,通过动态的改变其后缀,加载不同的本地资源达到换肤的效果;
3. 自己目前用的这种方式,皮肤可以是zip可以是apk或是其他,我们联网去下载皮肤包,通过资源管理去获取插件资源,达到换肤的效果,这种方式不需要运行插件apk。

2.2实现第一种:

1.先制作资源插件apk

    1.新建一个SummerSkinPlugin项目在mipmap-xhdpi中放置一张main_bg的图片;    2.在androidMainfest中配置android:sharedUserId="com.example.hui.summerplug.background"    3.将资源插件打包成apk即可

2.主程序换肤,新建AndroidExchangeSkin项目,在MainActivity先测试一下

    // 在androidMainfest中配置android:sharedUserId="com.example.hui.summerplug.background"    // 布局中就放置一个mExchangeSkinBt按钮,和一个全屏mMainIv图片    // 插件列表    private List<Map<String,String>> mPulginList;    public void onClick(View view){        // 查找插件列表        mPulginList = findPluginList();        if(mPulginList == null || mPulginList.size() == 0){            Toast.makeText(this,"目前没有皮肤,请下载皮肤",Toast.LENGTH_LONG).show();            return;        }        // 用dialog显示插件的名称列表,当条目点击时获取当前插件条目map:        // 1.获取插件上下文        Context pluginContext = findPluginContext(map);        // 2.根据插件资源加载器,加载资源        int resourceId = findResourceId(pluginContext,map);        Log.e("TAG",resourceId+"");        if(resourceId != 0){            // 直接设置是不行的, (必须通过插件来加载)            // iv.setImageResource(resouceId);            iv.setImageDrawable(pluginContext.getResources().getDrawable(resourceId));        }    }    /**    * 查找插件列表    **/    private List<Map<String, String>> findPluginList() {        List<Map<String,String>> pluginList = new ArrayList<>();        // 获取包管理器        PackageManager packageManager = this.getPackageManager();        // 获取手机已安装的包信息        List<PackageInfo> packageInfos = packageManager.getInstalledPackages(            PackageManager.GET_ACTIVITIES);        try {            // 获取当前APP的包信息            PackageInfo currentPackageInfo = this.getPackageManager().                getPackageInfo(getPackageName(),0);            /**            * 循环包信息,根据sharedUserId去找插件            **/            for (PackageInfo packageInfo:packageInfos){                String packageName =  packageInfo.packageName;                String sharedUserId = packageInfo.sharedUserId;                if(TextUtils.isEmpty(sharedUserId) || !                     sharedUserId.equals(currentPackageInfo.sharedUserId) ||                         packageName.equals(currentPackageInfo.packageName)){                    // 如果是空,或者与主程序shared user id 不同 ,或者报名相同                    continue;                }                // 加载插件                Map<String,String> pluginMap = new HashMap<>();                // 获取插件程序的名称                String label = packageInfo.applicationInfo.loadLabel(packageManager).toString();                // 获取包名称                pluginMap.put("packageName",packageName);                pluginMap.put("label",label);                // 添加插件                pluginList.add(pluginMap);            }        } catch (PackageManager.NameNotFoundException e) {            e.printStackTrace();        }        return pluginList;    }    /**     * 获取插件上下文     */    private Context findPluginContext(Map<String, String> map) {        try{            return this.createPackageContext(map.get("packageName"),                 Context.CONTEXT_IGNORE_SECURITY);        }catch(Exception e){            e.printStackTrace();        }        return null;    }    /**     * 查找资源的Id     * 利用反射     */    private int findResourceId(Context context,Map<String, String> map) {        String packageName =  map.get("packageName");        // 通过类加载器加上反射获取图片资源Id        ClassLoader classLoader = new PathClassLoader(context.getPackageCodePath(),            PathClassLoader.getSystemClassLoader());        try {            Class clazz =  Class.forName(packageName+".R$mipmap",true,classLoader);            Field[] fields =  clazz.getFields();            for(Field field : fields){                String name = field.getName();                if(name.endsWith("main_bg")){                    return field.getInt(R.drawable.class);                }            }        } catch (Exception e) {            e.printStackTrace();        }        return 0;    }

  将插件皮肤apk和主程序都运行起来就会惊奇的发现可以换肤了,我们可以新建两套或者更多的插件皮肤,当然这只是图片,文字肯定也是一样的,这只是单个功能实现。用到项目中我们每次都写这么多代码肯定是不行的,所以肯定要进行封装的。
  最后就要感谢Hongyang了,自己是看你的代码和你的慕课视频成长的http://blog.csdn.net/lmj623565791/article,万分感谢啊!

源码下载地址:http://download.csdn.net/detail/z240336124/9430544

0 0
原创粉丝点击