編輯:關於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 ListfindPlugIn() { 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(ListallPlugIn) { //遍歷 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 ListmList; //加載的皮膚數據 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; } }
對於沒有接觸過Android開發的人員來說,可能感覺Android開發比較困難,接下來的一段時間,我們將了解Android開發的具體細節,主要是面對.NET程序員,來看看
前言 Android中支持許多資源,包括圖片(Bitmap),對應於bitmap的文件夾是drawable,除了drawable,還有drawable-ld
類加載流程在周志明寫的<<深入理解java虛擬機的一本書中>>已經詳細地介紹java加載類過程,在HotSpot虛擬機實現中是通過雙親委派機制來加
二維碼:是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;在代碼編制上巧妙的利用構成計算機內部邏輯基礎的0和1比特流的概念,使用