欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-08 13:31:21
...

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;

布局效果:


Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
 

点击更换主题后的效果:


Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
 

说明:

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

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

 

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

 

看看效果:


Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
 
Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
 
Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
 
Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 

 

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

 

 

 

 

  • Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
  • 大小: 24.5 KB
  • Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
  • 大小: 16 KB
  • Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
  • 大小: 27.3 KB
  • Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
  • 大小: 19.1 KB
  • Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
  • 大小: 26.9 KB
  • Android 主题之安装的APK主题文件
            
    
    博客分类: 主题Android Android主题APK主题文件 
  • 大小: 22.9 KB