APK外掛載入資源實現
前言
外掛化程式設計現在非常的火熱,通常用來解決65536問題,外掛通常被做成不同的apk模組,每個模組專門負責某種業務邏輯,主APK通過呼叫動態載入外掛裡的程式碼和資源實現宿主和外掛的互動。為了瞭解外掛APK檔案如何使用,這裡通過讀取APK外掛的資源來實現換膚功能。
實現效果
生成資源外掛
只要在Android Studio中建立一個新的專案,專案裡不需要有任何的Activity元件,再把需要的圖片資源和顏色資源都定義一下,最後在命令列執行gradle assemble會發現在build/outputs/apk裡找到了app-debug.apk檔案,它就是包含資源的apk外掛包。可以通過adb push方法將這個外掛包推到Android手機的/sdcard/目錄下,方便主應用APK能夠找到外掛檔案。
// 在外掛資源包裡定義的顏色資源
<color name="content_text_color">#00ff00</color>
<color name="menu_text_color">#ff00ff</color>
C:\Users>adb push appres.apk /sdcard/
appres.apk: 1 file pushed. 4.1 MB/s (1720401 bytes in 0.399s)
換膚實現
前面已經將外掛資源放到了/sdcard/目錄下,為了能夠讀取到外部檔案對於Android6.0之後的系統需要申請外部儲存讀取許可權,關於動態申請許可權的程式碼邏輯這裡就不再贅述,現在開始定義簡單的介面佈局。佈局分為SlideMenu部分和內容部分,兩者都有一個背景ImageView和一個展示文案的TextView,這裡需要對背景圖和文案的顏色做修改,預設情況下使用的是主APK中的資源,如果使用者要求切換面板就會從外掛APK中讀取資源並且設定到背景和文案上。
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context =".DrawerActivity">
<FrameLayout
android:id="@+id/main_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/content"
android:src="@drawable/skin_content_bg"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/content_text"
android:textColor="@color/skin_red"
android:layout_gravity="center"
android:textSize="50sp"
android:text="@string/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
<FrameLayout
android:id="@+id/slideMenu"
android:layout_gravity="left"
android:layout_width="250dp"
android:layout_height="match_parent">
<ImageView
android:id="@+id/menu"
android:src="@drawable/skin_menu_bg"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/menu_text"
android:textColor="@color/skin_orange"
android:layout_gravity="center"
android:textSize="50sp"
android:text="@string/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</FrameLayout>
</android.support.v4.widget.DrawerLayout>
使用者可以通過選項選單來切換不同的面板,其中ResourceManager物件儲存了預設的資源和外掛APK中的資源,使用者點選修改會使用外掛中的資源,點選還原會自動使用原始的資源。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.action_change_skin:
ResourceManager.ResourceEntity entity = ResourceManager.getInstance()
.getResource(ResourceManager.APK_RESOURCE);
initViews(entity);
return true;
case R.id.action_reset_skin:
ResourceManager.ResourceEntity defEntity = ResourceManager.getInstance()
.getResource(ResourceManager.DEFAULT_RESOURCE);
initViews(defEntity);
return true;
}
return super.onOptionsItemSelected(item);
}
// 將資源設定到背景和文案檢視物件上
private void initViews(ResourceManager.ResourceEntity entity) {
menuBg.setImageDrawable(entity.menuBg);
contentBg.setImageDrawable(entity.contentBg);
menuText.setTextColor(entity.menuColor);
contentText.setTextColor(entity.contentColor);
}
其中的ResourceEntity就是用來儲存資源的資料類,這裡主要來看ResourceManager.getInstance().getResource(resourceType)根據使用者指定的資源型別載入資源物件的實現。
public class ResourceManager {
// 資源型別
public static final String DEFAULT_RESOURCE = "default_resource";
public static final String APK_RESOURCE = "apk_resource";
// 資源物件快取
private Map<String, ResourceEntity> mCacheMap = new HashMap<>();
private ResourceManager() {
// 初始化預設的資源,使用的是主APK內定義的資源
ResourceEntity defaultEntity = new ResourceEntity();
Resources resources = Application.getContext().getResources();
defaultEntity.contentBg = resources.getDrawable(R.drawable.skin_content_bg);
defaultEntity.menuBg = resources.getDrawable(R.drawable.skin_menu_bg);
defaultEntity.contentColor = resources.getColor(R.color.skin_red);
defaultEntity.menuColor = resources.getColor(R.color.skin_orange);
mCacheMap.put(DEFAULT_RESOURCE, defaultEntity);
}
private static class ResourceManagerHolder {
private static final ResourceManager INSTANCE = new ResourceManager();
}
public static ResourceManager getInstance() {
return ResourceManagerHolder.INSTANCE;
}
public void add(String key, ResourceEntity entity) {
mCacheMap.put(key, entity);
}
public ResourceEntity getResource(String key) {
ResourceEntity entity = mCacheMap.get(key);
// 如果沒有對應的資源物件
if (entity == null) {
// 如果請求的是外掛資源型別
if (APK_RESOURCE.equals(key)) {
// 載入外掛資源型別
entity = loadResources();
mCacheMap.put(key, entity);
return entity;
} else {
return mCacheMap.get(DEFAULT_RESOURCE);
}
} else {
return entity;
}
}
/**
* 從外掛APK中獲取資源
*/
private ResourceEntity loadResources() {
try {
// 反射生成AssetManager物件
AssetManager assetManager = AssetManager.class.newInstance();
// 獲取addAssetPath方法
Method method = AssetManager.class.getMethod("addAssetPath", String.class);
// 獲取外掛的路徑
String path = Environment.getExternalStorageDirectory() + File.separator + "appres.apk";
// 將外掛的路徑新增到AssetManager裡
method.invoke(assetManager, path);
Resources originResources = Application.getContext().getResources();
// 外掛APK的packageName名稱
String pkgName = "com.example.apkresource";
// 生成外掛APK對應的Resources物件
Resources resources = new Resources(assetManager,
originResources.getDisplayMetrics(), originResources.getConfiguration());
// 獲取外掛中的背景圖資源id
int contentBgId = resources.getIdentifier("content_bg", "drawable", pkgName);
int menuBgId = resources.getIdentifier("menu_bg", "drawable", pkgName);
// 獲取外掛中文案顏色資源id
int contentColorId = resources.getIdentifier("content_text_color", "color", pkgName);
int menuColorId = resources.getIdentifier("menu_text_color", "color", pkgName);
// 從Resources物件中獲取外掛的資源資料並儲存到ResouceEntity中
ResourceEntity entity = new ResourceEntity();
entity.menuBg = resources.getDrawable(menuBgId);
entity.contentBg = resources.getDrawable(contentBgId);
entity.menuColor = resources.getColor(menuColorId);
entity.contentColor = resources.getColor(contentColorId);
return entity;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
public static class ResourceEntity {
public Drawable contentBg;
public Drawable menuBg;
public int contentColor;
public int menuColor;
}
}
上面的ResourceManager物件使用了單例模式,內部會儲存外掛資源和預設資源,在外掛資源未被初始化的情況下使用AssetManager和Resources兩個工具類獲取外掛內的資源資料,最後通過前面定義的initViews方法將外掛資源設定到背景和文案檢視上實現換膚功能。
相關推薦
APK外掛載入資源實現
前言 外掛化程式設計現在非常的火熱,通常用來解決65536問題,外掛通常被做成不同的apk模組,每個模組專門負責某種業務邏輯,主APK通過呼叫動態載入外掛裡的程式碼和資源實現宿主和外掛的互動。為了瞭解外掛APK檔案如何使用,這裡通過讀取APK外掛的資源來實現換
Android外掛化完美實現程式碼資源載入及原理講解 附可執行demo
*本篇文章已授權微信公眾號 guolin_blog (郭霖)獨家釋出 。 我們通過前4篇的分解,分別將外掛化設計到的知識點全部梳理了一遍,如果沒有看過的,建議先看前面4篇 6. 外掛化資源的使用及動態載入 附demo 好了上面介紹了之
Android如何載入外掛APK裡面的資源
前言:ClassLoader能夠動態載入類,Android裡面的DexClassLoader和PathClassLoader繼承自ClassLoader。DexClassLoader能夠載入未安裝的jar/apk/dex檔案。PathClassLoader只能載
Android中apk動態載入技術研究(2)android插件化及實現
name creat package path iss fontsize 調用 dex con 了解了android中類載入的前期知識點後,來看看android中DexClassLoader詳細的實現 詳細載入流程例如以下: 宿主程序會到文件系統比
Android apk動態載入機制的研究(二) 資源載入和activity生命週期管理
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
外掛載入帶有動態庫so的apk處理
外掛載入帶有動態庫的apk時,會報UnsatisfiedLinkError找不到動態庫的錯誤。 解決方法是在DexClassLoader中 dalvik.system.DexClassLoader.DexClassLoader(String dexPath, S
Android外掛化資源的使用及動態載入 附demo
上一篇我們已經完成了一個真正可執行的外掛化demo,而且demo中也解決了外掛中不可以使用資源的問題,但是由於篇幅的問題我們並沒有對原理講解,所以這一篇是對上一篇的一個收尾,如果沒有看過上一篇建議先看Android外掛化完美實現程式碼資源載入及原理講解 附可執行
Android 系統上實現APK外掛機制——360手機助手
Droid Plugin DroidPlugin 是360手機助手在Android系統上實現了一種新的外掛機制:它可以在無需安裝、修改的情況下執行APK檔案,此機制對改進大型APP的架構,實
Gradle外掛開發 APK瘦身資源自定義7z壓縮
APK瘦身實戰 資源自定義7z壓縮 專案開發中,隨著業務的增長,常常需要在apk編譯階段對包程式碼或是資源做一定的自定義修改,比如熱修復,外掛生成,無埋點統計,渠道包生成等等。 但是公司專案業務開發人員基本上都很少接觸到相關技術,這裡以學習的態度,實現一套用
外掛化 載入資源的核心程式碼
外掛化 載入其他apk 的資源的核心思想是 拿到其他apk 的Resources 物件,然後通過反射 , 可以拿到 我們想要的資源,然後使用。 拿到其他apk 的物件 ,主要是通過 AssetManager 的 addAssetPath,方法; 因為這個方法在frameW
通過Android manifest中的sharedUserId屬性的設定來實現apk之間的資源共享
在Android應用的開發中應該都會有兩個程序中需要互相訪問資料的需求,而在APK在安裝到裝置裡的時候該應用的userid就已經確定了, 一般情況下都不會再改變,每個應用的程序在預設情況下的userid是不一樣的,如adb shell下top命令顯示的程序資訊中的UID,
Android動態載入APK外掛類
前言 外掛化開發目前是非常熱門的Android技術,它主要通過將不同的業務物件封裝到外掛中,這樣不同的業務可以獨立開發和除錯,提高專案的開發效率。APK檔案就是常見的外掛檔案格式,它包含了Android應用常見的資源和程式碼,不過由於外掛沒有被安裝到系統中還需
Android實現apk外掛方式換膚
換膚思路: 1.什麼時候換膚? xml載入前換膚,如果xml載入後換膚,使用者將會看見換膚之前的色彩,使用者體驗不好。 2.面板是什麼? 面板就是apk,是一個資源包,包含了顏色、圖片等。 3.什麼樣的控制元件應該進行換膚? 包含背景圖片的控制元件,例如textView文字顏色。 4.面板與已安裝的資源如何匹
逆向知識之CS輔助/外掛專題.2.實現CS1.6無限夜視儀.無限閃光煙霧高爆彈.
寫代碼 循環 編寫 編寫代碼 我們 思路 是個 代碼 打開 逆向知識之CS輔助/外掛專題.2.實現CS1.6無限夜視儀.無限閃光煙霧高爆彈. 關於人物子彈無限可以觀看上一篇博客. 一丶無限夜視儀. 無限夜視儀找法. 1.CE附加遊戲. 2.搜索0或者
Vue 進階系列(二)之外掛原理及實現
Vue進階系列彙總如下,歡迎閱讀,歡迎加群討論(文末)。 Vue 進階系列(一)之響應式原理及實現 Vue 進階系列(二)之外掛原理及實現 使用方法 外掛的詳細使用方法詳情看Vue官網 Vue官網之外掛Plugins 概括出來就是 1、通過Vue.use(MyPlugin)使用,
如何使用postman及外掛postman interceptor 實現模擬帶站點cookie的請求呼叫
前提:瀏覽器chrome及chrome外掛安裝 背景:前後端分離後,除錯後端介面需要模擬前端發起的請求,構建請求引數較為費事,不如將瀏覽器cookie及請求引數一起帶出通過postman 模擬前端請求。 1、安裝chrome外掛postman interceptor及谷歌應用postman
Egret製作Loading頁面及分步載入資源教程
我們都知道,當遊戲越做越大,資源越來越多的時候,載入資源會造成大量時間的浪費。為避免載入資源時遊戲黑屏,導致玩家誤認為遊戲非正常執行,Loading介面起到至關重要的作用。今天就為大家帶來用Egret製作Loading頁面及分步載入資源的教程。 本文涉及以下內容: · RES載入Loading介面所使用的
egret 全屏, 和載入資源, 以及回撥函式
1, 有時候在手機瀏覽器中因為有 虛擬按鍵以及標題欄, 使得即便設定了全屏也沒有辦法變成全屏, 但是好像JS 中有方法向瀏覽器請求全屏 2, 載入資源, 關閉後解除安裝, 第二次再進來的時候依然很快, 這是因為瀏覽器有快取 3, egret的回撥函式十分的隨便, 帶引數的回撥函式
菜鳥教程丨Egret製作Loading頁面及分步載入資源教程
我們都知道,當遊戲越做越大,資源越來越多的時候,載入資源會造成大量時間的浪費。為避免載入資源時遊戲黑屏,導致玩家誤認為遊戲非正常執行,Loading介面起到至關重要的作用。今天就為大家帶來用Egret製作Loading頁面及分步載入資源的教程。 本文涉及以下內容: RES載入Loadi
設定Sublime外掛快捷鍵--實現CSS顏色選取
安裝外掛ColorPicker 如果你經常要檢視或設定顏色值,這個外掛可以很方便地呼叫你本機的調色盤應用。(譯者擴充:)這是一個雙向的功能,你既可以在調色盤中選擇一個顏色,然後按“確定”按鈕把該值填寫到 SublimeText 中活動文件的當前位置,也可以在活動文件中選擇一個顏色的值,按此外掛的