編輯:關於Android編程
Android插件化的思考——仿QQ一鍵換膚。今天群友希望寫一個關於插件的Blog,思來想去,插件也不是很懂,只是用大致的思路看看能不能模擬一個,思路還是比較重要的!好的,不多說,我們進入正題:
關於QQ的換膚,他們的實現思路我不是很清楚,但是你可以看一下這張換膚的截圖

我們想使用哪個主題就直接下載就好了,這一實現的過程我們大致的可以猜想:
首選是下載到本地指定文件夾,然後通過插件加載到我們的apk,最後應用為皮膚,邏輯大致是這樣的邏輯了,那我們是不是應該動動手啊動動腦?
首選我們新建一個工程好了——PlugInSample

其實說起來,這個插件的實現思路,確實是比較的麻煩,思來想去,還是一種辦法比較靠譜,首先,我們刻意去獲取手機上所有的安裝的/未安裝的程序,過濾掉沒用的,留下我們的插件apk,我們的插件apk怎麼去辨別呢?我們可用通過設置sharedUserId,然後用實體類把插件名稱和包名保存下來,有了包名,就比較好說了,我們可用獲取插件的上下文,也就是createPackageContext,然後就可以做點壞事了,我們可以去剖析我們的R文件

因為R文件裡面都是靜態的原因,我們很容易聯想到反射機制,是的,我們可以再一次過濾掉無用的信息,通過我們的PathClassLoader去加載,訪問我們的內加載器反射到我們的圖片ID,也就是後面的那段數字,然後,嘿嘿,就可以使用了,是不是思路比較清晰了?這裡要注意的就是圖片命名統一,這樣就比較號過來,那具體我們應該怎麼做?
我們寫一個Spinner,每次切換就直接換膚怎麼樣?OK,每次換的時候就從插件APK裡加載我們的圖片資源,看起來是比較順暢的邏輯,那我們具體該怎麼做呢?
/**
* 初始化View
*/
private void initView() {
//初始化控件
mSpinner = (Spinner) findViewById(R.id.mSpinner);
}
當然,我這剛應用就一個View,但是實際開發當中可不止,所以步驟一定要明了
/**
* 獲取手機裡的插件
*
* @return
*/
private List findPlugIn() {
mList = new ArrayList<>();
//獲取相關信息
PackageManager mPackageManager = getPackageManager();
//獲取卸載/未安裝的安裝包信息
List mUninstallPackage = mPackageManager.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
//遍歷拿到我們的信息
for (PackageInfo info : mUninstallPackage) {
String pkgNmae = info.packageName;
//獲取shareId,根據id判斷是否是我們的ID
String shareUserId = info.sharedUserId;
if (!TextUtils.isEmpty(shareUserId)) {
//如果id相同
if (shareUserId.equals("com.liuguilin.share")) {
//且排除自己的包名
if (!pkgNmae.equals(getPackageName())) {
//這個就是我們的插件了
String lable = mPackageManager.getApplicationLabel(info.applicationInfo).toString();
PlugInBean bean = new PlugInBean();
bean.setLabelNmae(lable);
bean.setPackagNmae(pkgNmae);
mList.add(bean);
}
}
}
}
return mList;
}
這裡就是過濾了一下,通過sharedUserId去拿到我們的插件APK了,然後就可以拿到我們的包名和應用名,他返回給我們一個數據集
//所有的插件 ListallPlugIn = findPlugIn();
/**
* 加載皮膚
*
* @param allPlugIn
*/
private void LoadSkin(List allPlugIn) {
//遍歷
for (PlugInBean bean : allPlugIn) {
HashMap mMap = new HashMap<>();
mMap.put("lable", bean.getLabelNmae());
mMap.put("package", bean.getPackagNmae());
mData.add(mMap);
}
//建立Adapter並且綁定數據源
mAdapter = new SimpleAdapter(this, mData, android.R.layout.simple_list_item_1, new String[]{"lable"}, new int[]{android.R.id.text1});
//設置數據
mSpinner.setAdapter(mAdapter);
//設置監聽事件
mSpinner.setOnItemSelectedListener(this);
}
我們通過剛才的數據集便可以把我們拿到的數據給直接顯示出來了,這裡其實可以判斷一下size是否為0,如果為0的話也就沒有插件,OK,我們設置adapter和監聽,做到這裡,其實你可以運行一下,雖然我們現在什麼都沒有,我們要做的還有很多
/**
* 選中監聽事件
*
* @param adapterView
* @param view
* @param i
* @param l
*/
@Override
public void onItemSelected(AdapterView adapterView, View view, int i, long l) {
PlugInBean bean = mList.get(i);
//插件的包名
String packageNmae = bean.getPackagNmae();
Context mContext = null;
try {
//無視警告 訪問代碼
mContext = createPackageContext(packageNmae, CONTEXT_IGNORE_SECURITY | CONTEXT_INCLUDE_CODE);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
//獲取圖片
getImg(packageNmae, mContext);
//通過ID加載插件的圖片
getWindow().setBackgroundDrawable(mContext.getResources().getDrawable(mListId.get(i)));
}
@Override
public void onNothingSelected(AdapterView adapterView) {
}
這裡的代碼就比較有意思,一定要仔細看,我們首先拿到選中的item的包名,通過我們的createPackageContext拿到我們的上下文,通過這兩個我們可用拿到我們的資源ID,也就是R清單裡面的ID,然後直接設置window的背景,這裡為了好看才設置window的背景,實際上你要設置的是你根布局的背景,那好,我們來看一下如何通過插件的上下文和包名拿到R清單的資源ID
/**
* 獲取插件圖片 / 返回圖片R文件ID / 反射R文件
*
* @param packageNmae
* @param mContext
*/
private void getImg(String packageNmae, Context mContext) {
//類加載器反射插件
PathClassLoader pathClass = new PathClassLoader(mContext.getPackageResourcePath(), ClassLoader.getSystemClassLoader());
//反射 $ 訪問類加載器
try {
Class forNmae = Class.forName(packageNmae + ".R$drawable", true, pathClass);
//拿到所有圖片的id
Field[] files = forNmae.getDeclaredFields();
for (Field id : files) {
//過濾 / 這裡的命名可以注意一下
if (id.getName().startsWith("img")) {
int drawId = 0;
////這就是我們圖片R下的ID
drawId = id.getInt(R.drawable.class);
mListId.add(drawId);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
這裡我們做了很多事情,首選是拿到我們的類加載器去反射我們的插件,然後通過Class去拿我們的資源,這裡注意packageNmae是我們的文件目錄,他下面的R文件,$代表類部類的意思,他下面的drawable子節點,然後再一次過濾,過濾之後我們可用遍歷一遍拿到我們的ID用List保存起來,也就有了我們選中的時候的設置,好的,到這裡主程序算是編寫完成了,不過要注意的是,記住要添加sharedUserId啊,至關重要!!!
android:sharedUserId="com.liuguilin.share"
我們現在運行也是空的,無意義,我們直接來寫我們的插件吧!
插件的編寫很簡單,我們新建一個PlugInApk的工程

工程裡要做的事情就三件
1.添加sharedUserIdandroid:sharedUserId="com.liuguilin.share"
2.更改name
這就取決於你了,比如我這裡是Angelababy的主題,我就把名字改成Angelababy
3.把圖片放在drawable文件夾下
好的,做完這三部,我們本能的把插件運行一下,運行之後,我們再次啟動主程序,你會看到….

其實我們主程序裡啥也沒有,對吧,但是的卻加載進來了,這就說明我們的插件化算是圓滿實現了,那我們多來點主題看看最終的效果是什麼樣子的?

通過這個思路確實可以加載到圖片,但是這個邏輯依舊有些不完美,不過最重要的,思考比實現更重要,對吧,後續的也就是一步步的優化了,希望大家和我一起探討一下!
當上完整的代碼
package com.liuguilin.pluginsample;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.AdapterView;
import android.widget.SimpleAdapter;
import android.widget.Spinner;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import dalvik.system.PathClassLoader;
public class MainActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
//下拉
private Spinner mSpinner;
//數據源
private SimpleAdapter mAdapter;
//插件數據
private List mList;
//加載的皮膚數據
private List
這裡還有一個實體類哦,具體看Demo:
package com.liuguilin.pluginsample;
/*
* 項目名: PlugInSample
* 包名: com.liuguilin.pluginsample
* 文件名: PlugInBean
* 創建者: LGL
* 創建時間: 2016/9/17 4:18
* 描述: 插件實體類
*/
public class PlugInBean {
//包名
private String packagNmae;
//應用名
private String labelNmae;
public String getPackagNmae() {
return packagNmae;
}
public void setPackagNmae(String packagNmae) {
this.packagNmae = packagNmae;
}
public String getLabelNmae() {
return labelNmae;
}
public void setLabelNmae(String labelNmae) {
this.labelNmae = labelNmae;
}
}
.Net程序員玩轉Android開發---(1)環境搭建
對於沒有接觸過Android開發的人員來說,可能感覺Android開發比較困難,接下來的一段時間,我們將了解Android開發的具體細節,主要是面對.NET程序員,來看看
Android中屏幕密度和圖片大小的關系分析
前言 Android中支持許多資源,包括圖片(Bitmap),對應於bitmap的文件夾是drawable,除了drawable,還有drawable-ld
Android的類加載淺析
類加載流程在周志明寫的<<深入理解java虛擬機的一本書中>>已經詳細地介紹java加載類過程,在HotSpot虛擬機實現中是通過雙親委派機制來加
Android之zxing二維碼生成與識別
二維碼:是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;在代碼編制上巧妙的利用構成計算機內部邏輯基礎的0和1比特流的概念,使用