Android换肤功能设计与实现

来源:互联网 发布:声音处理 傻瓜 软件 编辑:程序博客网 时间:2024/06/02 22:51

MIUI系统最具特色的功能就是系统级的主题换肤,能够更换任何可见的元素。像桌面ICON、桌面文件夹、桌面壁纸、APP中的各种图片资源、字体等等。如果一个ROM想像MIUI一样,支持这种功能的话,那么这个功能是如何实现的那。从功能实现角度划分,可以分成第三方也能换的,还有只有系统能换的。这里主要是Android系统开放的各种服务,实现换肤的功能。比如壁纸,铃声这些,通过系统的相关接口,可以实现对这些功能的更换。MIUI其它的换肤功能,主要是对APP资源的更换,这个功能应该说最具特色。下面主要对这个功能的实现及主题管理APP的开发中遇到的问题,进行一下说明,会持续几篇博客。

    从理论上讲,对APP更换资源(桌面也是APP),可以与特定应用定义相关接口,从而有针对性的实现对资源的更换。如桌面,现在市面上的很多桌面都支持自身的换肤功能。可以更换ICON图库、图标背板等等。如果以这种方式实现换肤功能的话,那么换肤模块就要与各个APP定义相关的接口,将对应的资源放于约定好的位置,通过Intent广播方式通知各个APP,使各个APP重新加载。但这样做势必增加各个APP的复杂程度,同时不能更新第三方APP的资源。 如果需要做到对ROM中任何APP都可以进行换肤,那么就需要深入到Android系统对资源的加载、与使用部分去寻找答案了。

    简单将Android系统提取资源的相关类关系如上图所示,在我们所使用的Activity的getResource调用的是Context这个虚基类的接口,而如果想对Context的具体实现有一个深入的了解,那么可以去看ContextImpl。简单介绍一下Android系统对资源的提取方式,Android中,对资源的加载,通过Context获取Resources,最后调用的是AssetManager的openNonAsset()加载资源。Android系统通过资源ID来标识不同的资源,ID大于0x01000000为系统资源,否则为app自带资源。通过包名来确定不同的资源包,读取资源文件。

    Android系统中所有的资源都是以文件夹压缩包的形式存在的,这就是Android的APP在编译是所做的事,我们知道在Android的APP中,所有的应用代码被编译为dex格式的文件,那res资源那,其实所有的资源文件只是简单地使用zip文件压缩到APK应用包中。在Android手机进行安装时,

其实过程就是 1:解压APK应用包。2.解析文件夹下的xml文件,向系统注册包信息。主要包括启动Activity文件名,应用包名。3.将APK包拷贝到/data/app文件夹,以包名命名。在/data/app下面的就是各个应用所对应的资源包了,其实就是apk包的拷贝。在资源加载过程中,通过应用包名确定资源包的位置。直接到/data/app下面查找相应的资源包。提取对应的资源文件。

特殊的几个资源包:

1.系统资源包,在/system/framework/framework-res

   2.系统应用资源包,其实系统应用与普通应用一样,都是通过包名来确定资源包的位置的,所不同的是系统应用是在系统编译时直接编译为Android.mk中指定的包名,并拷贝到/system/app下面的,不存在上面所说的安装应用、解析的部分。其资源包就是/system/app下面的APK本体。包名就是在编译时,在Android.mk中指定的名称。

 

以上对Android系统资源加载的过程进行了简单地描述,按着这条思路走,基本可以实现对资源选择加载及替换了。

由于本篇涉及到比较底层的东西,部分属于产品级核心,不能详述请见谅,接下来的几篇,主要会说一下主题管理APP的开发,会进行详细介绍。


