Android 插件化开发-主题皮肤更换

参考 http://www.2cto.com/kf/201501/366859.html

本项目是以插件化开发思想进行的,主要工作和代码如下

资源文件,这里以color资源为例

1、首先我们需要准备一个皮肤包,这个皮肤包里面不会包含任何Activity,里面只有资源文件,这里我为了简单,仅仅加入一个color.xml(其实就相当于Android系统中的framework_res.apk)

<!--?xml version="1.0" encoding="utf-8"?-->
<resources>
    <color name="main_btn_color">#E61ABD</color>
    <color name="main_background">#38F709</color>
     
    <color name="second_btn_color">#000000</color>
    <color name="second_background">#FFFFFF</color>
     
</resources>

2、将该资源打包成apk文件,放入sd卡中(实际项目你可以从我网络下载)

3、将需要换肤的Activity实现ISkinUpdate(这个可以自己随便定义名称)接口

public class MainActivity extends Activity implements ISkinUpdate,OnClickListener
{
    private Button btn_main;
    private View main_view;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      this.setContentView(R.layout.activity_main);
         
        SkinApplication.getInstance().mActivitys.add(this);
        btn_main=(Button)this.findViewById(R.id.btn_main);
    btn_main.setOnClickListener(this);
         
        main_view=this.findViewById(R.id.main_view);
         
         
         
         
    }
         
    @Override
    protected void onResume() {
      super.onResume();
      if(SkinPackageManager.getInstance(this).mResources!=null)
      {
        updateTheme();
        Log.d("yzy", "onResume-->updateTheme");
      }
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            //Toast.makeText(this, "change skin", 1000).show();
            File dir=new File(Environment.getExternalStorageDirectory(),"plugins");
             
            File skin=new File(dir,"SkinPlugin.apk");
            if(skin.exists())
            {
                  SkinPackageManager.getInstance(MainActivity.this).loadSkinAsync(skin.getAbsolutePath(), new loadSkinCallBack() {
          @Override
          public void startloadSkin() 
          {
            Log.d("yzy", "startloadSkin");
          }
           
          @Override
          public void loadSkinSuccess() {
            Log.d("yzy", "loadSkinSuccess");
            MainActivity.this.sendBroadcast(new Intent(SkinBroadCastReceiver.SKIN_ACTION));
          }
           
          @Override
          public void loadSkinFail() {
            Log.d("yzy", "loadSkinFail");
          }
        });
            }
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
 
    @Override
    public void updateTheme() 
    {
        // TODO Auto-generated method stub
        if(btn_main!=null)
        {
            try {
                Resources mResource=SkinPackageManager.getInstance(this).mResources;
                Log.d("yzy", "start and mResource is null-->"+(mResource==null));
                int id1=mResource.getIdentifier("main_btn_color", "color", "com.skin.plugin");
                btn_main.setBackgroundColor(mResource.getColor(id1));
                int id2=mResource.getIdentifier("main_background", "color","com.skin.plugin");
                main_view.setBackgroundColor(mResource.getColor(id2));
                //img_skin.setImageDrawable(mResource.getDrawable(mResource.getIdentifier("skin", "drawable","com.skin.plugin")));
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
     
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        SkinApplication.getInstance().mActivitys.remove(this);
        super.onDestroy();
    }
 
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        if(v.getId()==R.id.btn_main)
        {
            Intent intent=new Intent(this,SecondActivity.class);
            this.startActivity(intent);
        }
    }
}

这段代码里面主要看onOptionsItemSelected,这个方法里面,通过资源apk路径,拿到该资源apk对应Resources对象。我们直接看看SkinPacakgeManager里面做了什么吧

/**
 * 解析皮肤资源包
 * com.skin.demo.SkinPackageManager
 * @author yuanzeyao <br>
 * create at 2015年1月3日 下午3:24:16
 */
public class SkinPackageManager 
{
  private static SkinPackageManager mInstance;
  private Context mContext;
  /**
   * 当前资源包名
   */
  public String mPackageName;
   
  /**
   * 皮肤资源
   */
  public Resources mResources;
   
  private SkinPackageManager(Context mContext)
  {
    this.mContext=mContext;
  }
   
  public static SkinPackageManager getInstance(Context mContext)
  {
    if(mInstance==null)
    {
      mInstance=new SkinPackageManager(mContext);
    }
     
    return mInstance;
  }
   
   
  /**
   * 异步加载皮肤资源
   * @param dexPath
   *        需要加载的皮肤资源
   * @param callback
   *        回调接口
   */
  public void loadSkinAsync(String dexPath,final loadSkinCallBack callback)
  {
    new AsyncTask<string,void,resources>()
    {
 
      protected void onPreExecute() 
      {
        if(callback!=null)
        {
          callback.startloadSkin();
        }
      };
    
      @Override
      protected Resources doInBackground(String... params) 
      {
        try {
          if(params.length==1)
          {
            String dexPath_tmp=params[0];
            PackageManager mPm=mContext.getPackageManager();
            PackageInfo mInfo=mPm.getPackageArchiveInfo(dexPath_tmp,PackageManager.GET_ACTIVITIES);
            mPackageName=mInfo.packageName;
             
             
            AssetManager assetManager = AssetManager.class.newInstance();
            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
            addAssetPath.invoke(assetManager, dexPath_tmp);
             
            Resources superRes = mContext.getResources();
            Resources skinResource=new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
            SkinConfig.getInstance(mContext).setSkinResourcePath(dexPath_tmp);
            return skinResource;
          }
          return null;
        } catch (Exception e) {
          return null;
        } 
         
      };
       
      protected void onPostExecute(Resources result) 
      {
        mResources=result;
        
        if(callback!=null)
        {
          if(mResources!=null)
          {
            callback.loadSkinSuccess();
          }else
          {
            callback.loadSkinFail();
          }
        }
      };
       
    }.execute(dexPath);
  }
   
  /**
   * 加载资源的回调接口
   * com.skin.demo.loadSkinCallBack
   * @author yuanzeyao <br>
   * create at 2015年1月4日 下午1:45:48
   */
  public static interface loadSkinCallBack
  {
    public void startloadSkin();
     
    public void loadSkinSuccess();
     
    public void loadSkinFail();
  }
   
   
  
}

调用loadSkinAsync后,如果成功,就会发送一个换肤广播,并将当前皮肤apk的路径保存到sp中,便于下次启动app是直接加载该皮肤资源。接受换肤广播是在SkinApplication中注册的,当接收到此广播后,随即调用所有已经启动,并且需要换肤的Activity的updateTheme方法,从而实现换肤。

public class SkinApplication extends Application 
{
    private static SkinApplication mInstance=null;
     
    public ArrayList<iskinupdate> mActivitys=new ArrayList<iskinupdate>();
     
    @Override
    public void onCreate() {
        // TODO Auto-generated method stub
        super.onCreate();
        mInstance=this;
        String skinPath=SkinConfig.getInstance(this).getSkinResourcePath();
        if(!TextUtils.isEmpty(skinPath))
        {
          //如果已经换皮肤,那么第二次进来时,需要加载该皮肤
          SkinPackageManager.getInstance(this).loadSkinAsync(skinPath, null);
        }
         
        SkinBroadCastReceiver.registerBroadCastReceiver(this);
    }
     
    public static SkinApplication getInstance()
    {
        return mInstance;
    }
     
    @Override
    public void onTerminate() {
        // TODO Auto-generated method stub
        SkinBroadCastReceiver.unregisterBroadCastReceiver(this);
        super.onTerminate();
    }
     
    public void changeSkin()
    {
        for(ISkinUpdate skin:mActivitys)
        {
            skin.updateTheme();
        }
    }
}
时间: 2024-08-08 17:52:46

Android 插件化开发-主题皮肤更换的相关文章

Android插件化开发---运行未安装apk中的Service

如果你还不知道什么叫插件化开发,那么你应该先读一读之前写的这篇博客:Android插件化开发,初入殿堂 上一篇博客主要从整体角度分析了一下Android插件化开发的几个难点与动态加载没有被安装的apk中的Activity和资源的方法.其实一般的插件开发主要也就是加载个Activity,读取一些资源图片之类的.但是总有遇到特殊情况的时候,比如加载Service. 要动态加载Service,有两种思路:一是通过NDK的形式,将Service通过C++运行起来(这种方法我没有尝试,只听群里的朋友说实现

Android插件化开发,初入殿堂

好久没有写博客了,这次准备写写我这几天的研究成果--Android插件化开发框架CJFrameForAndroid. 好久没有写博客了,这次准备写写我这几天的研究成果--Android插件化开发框架CJFrameForAndroid. 背景交代 首先,你需要知道什么是插件化开发.就拿最常见的QQ来说,在第三个界面动态那里有个管理,点开后可以选择很多的增植功能,这里腾讯只放了一些网页应用,那么如果未来想加入一个打飞机游戏,要怎么做?让用户重新安装吗,这就是插件化开发所解决的问题. 用一句话来概括插

详解Android插件化开发-资源访问

动态加载技术(也叫插件化技术),当项目越来越庞大的时候,我们通过插件化开发不仅可以减轻应用的内存和CPU占用,还可以实现热插拔,即在不发布新版本的情况下更新某些模块. 通常我们把安卓资源文件制作成插件的形式,无外乎有一下几种: zip.jar.dex.APK(未安装APK.安装APK) 对于用户来讲未安装的APK才是用户所需要的,不安装.不重启,无声无息的加载资源文件,这正是我们开发者追求的结果. 但是,开发中宿主程序调起未安装的插件apk,一个很大的问题就是资源如何访问,这些资源文件的ID都映

Android插件化开发之解决Atlas组件在宿主的注册问题

OpenAtlas有一个问题,就是四大组件必须在Manifest文件中进行注册,那么就必然带来一个问题,插件中的组件都要重复在宿主中注册.像Service,ContentProvider等组件目前没有什么好的解决方法,只能在宿主中注册.但是像Activity,显然是有解决方法的,就是使用Fragment代替Activity,Activity只是作为一个放Fragment的容器,那么不仅在插件中不用再清单文件中注册,就连宿主的注册问题也一并解决了.那么,解决方案呢,没错,就是之前写的一篇博文And

Android插件化开发-hook动态代理

首先,我们阐述为什么android需要插件化: 1:由于业务的增长,app的方法数逐渐达到65535(有人说用于检索方法数的列表大小使用short存储的,其实我看了源码之后并没有发现相关信息,并对此说法产生了怀疑,不过最后找到的结果就是,65535这个限制可能是由于dalvik的bytecode大小限制的,具体的可以查看官方文档). 2:一个模块的变化都要整体编译一次app,维护成本太大了,用插件开发app会好很多 对于以上问题解决方案不少,著名的有h5,hybird,不过这些都没有native

Android 插件化开发(三):资源插件化

在前面的文章中我们成功的加载了外部的Dex(Apk)并执行了插件的Bean代码.这时我们会想,能不能加载并运行插件Apk的Activity.答案当然是能,否则后续我们的研究就没意义了,但是想实现Activity的插件化运行,我们必须要解决一个问题——如何使用插件中的资源. 本文我们就讲一下插件的资源加载机制,并讲述一下如何实现资源的插件化. 一.资源的加载机制 Android的资源文件分为两类: 第一类是res目录下存放的可编辑的资源文件,这类文件在编译时系统会自动在R文件中生成资源文件的16进

android插件化开发——加载广播

阅读本文前,先阅读前面几篇: http://blog.csdn.net/u013022222/article/details/51171720 引言 在android开发过程中,我们不可避免的会使用广播,比如,侦听开机,侦听短信. 而对于广播,我想很多人都知道他有两种类型,动态广播,通过代码在runtime进行register, 像这样: IntentFilter intentFilter = new IntentFilter("com.chan.plugin.receiver");

Android插件化开发之DexClassLoader动态加载dex、jar小Demo

一.温故动态加载ClassLoader机制 如果对Android的ClassLoader加载机制不熟悉,猛戳Android插件化开发动态加载基础之ClassLoader工作机制 http://blog.csdn.net/u011068702/article/details/53248960 二.介绍 我们知道在Android中可以跟java一样实现动态加载jar,但是Android使用德海Dalvik VM,不能直接加载java打包jar的byte code,需要通过dx工具来优化Dalvik

Android插件化开发之Hook StartActivity方法

第一步.先爆项目demo照片,代码不多,不要怕 第二步.应该知道Java反射相关知识 如果不知道或者忘记的小伙伴请猛搓这里,Android插件化开发基础之Java反射机制研究 http://blog.csdn.net/u011068702/article/details/49863931 第三步.应该知道Java静态代理知识 如果不知道或者忘记的小伙伴请猛搓这里,Android插件化开发基础之静态代理模式 http://blog.csdn.net/u011068702/article/detai