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等资源,还需要添加相关代码和进行测试!:)
相关推荐
本示例“android应用下载安装apk升级版本实现demo适配Android10”聚焦于如何在Android 10(API级别29)及更高版本中实现这一过程。以下是关于这一主题的详细知识: 1. **安全下载APK**:在Android 10中,为了保障...
在Android平台上,有时候我们需要实现一个功能,让用户能够直接通过应用下载并安装APK文件,比如更新应用程序或安装第三方应用。这个过程涉及到多个Android系统API和技术点。以下是对这个主题的详细解析: 首先,...
在标签中,".apk android android_apk apk"进一步强调了这个主题,其中".apk"是文件扩展名,"android"指的是Android操作系统,"android_apk"可能是对Android平台上APK文件的特有提及,而"apk"则是再次强调这是个APK...
在Android系统中,APK(Android Package)是应用程序的安装包,类似于其他操作系统中的.exe文件。这个"Android APK安装脚本.rar"是一个压缩包,包含了用于在Android设备上自动化安装APK的应用脚本和相关说明。以下是...
本主题将深入探讨如何在Android系统的安装目录下安装APK包,并提供一个简单的例子来帮助理解这一过程。 首先,了解Android的文件系统结构至关重要。在Android设备上,系统目录通常分为几个主要部分,例如 `/system`...
本项目是一个Android应用源码开发Demo,专为毕业设计学习而设计,可以帮助学生或开发者深入理解Android主题更换的原理和技术。 1. **Android主题系统**: Android系统支持主题更换,主要是通过主题资源文件(Theme...
在Unity引擎中调用Android设备进行APK安装并实现应用自动重启是一项常见的跨平台功能需求。这个场景通常出现在游戏或应用的更新过程中,用户在Unity游戏中点击更新按钮,然后Unity会调用Android系统API来安装新的APK...
"探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法"这个主题,揭示了腾讯如何在不需用户下载并安装完整游戏APK的情况下,实现游戏的快速启动和运行。这一技术在提高用户便利性、节省存储空间以及降低游戏分发...
"Android APK2"这个主题意味着我们将深入探讨一系列不同的Android应用,这些应用的APK安装包被收集在一起,构成了一个全面的集合。 首先,我们来看"金山词霸2.0.apk",这是一款著名的英语学习应用,提供了丰富的...
这样的主题APK包含独立的资源文件,通过动态加载来改变应用界面。这涉及到更复杂的权限管理、资源解析和插件化框架的实现。 5. **热更新和增量更新**:为了减少用户更换主题时的等待时间,可以采用热更新或增量更新...
批量安装是指一次处理多个APK文件的安装,这通常通过编写脚本或使用特定工具完成。在Android中,可以使用`pm`命令(Package Manager Service)来实现。例如,`pm install -r path_to_apk` 命令用于安装APK,`-r` ...
本文将深入探讨“Android APK反编译文件”这一主题,包括如何进行反编译,以及反编译过程中的关键知识点。 首先,让我们了解什么是APK反编译。APK反编译是将已编译的APK文件转换回其源代码形式,以便分析和理解其...
3. **使用Intent**:在APK的清单文件(AndroidManifest.xml)中,可以设置应用的主Activity具有`android.intent.category.LAUNCHER`类别,这样系统在安装后会自动尝试启动这个Activity。 ### 系统签名工具 系统签名...
根据提供的信息,“Unity中安装APK”这一主题涵盖了如何在Unity环境中通过编程方式来触发APK文件的安装流程。接下来,我们将详细探讨这一过程中的关键技术点。 ### Unity环境下的APK安装原理 在Unity中安装APK主要...
- **AndroidManifest.xml**:主题APK的AndroidManifest文件中会包含必要的权限声明,例如读写存储权限,以便于主题文件的安装和应用。 - **资源替换**:主题APK通常会包含一套新的资源文件,如drawable、layout、...
总结,Android通过apk皮肤方式进行主题切换,不仅提供了丰富的个性化选项,也对开发者提出了挑战,要求他们具备良好的资源管理和代码组织能力。正确实现这一功能,不仅可以提升用户体验,也是展示技术实力的一个窗口...
在本主题中,我们将深入探讨“android 下载管理器 apk”,这是一个可能包含自定义实现的下载管理工具,或者是一个用于分析和理解Android系统内置下载管理服务的示例。根据提供的信息,我们无法直接获取到该APK的详细...
2. **运行APK**:通常,Android系统通过Android Package Manager服务来安装和运行APK文件。然而,如果我们想要在运行时加载并执行另一个APK中的代码,这需要更深入的编程技巧,比如使用 DexClassLoader 和 DexFile ...
为了实现开机安装T卡上的Apk,开发者需要对Android系统的启动流程有深入理解,并且要熟悉与文件系统、权限管理、以及安装过程相关的代码。 预装Apk是在设备出厂前就预先安装好的应用程序,它们通常是制造商或运营商...