整体来说,换肤功能的上层APP的主要功能如下:

      1.访问网络获取主题列表。

      2.下载主题包。

      3.在本地管理主题包。

      4.应用主题包,触发换肤功能。

    下面会重点描述该APP的设计与技术难点,主要以Android4.0系统作为实现目标平台,使用相应SDK。使用MVC典型分层设计,对APP进行大体划分。对于该APP首先需要确定与后台的交互协议,即使是大体上的交互协议。分别对应上述各功能,简单的需求分析后,得到如下简单实现方案。

      1.访问网络主题列表,通过主题类型,获取主题缩略图,根据皮肤包编号获取皮肤详细预览图。

      2.下载主题包,根据主题URL,使用DownloadManager下载主题包。

      3.在本地管理主题包。在下载完后,在本地进行解压,存放在指定目录,并插入对应数据库,提供应用、删除等基本操作。

      4.应用主题包,触发换肤功能。应用主题包,需要触发相关的系统换肤模块。

      根据上述实现方案,绘制概要设计对应UML图,如下:

    

    根据实现方案,抽象出各类。底层主要抽象:

     1.主题数据。2.数据库实现

    Control
    1.ZIP压缩、解压操作 2.文件(夹)拷贝、删除操作。

     3.网络数据访问 4. 与界面的相关交互。

      View

      数据展示界面。

 这一节详细介绍一下Model层的设计,本身并无太多难点,采用标准的Provider结构访问底层数据库。简单UML图如下:

    通过ThemeProvider统一访问数据库具体实现ThemeDBHelper。通过向ThemeProvider添加相应的Observer来监听数据库的变化。这里属于标准的Provider操作、及sqlite操作,由于不涉及到多个应用数据共享的操作,只是使用Provider接管、简化对数据库的访问操作,所以实现相对简单,唯一需要注意的是由于需要Observer来监听数据库的变化,所以在Provider的相关操作后,需要通过sendNotification来通知相关监听器。

[java] view plaincopy
  1. import android.content.ContentProvider;  
  2. import android.content.ContentUris;  
  3. import android.content.ContentValues;  
  4. import android.content.Context;  
  5. import android.database.Cursor;  
  6. import android.database.sqlite.SQLiteDatabase;  
  7. import android.database.sqlite.SQLiteQueryBuilder;  
  8. import android.net.Uri;  
  9. import android.text.TextUtils;  
  10. import android.util.Log;  
  11.   
  12. public class ThemeProvider extends ContentProvider {  
  13.       
  14.     private final static String TAG = ThemeProvider.class.toString();  
  15.   
  16.     public static final String AUTHORITY = "com.tencent.themedatabase";// 对外提供服务接口名  
  17.   
  18.     public static final String NOTIFICATION = "notify";// 是否通知提示  
  19.   
  20.     private static ThemeDBHelper mThemeDBHelper;  
  21.   
  22.     private Context mContext;  
  23.   
  24. //  public ThemeProvider(Context context) {  
  25. //      mContext = context;  
  26. //      mThemeDBHelper = new ThemeDBHelper(context);  
  27. //  
  28. //  }  
  29.   
  30.     private long checkandInsert(SQLiteDatabase db, ContentValues cv,  
  31.             String table, String nullColumnHack) {  
  32.         if (cv.get(ThemeDBHelper._ID) == null) {  
  33.             throw new RuntimeException("Error : the Insert value has no id");  
  34.         }  
  35.         return db.insert(table, nullColumnHack, cv);  
  36.     }  
  37.   
  38.     private void sendNotify(Uri uri) {  
  39.   
  40.         String notify = uri.getQueryParameter(NOTIFICATION);  
  41.         if (notify == null || notify.equals("true")) {  
  42.             mContext.getContentResolver().notifyChange(uri, null);  
  43.               
  44.         }  
  45.   
  46.     }  
  47.   
  48.     public static long generateNewId() {  
  49.         return mThemeDBHelper.generateNewId();  
  50.     }  
  51.   
  52.     @Override  
  53.     public int delete(Uri uri, String selection, String[] selectionArgs) {  
  54.         /**/  
  55.         SqlArguments sqlargs = new SqlArguments(uri, selection, selectionArgs);  
  56.         SQLiteDatabase db = mThemeDBHelper.getWritableDatabase();  
  57.   
  58.         int count = db.delete(sqlargs.table, sqlargs.where, sqlargs.selection);  
  59.         if (count > 0) {  
  60.             sendNotify(uri);  
  61.         }  
  62.   
  63.         return 0;  
  64.     }  
  65.   
  66.     @Override  
  67.     public String getType(Uri uri) {  
  68.         SqlArguments sqlarg = new SqlArguments(uri);  
  69.         if (TextUtils.isEmpty(sqlarg.where)) {  
  70.             return "vnd.android.cursor.dir/" + sqlarg.table;  
  71.         } else {  
  72.             return "vnd.android.cursor.item/" + sqlarg.table;  
  73.         }  
  74.   
  75.     }  
  76.   
  77.     @Override  
  78.     public Uri insert(Uri uri, ContentValues values) {  
  79.         SqlArguments sqlargs = new SqlArguments(uri);  
  80.         SQLiteDatabase db = mThemeDBHelper.getWritableDatabase();  
  81.         long id = checkandInsert(db, values, sqlargs.table, null);  
  82.         if (id < 0) {  
  83.             return null;  
  84.         }  
  85.         uri = ContentUris.withAppendedId(uri, id);  
  86.         sendNotify(uri);  
  87.         return uri;  
  88.     }  
  89.   
  90.     @Override  
  91.     public int bulkInsert(Uri uri, ContentValues[] values) {  
  92.         SqlArguments sqlargs = new SqlArguments(uri);  
  93.         SQLiteDatabase db = mThemeDBHelper.getWritableDatabase();  
  94.           
  95.         db.beginTransaction();  
  96.         try {  
  97.             for(int i=0; i<values.length;i++){  
  98.                 if(checkandInsert(db, values[i],sqlargs.table, null) <0){  
  99.                     return 0;  
  100.                 }  
  101.             }  
  102.             db.setTransactionSuccessful();  
  103.         } catch (Exception e) {  
  104.             e.printStackTrace();  
  105.         }  
  106.         finally{  
  107.             db.endTransaction();  
  108.         }  
  109.         sendNotify(uri);  
  110.         return values.length;  
  111.     }  
  112.   
  113.     @Override  
  114.     public boolean onCreate() {  
  115.   
  116.         mContext = this.getContext();  
  117.         mThemeDBHelper = new ThemeDBHelper(mContext);  
  118.         return true;  
  119.     }  
  120.   
  121.     @Override  
  122.     public Cursor query(Uri uri, String[] projection, String selection,  
  123.             String[] selectionArgs, String sortOrder) {  
  124.         SqlArguments sqlargs = new SqlArguments(uri, selection, selectionArgs);  
  125.         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();  
  126.   
  127.         qb.setTables(sqlargs.table);  
  128.         SQLiteDatabase db = mThemeDBHelper.getReadableDatabase();  
  129.   
  130.         Cursor cr = qb.query(db, projection, sqlargs.where, sqlargs.selection,  
  131.                 nullnull, sortOrder);  
  132.         cr.setNotificationUri(mContext.getContentResolver(), uri);  
  133.         return cr;  
  134.     }  
  135.   
  136.     @Override  
  137.     public int update(Uri uri, ContentValues values, String selection,  
  138.             String[] selectionArgs) {  
  139.         SqlArguments sqlargs = new SqlArguments(uri, selection, selectionArgs);  
  140.         SQLiteDatabase db = mThemeDBHelper.getWritableDatabase();  
  141.         int count = db.update(sqlargs.table, values, sqlargs.where,  
  142.                 sqlargs.selection);  
  143.         Log.e(TAG,"GET WHERE "+sqlargs.where);  
  144.         if (count > 0) {  
  145.             sendNotify(uri);  
  146.         }  
  147.   
  148.         return count;  
  149.     }  
  150.   
  151.     static class SqlArguments {  
  152.         /** 
  153.          * 处理sql语句,解析各个参数 
  154.          * */  
  155.         public final String table;  
  156.         public final String where;  
  157.         public final String[] selection;  
  158.   
  159.         public SqlArguments(Uri uri, String where, String[] selection) {  
  160.   
  161.             int argscount = uri.getPathSegments().size();  
  162.             if (argscount == 1) {  
  163.                 // uri://host/table  
  164.                 this.table = uri.getPathSegments().get(0);  
  165.                 Log.e("pluszhang","parse uri "+this.table);  
  166.                 this.where = where;  
  167.                 this.selection = selection;  
  168.             } else if (argscount != 2) {  
  169.                 // uri://host/table/id/...  
  170.                 throw new IllegalArgumentException("Invalidate URI " + uri);  
  171.             } else if (!TextUtils.isEmpty(where)) {  
  172.                 // uri://host/talbe/id,where应该为空,已经指明id  
  173.                 throw new UnsupportedOperationException(  
  174.                         "WHERE opt not support " + uri);  
  175.             } else {  
  176.                 // uri://host/table/id  
  177.                 this.table = uri.getPathSegments().get(0);  
  178.                 this.where = "_id=" + uri.getPathSegments().get(1);  
  179.                 this.selection = null;  
  180.             }  
  181.   
  182.         }  
  183.   
  184.         public SqlArguments(Uri uri) {  
  185.             if (uri.getPathSegments().size() == 1) {  
  186.                 this.table = uri.getPathSegments().get(0);  
  187.                 this.where = null;  
  188.                 this.selection = null;  
  189.             } else {  
  190.                 throw new IllegalArgumentException("Invalidate URI " + uri);  
  191.             }  
  192.         }  
  193.   
  194.     }  
  195.   

http://blog.csdn.net/zyplus/article/details/7655965