`
wangleyiang
  • 浏览: 215232 次
社区版块
存档分类
最新评论

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

阅读更多

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;

	@Override
	public 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()));

	}

	@Override
	public 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;
		}

	}

	@Override
	protected 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 {
	
	@Override
	protected 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);
	    	}
	    	
	    }
		
	}
	
	@Override
	public 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;

布局效果:



 

点击更换主题后的效果:



 

说明:

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

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

 

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

 

看看效果:



 

 

 

 

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

 

 

 

 

  • 大小: 24.5 KB
  • 大小: 16 KB
  • 大小: 27.3 KB
  • 大小: 19.1 KB
  • 大小: 26.9 KB
  • 大小: 22.9 KB
分享到:
评论

相关推荐

    Android 主题切换,切换字体颜色颜色,背景色,图片引用文件等

    Android 主题切换,切换字体颜色颜色,背景色,图片引用文件等。

    Android 主题动态切换

    设置应用主题的动态切换,将解压的三个工程导入eclipse中,然后将theme和theme2打包成apk文件,拷到sd卡根目录下,否则点击主应用中的按钮无效。

    apktoolmv2.4.0_downcc.com.apk

    -将Android App Bundle(拆分)合并到一个安装文件中,以及安装和解压缩此类文件。 创建自己的签名和签名应用程序。 快速编辑应用程序名称,程序包名称(克隆应用程序),应用程序图标等,而无需重建。 不需要根...

    开发工具:eclipse 黑色主题 + Android反编译工具包(升级)+ Postman

    Android反编译工具包是用于将已编译的Android应用程序(APK文件)转换回源代码的工具。升级后的工具包通常包含最新的反编译技术和功能,以应对新版本的Android系统和安全机制。 其中,dex2jar是一个常用的工具,...

    apk工具ApkTool

    用于将apk文件反编译,可以修改资源布局等文件,如果对smali格式熟悉的话,也可修改代码。修改后可回编译成apk文件

    Android 雪豹速清 v2.4.2 安卓13文件管理及清理工具

    雪豹速清app,目前评分很高的一款安卓垃圾清理工具,特色功能自动扫全盘,扫描速度超快,智能文件分类,一建清理垃圾,支持安卓11/Android/data目录访问、文件复制、缓存垃圾扫描、文件管理等操作。 新版变化 2022....

    ZArchiver Donate 0.9.2.apk

    单击所选文件之一,然后从菜单中选择“压缩”。设置所需选项,然后按OK按钮。 问:如何提取文件? 答:单击存档名称并选择合适的选项(“在此提取”或其他)。 什么是新的 0.9.0 - 扩展Android 7/8支持; - 7zip...

    Android插件框架Android-Plugin-Framework.zip

    “ant clean debug install” 直接将插件apk安装到系统中,PluginMain工程会监听系统的应用安装广播,监听到插件apk安装广播后, 再自动调用PluginLoader.installPlugin("/data/app/插件apk文件.apk")进行插件安装。...

    乐秀录屏大师安卓apk文件

    操作简单没有繁琐的录屏步骤,就算手机没有root权限也可以轻松进行超清视频录制、屏幕截图、同步录音、视频剪辑、视频编辑、视频制作、特效主题添加,一键分享等功能。

    Android开发应用实战详解源代码

    2.3 android应用项目文件组成 2.3.1 androidmanifest.xml文件 2.3.2 src目录 2.3.3 常量的定义文件 2.4 程序生命周期 2.4.1 android周期 2.4.2 android进程 2.5 activity的生命周期 2.5.1 activity状态 2.5.2 剖析...

    AnySeeker文件快速搜索工具android版(Beta版)

    支持apk资源文件提取功能 支持zip压缩和解压缩功能 支持文件夹信息查看,包括总大小,子文件数量和总数量 支持自定义时间格式和大小格式 内置图片查看器和文本查看器 支持文件多选和处理文件功能,包括复制移动删除...

    Android编程入门很简单.(清华出版.王勇).part1

    2.4安装并配置Android SDK 2.4.1 下载Android SDK 2.4.2配置SDK 2.5下载ADT 2.5.1 下载ADT 2.5.2 为Eclipse设置SDK路径 2.6新建模拟器 2.6.1 新建AVD 2.6.2运行模拟器 2.7真机测试 2.7.1安装手机驱动 2.7.2设置手机 ...

    适用于Android的Material Design文件管理器-Android开发

    适用于Android Material Files的Material Design文件管理器本文中文版适用于Android 5.0及更高版本的开源Material Design文件管理器。 在Coolapk上获取它。在Transifex预览功能上获取APK帮助翻译。开源:轻巧,干净...

    最新apk更新

    支持post和get两种版本检查...支持自定义版本更新检查器、版本更新解析器、版本更新提示器、版本更新下载器、版本更新安装、出错处理。 支持MD5文件校验、版本忽略、版本强制更新等功能。 支持自定义请求API接口。

    最通俗_最实用的Android开发高级培训教程

    最通俗_最实用的Android开发高级培训教程 1.什么是3G 2.什么是Android 3.智能手机软件平台 4.如何安装Android SDK和Eclipse插件 5.开发第一个Android应用 6.Android应用程序框架 ...19.如何反编译APK文件

    Android 解压缩工具 ZArchiver Pro 0.9.5.8 中文多语免费版.zip

    如果您定期使用 OBB 文件安装 Android 游戏,则 Zarchiver Pro 绝对是必不可少的应用程序。 Android 解压缩工具 ZArchiver Pro 中文版Android 解压缩工具 ZArchiver Pro 中文版 ZArchiver Pro 的优点: – 白色与...

    Android编程入门很简单.(清华出版.王勇).part2

    2.4安装并配置Android SDK 2.4.1 下载Android SDK 2.4.2配置SDK 2.5下载ADT 2.5.1 下载ADT 2.5.2 为Eclipse设置SDK路径 2.6新建模拟器 2.6.1 新建AVD 2.6.2运行模拟器 2.7真机测试 2.7.1安装手机驱动 2.7.2设置手机 ...

    AppMgr_Pro_III_v4.76.apk

    AppMgr Pro III(App 2 SD)是一种工具,可让您管理设备上已安装的所有应用程序,包括在设备的内存和SD卡之间来回移动APK文件。这是AppMgr Pro III(App 2 SD)的主要且可能是最有趣的功能。它允许您通过将文件存储...

    QuickEdit Pro 1.4.3.apk

    QuickEdit是一款适用于Android设备的快速,稳定且功能齐全的文本编辑器。它已针对手机和平板电脑进行了优化! 特征: ✓增强的记事本应用程序,有许多改进。 ✓代码编辑器和语法突出显示50多种语言(C ++,C#,...

Global site tag (gtag.js) - Google Analytics