Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android插件化的思考——仿QQ一鍵換膚,思考比實現更重要!

Android插件化的思考——仿QQ一鍵換膚,思考比實現更重要!

編輯:關於Android編程

今天群友希望寫一個關於插件的Blog,思來想去,插件也不是很懂,只是用大致的思路看看能不能模擬一個,思路還是比較重要的,如果你有興趣的話,也可以加群:555974449,你也可以說出你想看的Blog哦,嘿嘿!好的,不多說,我們進入正題:

關於QQ的換膚,他們的實現思路我不是很清楚,但是你可以看一下這張換膚的截圖

這裡寫圖片描述vc7Sw8e1xGFwa6Os1+6689Om08POqsakt/SjrMLfvK2089bCysfV4tH5tcTC37ytwcujrMTHztLDx8rHsrvKx9OmuMO2r7avytawobavtq/E1KO/PC9wPg0KPHA+ytfRoc7Sw8fQwr2o0ru49rmks8y6w8HLJm1kYXNoOyZtZGFzaDtQbHVnSW5TYW1wbGU8L3A+DQo8cD48aW1nIGFsdD0="這裡寫圖片描述" src="/uploadfile/Collfiles/20160918/2016091809204351.png" title="\" />

一.實現思路

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

這裡寫圖片描述

因為R文件裡面都是靜態的原因,我們很容易聯想到反射機制,是的,我們可以再一次過濾掉無用的信息,通過我們的PathClassLoader去加載,訪問我們的內加載器反射到我們的圖片ID,也就是後面的那段數字,然後,嘿嘿,就可以使用了,是不是思路比較清晰了?這裡要注意的就是圖片命名統一,這樣就比較號過來,那具體我們應該怎麼做?

二.PlugIn主程序

我們寫一個Spinner,每次切換就直接換膚怎麼樣?OK,每次換的時候就從插件APK裡加載我們的圖片資源,看起來是比較順暢的邏輯,那我們具體該怎麼做呢?




    


1.初始化

    /**
     * 初始化View
     */
    private void initView() {
        //初始化控件
        mSpinner = (Spinner) findViewById(R.id.mSpinner);
    }

當然,我這剛應用就一個View,但是實際開發當中可不止,所以步驟一定要明了

2.獲取所有的插件

    /**
     * 獲取手機裡的插件
     *
     * @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了,然後就可以拿到我們的包名和應用名,他返回給我們一個數據集

//所有的插件
 List allPlugIn = findPlugIn();

3.加載皮膚數據

    /**
     * 加載皮膚
     *
     * @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和監聽,做到這裡,其實你可以運行一下,雖然我們現在什麼都沒有,我們要做的還有很多

4.獲取插件Context

    /**
     * 選中監聽事件
     *
     * @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

5.獲取插件圖片 / 返回圖片R文件ID / 反射R文件

    /**
     * 獲取插件圖片 / 返回圖片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插件

插件的編寫很簡單,我們新建一個PlugInApk的工程

這裡寫圖片描述

工程裡要做的事情就三件

1.添加sharedUserId

android:sharedUserId="com.liuguilin.share"

2.更改name

這就取決於你了,比如我這裡是Angelababy的主題,我就把名字改成Angelababy

3.把圖片放在drawable文件夾下

好的,做完這三部,我們本能的把插件運行一下,運行之後,我們再次啟動主程序,你會看到….

這裡寫圖片描述

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

這裡寫圖片描述

通過這個思路確實可以加載到圖片,但是這個邏輯依舊有些不完美,不過最重要的,思考比實現更重要,對吧,後續的也就是一步步的優化了,希望大家和我一起探討一下!

當上完整的代碼

MainActivity

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> mData = new ArrayList<>();
    //資源id
    private int drawId = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        //所有的插件
        List allPlugIn = findPlugIn();
        //加載皮膚數據
        LoadSkin(allPlugIn);
    }

    /**
     * 加載皮膚
     *
     * @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);

    }

    /**
     * 獲取手機裡的插件
     *
     * @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;
    }

    /**
     * 初始化View
     */
    private void initView() {
        //初始化控件
        mSpinner = (Spinner) findViewById(R.id.mSpinner);

    }

    /**
     * 選中監聽事件
     *
     * @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(drawId));
        //findViewById(R.id.mLinearLayout).setBackgroundDrawable(mContext.getResources().getDrawable(drawId));
    }

    @Override
    public void onNothingSelected(AdapterView adapterView) {

    }


    /**
     * 獲取插件圖片 / 返回圖片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")) {
                    ////這就是我們圖片R下的ID
                    drawId = id.getInt(R.drawable.class);
                    //mListId.add(drawId);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

這裡還有一個實體類哦,具體看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;
    }

}
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved