Android中应用主题设置之APK主题文件,主要想法是把主题素材打包成APK,然后安装到手机,而目标程序可以获得主题APK信息及其相关资源。获得资源可以用公共接口方法,反射,Android内部提供的IPC通信技术等实现。

无障碍访问另一个APK中的资源的一个简单方法是设置相同的android:sharedUserId,至于原因参考开发者网站:http://developer.android.com/guide/topics/manifest/manifest-element.html

如果主题APK和目标程序中可被编译的资源完全一样,可以通过主题APK的Context获得resource,然后根据resource id获得想要资源即可;否则,需要通过int android.content.res.Resources.getIdentifier(String name, String defType, String defPackage)方法先获得对应名称的资源id,然后再获得资源。

简单的主题工具类:

import android.content.Context;import android.content.pm.PackageManager.NameNotFoundException;import android.graphics.drawable.Drawable;/** * 主题工具类<br> * 目前支持Drawable和String类型数据资源,如果需要其它资源,需要另外添加代码处理,当然也要进行测试喽!:) * @author oss */public class ThemeUtil {/** * 远程主题包的Context引用 */private static Context remoteContext;/** * 远程主题包的包名引用 */private static String remotePackageName;/** * 本Application的Context引用 */private static Context appContext;/** * 本工具类实例 */private static ThemeUtil instance;/** * 构造函数 * @param context 本Application的Context * @param remkotePackageName 远程主题包的包名 * @throws NameNotFoundException */public ThemeUtil(Context context, String remkotePackageName)throws NameNotFoundException {// 创建远程主题包的Context引用remoteContext = context.createPackageContext(remkotePackageName, Context.CONTEXT_IGNORE_SECURITY);ThemeUtil.remotePackageName = remkotePackageName;ThemeUtil.appContext = context.getApplicationContext();}/** * 获得工具类实例 * @param context * @param remkotePackageName * @return * @throws NameNotFoundException */public static ThemeUtil getInstance(Context context, String remkotePackageName)throws NameNotFoundException {if (instance == null) {instance = new ThemeUtil(context, remkotePackageName);}return instance;}/** * 更新工具类实例 * @param context * @param remkotePackageName * @return * @throws NameNotFoundException */public static ThemeUtil refresh(Context context, String remkotePackageName)throws NameNotFoundException {instance = new ThemeUtil(context, remkotePackageName);return instance;}/** * 根据resId获得Drawable对象 * @param resId * @return */public Drawable getDrawable(int resId) {return remoteContext.getResources().getDrawable(remoteContext.getResources().getIdentifier(appContext.getResources().getResourceEntryName(resId),appContext.getResources().getResourceTypeName(resId),remotePackageName));}/** * 根据resId获得String对象 * @param resId * @return */public String getString(int resId) {return remoteContext.getResources().getString(remoteContext.getResources().getIdentifier(appContext.getResources().getResourceEntryName(resId),appContext.getResources().getResourceTypeName(resId),remotePackageName));}/** * 根据resId获得View对象 * @param resId * @return */public View getLayout(int resId) {String name = appContext.getResources().getResourceEntryName(resId);String defaultType = appContext.getResources().getResourceTypeName(resId);XmlResourceParser p =  remoteContext.getResources().getLayout(remoteContext.getResources().getIdentifier(name,defaultType,remotePackageName));LayoutInflater inflater = (LayoutInflater)remoteContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);return inflater.inflate(p, null);}}

说明:

  • 只支持图片和字符串,其它资源操作需要添加相关代码;
  • 示例中添加一个public View getLayout(int resId)方法,来根据resId获得需要的布局对象;

主题显示的Activity代码:

import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.preference.PreferenceManager;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;public class SkinApkDemoActivity extends Activity implements OnClickListener {private static final int REQUEST_CODE = 131422;private TextView informationTextView;private Button titleLeftButton;private Button titleRightButton;private Button themeButton;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);informationTextView = (TextView) findViewById(R.id.information);titleLeftButton = (Button) findViewById(R.id.title_left_button);titleRightButton = (Button) findViewById(R.id.title_right_button);themeButton = (Button) findViewById(R.id.theme_button);titleLeftButton.setOnClickListener(this);titleRightButton.setOnClickListener(this);themeButton.setOnClickListener(this);// 初始化主题initialTheme(PreferenceManager.getDefaultSharedPreferences(this).getString("package_name", getPackageName()));}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.title_left_button:Toast.makeText(this, "L", Toast.LENGTH_SHORT).show();break;case R.id.title_right_button:Toast.makeText(this, "R", Toast.LENGTH_SHORT).show();break;case R.id.theme_button:// 打开主题设置Activity页面startActivityForResult(new Intent(this, ThemeSelectedActivity.class), REQUEST_CODE);break;default:break;}}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);// 请求Code匹配 && 返回Code匹配 && 数据匹配if (requestCode == REQUEST_CODE && resultCode == RESULT_OK && data != null) {String packageName = data.getStringExtra("package_name");if (packageName != null) {try {// 更新主题工具类实例ThemeUtil.refresh(this, packageName);} catch (Exception e) {e.printStackTrace();}// 跟新主题initialTheme(packageName);// 保存主题信息,示例仅仅以包名来保存主题相关信息PreferenceManager.getDefaultSharedPreferences(this).edit().putString("package_name", packageName).commit();}}}/** * 更新当前页面内的UI主题效果 * @param packageName */private void initialTheme(String packageName) {try {informationTextView.setText(ThemeUtil.getInstance(this, packageName).getString(R.string.hello));titleLeftButton.setBackgroundDrawable(ThemeUtil.getInstance(this, packageName).getDrawable(R.drawable.button_left));titleRightButton.setBackgroundDrawable(ThemeUtil.getInstance(this, packageName).getDrawable(R.drawable.button_right));((View) titleLeftButton.getParent()).setBackgroundDrawable(ThemeUtil.getInstance(this, packageName).getDrawable(R.drawable.background));themeButton.setBackgroundDrawable(ThemeUtil.getInstance(this, packageName).getDrawable(R.drawable.button));} catch (Exception e) {e.printStackTrace();}}}

说明:

  • 点击主题选择按钮后,跳转到主题选择页面,选择主题后返回,并修改当前显示主题;
  • 主题修改涉及图片和字符资源,其它资源需要添加相关的代码处理;
  • 主题的保存采用SharedPreference,简单的保存主题包的包名;

主题选择页面代码:

import java.util.List;import android.app.Activity;import android.content.Intent;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.LinearLayout;import android.widget.ScrollView;import android.widget.LinearLayout.LayoutParams;public class ThemeSelectedActivity extends Activity implements OnClickListener {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ScrollView scrollView = new ScrollView(this);LinearLayout linearLayout = new LinearLayout(this);LayoutParams linearLayoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);linearLayoutParams.setMargins(10, 10, 10, 10);linearLayout.setOrientation(LinearLayout.VERTICAL);scrollView.addView(linearLayout, linearLayoutParams);setContentView(scrollView);    PackageManager packageManager = getPackageManager();    List<PackageInfo> packageInfos = packageManager.getInstalledPackages(0);        // 添加主题选择按钮    Button button;    for (PackageInfo packageInfo : packageInfos) {        if (packageInfo.sharedUserId == null) {continue;}        // 匹配sharedUserId    if (packageInfo.sharedUserId.equals("com.anhuioss")) {    // 创建主题按钮并设置其属性    button = new Button(this);button.setTag(packageInfo.packageName);button.setText(packageInfo.packageName);button.setOnClickListener(this);// 添加到父容器linearLayout.addView(button);    }        }}@Overridepublic void onClick(View v) {String packageName = (String) v.getTag();if (packageName == null) {return;}// 设置Activity返回的数据Intent data = new Intent();data.putExtra("package_name", packageName);setResult(RESULT_OK, data);// 返回finish();}}

说明:

  • onCreate:查询当前符合条件的主题包,然后添加对应的选择按钮,并添加点击事件;
  • onClick:获得保存在View中的包名,然后设置返回数据和结果;

manifest.xml文件:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.anhuioss.skin"    android:versionCode="1"    android:versionName="1.0" android:sharedUserId="com.anhuioss">    <uses-sdk android:minSdkVersion="3" />    <application        android:icon="@drawable/ic_launcher"        android:label="@string/app_name" >        <activity            android:label="@string/app_name"            android:name=".SkinApkDemoActivity" >            <intent-filter >                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <activity android:name=".ThemeSelectedActivity"></activity>    </application></manifest>

说明:

  • android:sharedUserId="com.anhuioss"设置共享用户ID;
  • 声明用到的Activity;

布局效果:


Android 主题之安装的APK主题文件

点击更换主题后的效果:


Android 主题之安装的APK主题文件

说明:

  • 由于没有其它主题APK,所以只有一个选项;

目标应用的源码见附件,至此,目标应用示例完成,下面做两个主题APK包即可!:)

粉色和橙色主题APK制作,这个比较简单,只要满足和目标应用具有相同android:sharedUserId就可以了,不多说,直接看源码包即可,从附件中可以获得!:)

看看效果:


Android 主题之安装的APK主题文件

Android 主题之安装的APK主题文件

Android 主题之安装的APK主题文件

Android 主题之安装的APK主题文件

多说一句:在实际开发中,主题包的资源往往是目标应用的一个子集,所以避免直接使用目标程序中的id去获得资源是比较好的处理!本处从Context获得资源,当Context变化时,对应的资源也就发生变化,从而达到改变主题的目的!关于Drawable和String以外的资源获得,比如layout,animation,attribute等资源,还需要添加相关代码和进行测试!:)

更多相关文章

  1. android中图片的三级cache策略(内存、文件、网络)之三:文件缓存策略
  2. Android 4.0 Launcher2源码分析——Laucher界面元素分解(主布局文
  3. 读懂Android (1):使用Android内部的DownloadProvider下载文件,并
  4. Android学习路线(二十八)保存文件

随机推荐

  1. 11、ffmpeg学习笔记—ffmpeg源码编译-And
  2. PullToRefreshListView addHeadView的正
  3. javaAndroid实现刚刚发表几天前的日期工
  4. Android SDK可以与JDK 1.7兼容吗?
  5. android开发中调用系统中分享功能的方法
  6. 我怎样才能获得移动方向
  7. Android核心分析 之十-------Android GWE
  8. 在string.xml中调用变量[重复]
  9. Android listview+SwipeRefreshLayout 下
  10. android webview对shouldOverrideUrlLoad