接着前面的使用“插件”模式定制皮肤(一)来讲,我们可以顺着这个思路来做一个定制皮肤功能的apk。
Android中的theme概念并不是真正的系统主题,我认为的系统主题的定义是,系统图标可以通过一种主题文件进行切换。设计思路是把每一种主题文件作为一个插件,也就是所谓的apk文件,我们可以在这个apk文件中定义我们想要的风格样式,然后通过我们的软件来引用该apk文件中资源文件来达到切换主题的目的。
接下来我们要做的是自定义两种皮肤,也就是两个apk,SkinCustom1和SkinCustom2,我们的主apk是SkinMain。下面我们开始写代码,首先新建工程SkinCustom1,这里我们引入plugin.jar包,这个jar包其实是在SkinCustom中定义的,这里作为library导入进来,上一节中已经介绍过了如何导入,新建类SelectSkin,如下:
package com.ldw.skincustom1; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.graphics.drawable.Drawable; import com.ldw.skinmain.IPlugin; public class SelectSkin implements IPlugin { private Context mContext; @Override public String getString(String packageName) { if(mContext == null) { throw new IllegalArgumentException("you should invoke setContext first!"); } try { PackageManager mPm = mContext.getPackageManager(); Resources res = mPm.getResourcesForApplication(packageName); int id = res.getIdentifier("app_name", "string", packageName); return res.getString(id); } catch (NameNotFoundException e) { e.printStackTrace(); } return ""; } @Override public Drawable getDrawable(String name, String packageName) { if(mContext == null) { throw new IllegalArgumentException("you should invoke setContext first!"); } try { PackageManager mPm = mContext.getPackageManager(); Resources res = mPm.getResourcesForApplication(packageName); int id = res.getIdentifier(name, "drawable", packageName); return res.getDrawable(id); } catch (NameNotFoundException e) { e.printStackTrace(); } return null; } @Override public void setContext(Context context) { this.mContext = context; } }
类中具体的方法作用我就不解释了,实现了接口中的三个方法,setContext,getDrawable,getString。再看Manifest文件,如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.ldw.skincustom1" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="17" /> <application android:icon="@drawable/icon" android:label="@string/app_name" > <activity android:name="com.ldw.skincustom1.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="com.ldw.skin"/> </intent-filter> </activity> </application> </manifest>
注意我们的MainActivity并没有做任何事情,只是加了一个action而已,这样的话我们的主apk中就可以通过PackageManager类的queryIntentActivity()方法查询相关的插件程序列表了,同样的我们新建工程SkinCustom2,该工程和SkinCustom1差不多,只是资源文件更改了,等会附件中会有具体的工程。
接着我们来看我们的主工程SkinMain:
IPlugin.java接口:
package com.ldw.skinmain; import android.content.Context; import android.graphics.drawable.Drawable; public interface IPlugin { public void setContext(Context context); public String getString(String packageName); public Drawable getDrawable(String name, String packageName); }
MainActivity类:
package com.ldw.skinmain; import java.lang.reflect.Method; public class MainActivity extends Activity implements OnItemClickListener { private List<ResolveInfo> mRlist; private ListView mListView; private PackageManager mPm; private RelativeLayout mMainLayout; private Button mButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); searchSkin(); initView(); initListView(); } private void searchSkin() { Intent intent = new Intent("com.ldw.skin", null); mPm = getPackageManager(); mRlist = mPm.queryIntentActivities(intent, 0); } private void initView() { mMainLayout = (RelativeLayout) findViewById(R.id.mainlayout); mButton = (Button) findViewById(R.id.button); } private void initListView() { mListView = (ListView) findViewById(R.id.listview); mListView.setAdapter(new MyAdapter()); mListView.setOnItemClickListener(this); } private class MyAdapter extends BaseAdapter { @Override public int getCount() { return mRlist.size(); } @Override public ResolveInfo getItem(int position) { return mRlist.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; ResolveInfo info = getItem(position); if (convertView == null) { convertView = getLayoutInflater().inflate(R.layout.item, null); holder = new ViewHolder(); holder.iv = (ImageView) convertView.findViewById(R.id.item_iv); holder.tv = (TextView) convertView.findViewById(R.id.item_tv); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } IPlugin plugin = getPlugin(info); holder.iv .setBackgroundDrawable(plugin.getDrawable("icon", info.activityInfo.packageName)); holder.tv.setText(plugin.getString(info.activityInfo.packageName)); return convertView; } class ViewHolder { TextView tv; ImageView iv; } } @SuppressLint("NewApi") private IPlugin getPlugin(ResolveInfo rinfo) { ActivityInfo ainfo = rinfo.activityInfo; String packageName = ainfo.packageName; // 目标类所在apk或jar包文件的路径 String dexPath = ainfo.applicationInfo.sourceDir; // 由于dex文件存在apk中,因此在装载目标类之前需要先从apk中解压出dex文件,这个参数就是解压后存放的路径 String dexOutputDir = getApplicationInfo().dataDir; // 指目标类中所使用的C/C++库存放的路径 String libPath = ainfo.applicationInfo.nativeLibraryDir; DexClassLoader cl = new DexClassLoader(dexPath, dexOutputDir, libPath, this.getClass().getClassLoader()); try { Class<?> clazz = cl.loadClass(packageName + ".SelectSkin"); // 获取真正的PluginClass对象 IPlugin plugin = (IPlugin) clazz.newInstance(); plugin.setContext(this); return plugin; } catch (Exception e) { Log.i("Host", "error", e); } return null; } @SuppressWarnings("deprecation") @Override public void onItemClick(AdapterView<?> parent, View arg1, int position, long arg3) { ResolveInfo info = (ResolveInfo) parent.getItemAtPosition(position); IPlugin plugin = getPlugin(info); mMainLayout.setBackgroundDrawable(plugin.getDrawable("bg", info.activityInfo.packageName)); mButton.setBackgroundDrawable(plugin.getDrawable("button_selector", info.activityInfo.packageName)); } }
没什么好讲的,大家好好看看代码应该能看懂的。下面是工程代码。
点击下载工程。
写得不错
嘿嘿。。。
这个名字太难听了,病人!!!!!
写的太好了,[good],我得好好学习下