android service外掛化之二
3. Service的外掛化
現在已經明白了Service元件的工作原理,可對如何實現Service的外掛化依然是一頭霧水。
從上文的原始碼分析來看,Service元件與Activity有著非常多的相似之處:它們都是通過Context類完成啟動,
接著通過 ActivityMnagaerNative進入AMS,最後又通過IApplicationThread這個Binder IPC到App程序的Binder執行緒池,
然後通過H轉發訊息到App程序的主執行緒,最終完成元件生命週期的回撥;
對於Service元件,看起來好像可 以沿用Activity元件的外掛化方式:Hook掉ActivityManagerNative以及H類,但事實真的如此嗎?
3.1 Service與Activity的異同
Service元件和Activity元件有什麼不同?這些不同使得對於外掛化方案的選擇又有什麼影響?
3.1.1 使用者互動對於生命週期的影響
首先,Activity與Service元件最大的不同點在 於,Activity元件可以與使用者進行互動
;這一點意味著使用者的行為會對Activity元件產生影響,對來說最重要的影響就是Activity元件的生命週期;
使用者點選按鈕從介面A跳轉到介面B,會引起A和B這兩個Activity一系列生命週期的變化。
而Service元件則代表後臺任務,除了記憶體不足系統回收之外,它的生命週期完全由的程式碼控制,與使用者的互動無關。
這意味著什麼?
Activity元件的生命週期受使用者互動影響,而這種變化只有Android系統才能感知,因此必須把外掛的Activity交給系統管理,
才能擁有完整的生命週期;但Service元件的生命週期不受外界因素影響,那麼自然而然,可以手動控制它的生命週期,
就像對於BroadcastReceiver的外掛化方式一樣!Activity元件的外掛化無疑是比較複雜的,
為了把外掛Activity交給系統 管理進而擁有完整生命週期,設計了一個天衣無縫的方案騙過了AMS;
既然Service的生命週期可以由自己控制,那麼可以有更簡單的方案實現它的外掛化。
3.1.2 Activity的任務棧
上文指出了Activity和Service元件在處理使用者互動方面的不同,這使得對於Service組建的外掛化可以選擇一種較為簡單的方式;
也許你會問,那採用Activity外掛化的那一套技術能夠實現Service元件的外掛化嗎?
很遺憾,答案是不行的。雖然Activity的外掛化技術更復雜,但是這種方案並不能完成Service元件的外掛化——複雜的方案並不意味了它能處理更多的問題。
原因在於Activity擁有任務棧的概念。或許你覺得任務棧並不是什麼了不起的東西,但是,這確實是Service元件與Activity元件外掛化方式分道揚鑣的根本原因。
任務棧的概念使得Activtiy的建立就代表著入棧,銷燬則代表出棧;又由於Activity代表著與使用者互動的介面,
所以這個棧的深度不可能太 深——Activity棧太深意味著使用者需要狂點back鍵才能回到初始介面,這種體驗顯然有問題;
因此,外掛框架要處理的Activity數量其實是有 限的,所以在AndroidManifest.xml中宣告有限個StubActivity就能滿足外掛啟動近乎無限個外掛Activity的需求。
但是Service元件不一樣,理論情況下,可以啟動的Service元件是無限的——除了硬體以及記憶體資源,
沒有什麼限制它的數目;如果採用 Activity的外掛化方式,就算在AndroidMafenist.xml中宣告再多的StubService,
總有不能滿足外掛中要啟動的 Service數目的情況出現。也許有童鞋會說,可以用一個StubService對應多個外掛Service,
這確實能解決部分問題;但是,下面的這個區別讓這種設想徹底泡湯。
3.1.3 Service無法擁有多例項
Service元件與Activity元件另外一個不同點在於,對同一個Service呼叫多次startService並不會啟動多個Service例項,
而非特定Flag的Activity是可以允許這種 情況存在的,因此如果用StubService的方式,為了實現Service的這種特性,
必須建立一個StubService到外掛Service的一 個Map,Map的這種一一對應關係使得使用一個StubService對應多個外掛Service的計劃成為天方夜譚。
至此,結論已經非常清晰——對於Service元件的外掛化,不能簡單地套用Activity的方案。
3.2 如何實現Service的外掛化?
上文指出,不能套用Activity的方案實現Service元件的外掛化,可以通過手動控制Service元件的生命週期實現;
Service的生命週期相當簡單:整個生命週期從呼叫onCreate() 開始起,到 onDestroy() 返回時結束。
對於非繫結服務,就是從startService呼叫到stopService或者stopSelf呼叫。對於繫結服務,就是 bindService呼叫到unbindService呼叫;
如果要手動控制Service元件的生命週期,只需要模擬出這個過程即可;而實現這一點並不複雜:
1.如果以startService方式啟動外掛Service,直接回調要啟動的Service物件的onStartCommand方法即可;
如果用stopService或者stopSelf的方式停止Service,只需要回撥對應的Service元件的onDestroy方法。
2.如果用bindService方式繫結外掛Service,可以呼叫對應Service對應的onBind方法,獲取onBind方法返回的Binder物件,
然後通過ServiceConnection物件進行回撥統計;unBindService的實現同理。
3.2.1 完全手動控制
現在已經有了實現思路,那麼具體如何實現呢?
必須在startService,stopService等方法被呼叫的時候拿到控制權,才能手動去控制Service的生命週期;
要達到這一 目的非常簡單——Hook ActivityManagerNative即可。在Activity的外掛化方案中就通過這種方式接管了startActivity呼叫,相信讀者 並不陌生。
Hook掉ActivityManagerNative之後,可以攔截對於startService以及stopService等方法的呼叫;
攔截之後,可以直接對外掛Service進行操作:
1.攔截到startService之後,如果Service還沒有建立就直接建立Service物件(可能需要載入外掛),
然後呼叫這個Service的onCreate,onStartCommond方法;如果Service已經建立,獲取到原來建立的Service物件並執行其 onStartCommand方法。
2.攔截到stopService之後,獲取到對應的Service物件,直接呼叫這個Service的onDestroy方法。
這種方案簡直簡單得讓人不敢相信!很可惜,這麼幹是不行的。
首先,Service存在的意義在於它作為一個後臺任務,擁有相對較高執行時優先順序;
除非在記憶體及其不足威脅到前臺Activity的時候,這個組 件才會被系統殺死。上述這種實現完全把Service當作一個普通的Java物件使用了,
因此並沒有完全實現Service所具備的能力。
其次,Activity以及Service等元件是可以指定程序的,而讓Service執行在某個特定程序的情況非常常見——
所謂的遠端 Service;用上述這種辦法壓根兒沒有辦法讓某個Service物件執行在一個別的程序。
Android系統給開發者控制程序的機會太少了,要麼在AndroidManifest.xml中通過process屬性指定,
要麼藉助Java的Runtime類或者native的fork;這幾種方式都無法讓以一種簡單的方式配合上述方案達到目的。
3.2.2代理分發技術
既然希望外掛的Service具有一定的執行時優先順序,那麼一個貨真價實的Service元件是必不可少的——
只有這種被系統認可的真正的Service元件才具有所謂的執行時優先順序。
因此,可以註冊一個真正的Service元件ProxyService,讓這個Service承載一個真正的Service元件所具備的能力 (程序優先順序等);
當啟動外掛的服務比如PluginService的時候,統一啟動這個ProxyService,當這個ProxyService 執行起來之後,
再在它的onStartCommand等方法裡面進行分發,執行PluginService的onStartCommond等對應的方法;把這種方案形象地稱為「代理分發技術」
代理分發技術也可以完美解決外掛Service可以執行在不同的程序的問題——可以在AndroidManifest.xml中註冊多個ProxyService,
指定它們的process屬性,讓它們執行在不同的程序;當啟動的外掛Service希望執行在一個新的程序時,
可以選擇 某一個合適的ProxyService進行分發。也許有童鞋會說,那得註冊多少個ProxyService才能滿足需求啊?
理論上確實存在這問題,但事實上,一個App使用超過10個程序的幾乎沒有;因此這種方案是可行的。
3.3 Service外掛化的實現
現在已經設計出了Service元件的外掛化方案,接下來以startService以及stopService為例實現這個過程。
3.3.1 註冊代理Service
需要一個貨真價實的Service元件來承載程序優先順序等功能,因此需要在AndroidManifest.xml中宣告一個或者多個(用以支援多程序)這樣的Sevice:
<service
android:name="com.weishu.upf.service_management.app.ProxyService"
android:process="plugin01"/>
3.3.2 攔截startService
要手動控制Service元件的生 命週期,需要攔截startService,stopService等呼叫,
並且把啟動外掛Service全部重定向為啟動ProxyService(保留原始外掛Service資訊);這個攔截過程需要HookActvityManagerNative。
public static void hookActivityManagerNative() throws ClassNotFoundException,
NoSuchMethodException, InvocationTargetException,
IllegalAccessException, NoSuchFieldException {
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// gDefault是一個 android.util.Singleton物件; 取出這個單例裡面的欄位
Class<?> singleton = Class.forName("android.util.Singleton");
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// ActivityManagerNative 的gDefault物件裡面原始的 IActivityManager物件
Object rawIActivityManager = mInstanceField.get(gDefault);
// 建立一個這個物件的代理物件, 然後替換這個欄位, 讓的代理物件幫忙幹活
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
mInstanceField.set(gDefault, proxy);
}
在收到startService,stopService之後可以進行具體的操作,對於startService來說,就是直接替換啟動的外掛Service為ProxyService等待後續處理,程式碼如下:
if ("startService".equals(method.getName())) {
// API 23:
// public ComponentName startService(IApplicationThread caller, Intent service,
// String resolvedType, int userId) throws RemoteException
// 找到引數裡面的第一個Intent 物件
Pair<Integer, Intent> integerIntentPair = foundFirstIntentOfArgs(args);
Intent newIntent = new Intent();
// 代理Service的包名, 也就是自己的包名
String stubPackage = UPFApplication.getContext().getPackageName();
// 這裡把啟動的Service替換為ProxyService, 讓ProxyService接收生命週期回撥
ComponentName componentName = new ComponentName(stubPackage, ProxyService.class.getName());
newIntent.setComponent(componentName);
// 把原始要啟動的TargetService先存起來
newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, integerIntentPair.second);
// 替換掉Intent, 達到欺騙AMS的目的
args[integerIntentPair.first] = newIntent;
Log.v(TAG, "hook method startService success");
return method.invoke(mBase, args);
}
3.3.3 分發Service
Hook ActivityManagerNative之後,所有的外掛Service的啟動都被重定向了到了註冊的ProxyService,
這樣可以保證們的外掛Service有一個真正的Service元件作為宿主;但是要執行特定外掛Service的任務,
必須把這個任務分發到真正要啟動的Service上去;以onStart為例,在啟動ProxyService之後,
會收到ProxyService的onStart回撥,可以在這個方法裡面把具體的任務交給原始要啟動的外掛Service元件:
public void onStart(Intent intent, int startId) {
Log.d(TAG, "onStart() called with " + "intent = [" + intent + "], startId = [" + startId + "]");
// 分發Service
ServiceManager.getInstance().onStart(intent, startId);
super.onStart(intent, startId);
}
1載入Service
可以在ProxyService裡面把任務轉發給真正要啟動的外掛 Service元件,要完成這個過程肯定需要建立一個對應的外掛Service物件,
比如PluginService;但是通常情況下外掛存在與單獨的檔案之中,正常的方式是無法建立這個PluginService物件的,
宿主程式預設的ClassLoader無法載入外掛中對應的這個類;所以,要建立這個對應的PluginService物件,
必須先完成外掛的載入過程,讓這個外掛中的所有類都可以被正常訪問;這種技術在之前專門討論過,這裡選擇第一種簡單的方案。
public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 獲取 BaseDexClassLoader : pathList
Field pathListField = DexClassLoader.class.getSuperclass().getDeclaredField("pathList");
pathListField.setAccessible(true);
Object pathListObj = pathListField.get(cl);
// 獲取 PathList: Element[] dexElements
Field dexElementArray = pathListObj.getClass().getDeclaredField("dexElements");
dexElementArray.setAccessible(true);
Object[] dexElements = (Object[]) dexElementArray.get(pathListObj);
// Element 型別
Class<?> elementClass = dexElements.getClass().getComponentType();
// 建立一個數組, 用來替換原始的陣列
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1);
// 構造外掛Element(File file, boolean isDirectory, File zip, DexFile dexFile) 這個建構函式
Constructor<?> constructor = elementClass.getConstructor(File.class, boolean.class, File.class, DexFile.class);
Object o = constructor.newInstance(apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0));
Object[] toAddElementArray = new Object[] { o };
// 把原始的elements複製進去
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
// 外掛的那個element複製進去
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length);
// 替換
dexElementArray.set(pathListObj, newElements);
2 匹配過程
上文中把啟動外掛Service重定向為啟動ProxyService,現在 ProxyService已經啟動,
因此必須把控制權交回原始的PluginService;在載入外掛的時候,儲存了外掛中所有的Service元件的資訊,
因此,只需要根據Intent裡面的Component資訊就可以取出對應的PluginService。
private ServiceInfo selectPluginService(Intent pluginIntent) {
for (ComponentName componentName : mServiceInfoMap.keySet()) {
if (componentName.equals(pluginIntent.getComponent())) {
return mServiceInfoMap.get(componentName);
}
}
return null;
}
3建立以及分發
外掛被載入之後,就需要建立外掛Service對應的Java物件了;由於這些類是在執行時動態載入進來的,
肯定不能直接使用new關鍵字——需要使用反射機制。但是下面的程式碼創建出外掛Service物件能滿足要求嗎?
ClassLoader cl = getClassLoader();
Service service = cl.loadClass("com.plugin.xxx.PluginService1");
Service作為Android系統的元件,最重要的特點是它具有Context;所以,直接通過反射創建出來的這個PluginService
就是一個殼子——沒有Context的Service能幹什麼?因此需要給將要建立的Service類創建出 Conetxt;
但是Context應該如何建立呢?平時壓根兒沒有這麼幹過,Context都是系統給建立好的。既然這樣,
可以參照一下系 統是如何建立Service物件的;在上文的Service原始碼分析中,
在ActivityThread類的handleCreateService完成了這個步驟,摘要如下:
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
}
try {
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
可以看到,系統也是通過反射創建出了對應的Service物件,然後也建立了對應的Context,並給Service注入了活力。
如果模擬系統建立Context這個過程,勢必需要進行一系列反射呼叫,那麼何不直接反射handleCreateService方法呢?
當然,handleCreateService這個方法並沒有把創建出來的Service物件作為返回值返回,
而是存放在ActivityThread的成員變數mService之中,這個是小case,反射取出來就行;所以,建立Service物件的程式碼如下:
/**
* 通過ActivityThread的handleCreateService方法創建出Service物件
* @param serviceInfo 外掛的ServiceInfo
* @throws Exception
*/
private void proxyCreateService(ServiceInfo serviceInfo) throws Exception {
IBinder token = new Binder();
// 建立CreateServiceData物件, 用來傳遞給ActivityThread的handleCreateService 當作引數
Class<?> createServiceDataClass = Class.forName("android.app.ActivityThread$CreateServiceData");
Constructor<?> constructor = createServiceDataClass.getDeclaredConstructor();
constructor.setAccessible(true);
Object createServiceData = constructor.newInstance();
// 寫入建立的createServiceData的token欄位, ActivityThread的handleCreateService用這個作為key儲存Service
Field tokenField = createServiceDataClass.getDeclaredField("token");
tokenField.setAccessible(true);
tokenField.set(createServiceData, token);
// 寫入info物件
// 這個修改是為了loadClass的時候, LoadedApk會是主程式的ClassLoader, 選擇Hook BaseDexClassLoader的方式載入外掛
serviceInfo.applicationInfo.packageName = UPFApplication.getContext().getPackageName();
Field infoField = createServiceDataClass.getDeclaredField("info");
infoField.setAccessible(true);
infoField.set(createServiceData, serviceInfo);
// 寫入compatInfo欄位
// 獲取預設的compatibility配置
Class<?> compatibilityClass = Class.forName("android.content.res.CompatibilityInfo");
Field defaultCompatibilityField = compatibilityClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
Object defaultCompatibility = defaultCompatibilityField.get(null);
Field compatInfoField = createServiceDataClass.getDeclaredField("compatInfo");
compatInfoField.setAccessible(true);
compatInfoField.set(createServiceData, defaultCompatibility);
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
Object currentActivityThread = currentActivityThreadMethod.invoke(null);
// private void handleCreateService(CreateServiceData data) {
Method handleCreateServiceMethod = activityThreadClass.getDeclaredMethod("handleCreateService", createServiceDataClass);
handleCreateServiceMethod.setAccessible(true);
handleCreateServiceMethod.invoke(currentActivityThread, createServiceData);
// handleCreateService創建出來的Service物件並沒有返回, 而是儲存在ActivityThread的mServices欄位裡面, 這裡手動把它取出來
Field mServicesField = activityThreadClass.getDeclaredField("mServices");
mServicesField.setAccessible(true);
Map mServices = (Map) mServicesField.get(currentActivityThread);
Service service = (Service) mServices.get(token);
// 獲取到之後, 移除這個service, 只是借花獻佛
mServices.remove(token);
// 將此Service儲存起來
mServiceMap.put(serviceInfo.name, service);
}
現在已經創建出了對應的PluginService,並且擁有至關重要的Context物件;接下來就可以把訊息分發給原始的PluginService元件了,
這個分發的過程很簡單,直接執行訊息對應的回撥(onStart,onDestroy等)即可;因此,完整的startService分發過程如下:
public void onStart(Intent proxyIntent, int startId) {
Intent targetIntent = proxyIntent.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
ServiceInfo serviceInfo = selectPluginService(targetIntent);
if (serviceInfo == null) {
Log.w(TAG, "can not found service : " + targetIntent.getComponent());
return;
}
try {
if (!mServiceMap.containsKey(serviceInfo.name)) {
// service還不存在, 先建立
proxyCreateService(serviceInfo);
}
Service service = mServiceMap.get(serviceInfo.name);
service.onStart(targetIntent, startId);
} catch (Exception e) {
e.printStackTrace();
}
}
至此,已經實現了Service元件的外掛化;程式碼以startService, stopService為例進行了說明,bindService以及unbindService的原理是一樣的。
4小節
本文中以繫結服務為例分析了Service元件的工作原理,並指出使用者交導致元件生命週期的變化是 Activity與Service的根本差別,
這種差別使得外掛方案對於它們必須採取不同的處理方式;最後通過手動控制Service元件的生命週期 結合「代理分發技術」
成功地實現了Service元件的外掛化;這種外掛化方案堪稱「完美」,如果非要吹毛求疵,
那隻能說由於同一個程序的所有 Service都掛載在同一個ProxyService上面,如果系統可用記憶體不夠必須回收Service,
殺死一個ProxyService會導致一大票的外掛Service歇菜。
實際使用過程中,Service元件的更新頻度並不高,因此直接把外掛Service註冊到主程式也是可以接受的;
而且如果需要繫結遠端Service,完全可以使用一個Service元件根據不同的Intent返回不同的IBinder,
所以不實現Service元件的外掛化也能滿足工 程需要。值得一提的是,對於Service元件的外掛化方案實際上是一種「代理」的方式,
用這種方式也能實現Activity元件的外掛化,有一些開源的外掛方案比如 DL 就是這麼做的。
迄今為止,講述了了Activity、BroadcastReceiver以及Service的外掛化方式,不知讀者思索過沒有,實現外掛化的關鍵點在哪裡?
Service,Activity等不過就是一些普通的Java類,它們之所稱為四大元件,是因為他們有生命週期;
這也是簡單地採用Java的動態載入技術無法實現外掛化的原因——動態載入進來的Service等類如果沒有它的生命週期,
無異於一個沒有靈魂的傀儡。對於Activity元件,由於他的生命週期受使用者互動影響,只有系統本身才能對這種互動有全域性掌控力,
因此它的外掛化方式是Hook AMS,但是生命週期依然交由系統管理;而Service以及BroadcastReceiver的生命週期沒有額外的因素影響,
因此選擇了手動控制其生命週期的方式。不論是借屍還魂還是女媧造人,對這些元件的外掛化終歸結底是要賦予元件“生命”。
相關推薦
android service外掛化之二
3. Service的外掛化 現在已經明白了Service元件的工作原理,可對如何實現Service的外掛化依然是一頭霧水。 從上文的原始碼分析來看,Service元件與Activity有著非常多的相似之處:它們都是通過Context類完成啟動, 接著通過 Activit
android service外掛化之一
1, 概述 本文將探討Android四大元件之一——Service元件的外掛化方式。 與Activity, BroadcastReceiver相比,Service元件的不同點在哪裡呢?能否用與之相同的方式實現Service的外掛化? 如果不行,它們的差別在哪裡,應該如何實
外掛化之外掛Service 新的Hook方法
給大家分享一個新的Hook外掛Service的方法,與Activity替換類似,可以先在AndroidManifest.xml預留一個service,然後通過intent啟動,並且將真正的Service的classname傳遞過去。可是Service沒有涉及
外掛化系列開發之九--Android 全面外掛化 RePlugin 流程與原始碼解析
RePlugin,360開源的全面外掛化框架,按照官網說的,其目的是“儘可能多的讓模組變成外掛”,並在很穩定的前提下,儘可能像開發普通App那樣靈活。那麼下面就讓我們一起深入♂瞭解它吧。 (ps :閱讀本文請多參考原始碼圖片 ( ̄^ ̄)ゞ ) 一、介紹 RePlugi
Android 外掛化之Hook機制
Android Hook簡介 什麼是Hook Hook 英文翻譯過來就是「鉤子」的意思,就是在程式執行的過程中去擷取其中的資訊。Android 作業系統中系統維護著自己的一套事件分發機制,那麼Hook就是在事件傳送到終點前截獲並監控事件的傳輸。其原理示意圖如
Android業務組件化之Gradle和Sonatype Nexus搭建私有maven倉庫
Android 前言: 公司的業務組件化推進的已經差不多三四個月的時間了,各個業務組件之間的解耦工作已經基本完成,各個業務組件以module的形式存在項目中,然後項目依賴本地的module,多少有點不太利於項目的並行開發維護了,本質原因就是如果是依賴本地的,必須要將依賴
Android組件化之終極方案
string 問題 我想 ont 建倉 組織 net 直接 互聯網技術 Android組件化項目地址:Android組件化項目AndroidModulePattern Fragment或View如何支持組件化 如何管理組件 Fragment或View如何
Android 專案元件化之建立module,生成aar,引入aar
導言: 在android平時的開發中,經常自己寫的東西讓別人使用,那麼就有module,aar,jar等方式. 1:module通過import module並dependencies完成 2:aar,包括所有檔案的android專用包,通過右邊的gradle->assembl
Android三種動畫之(二)補間動畫
介紹說明:補間動畫是一種定義開始狀態和結束狀態,其中播放的時候還能新增設定的一種動畫,它的動畫方式有平移(TranslateAnimation)、縮放(ScaleAnimation)、旋轉(RotateAnimation)、透明度(AlphaAnimation)四個子類,四種變化,也可以將這
【Android】Material Design 之二 BottomNavigationView使用
上午記錄了TabLayout的使用,簡單實現了一個頂部可滑動的導航效果,突然想到Material Design的另一個控制元件BottomNavigationView,可以實現類似淘寶、微信、QQ、京東的底部導航欄的效果,下面就來介紹一下使用BottomNavigationV
Service外掛化解決方案
--摘自《android外掛化開發指南》 1.ActivityThread最終是通過Instrumentation啟動一個Activity的。而ActivityThread啟動Service並不藉助於Instrumentation,而是直接把Service反射出來就啟動了。Instrumentation只給
外掛化之VirtualApk實戰一:專案配置
零、 介紹一下 VirtualApk是滴滴開源的一套外掛化方案,其支援四大元件,支援外掛宿主之間的互動,相容性強,在滴滴出行APP中有應用。下面是官方文件中與其他主流外掛化框架的對比(檢視原文): 特性 DynamicLoadApk DynamicAPK S
基於RStudio 實現資料視覺化之二
1、資料預覽 (資料來源於國家統計局) 2、輪廓圖 > par(mai=c(0.7,0.7,0.1,0.1),cex=0.8) > matplot(t(income[,2:9]),type="b",lty = 1:7,col=1:7,xlab = "消費專案",
Android開發學習資源之(二)
1.Android開源專案分類彙總(很多特效) https://github.com/Trinea/android-open-project 2.Android開發技術週報(AndroidDevWeekly) http://www.androidweekly.cn/tag/
Android Plugin 外掛化技術-Small外掛框架
本篇文章只是整理了一些流行的開源外掛化技術,其中言論純屬開源作者,不代表本人觀點。 完美內建 所有外掛支援內置於宿主包中高度透明 外掛編碼、佈局編寫方式與獨立應用開發無異外掛程式碼除錯與整包開發無
idea外掛編寫之二
做人要有始有終,答應的下一篇一定會給你們寫,雖然以後可能不再會做外掛這類的技術了,但也很開心給我這個機會,讓我做機試題。idea外掛自定義按鈕開啟EditRunConfigurations上一篇教大家學會了建立idea的自定義按鈕,這一節在action建立完畢的基礎上,給大家
Android業務元件化之現狀分析與探討
前言: 從個人經歷來說的話,從事APP開發這麼多年來,所接觸的APP的體積變得越來越大,業務的也變得越來越複雜,總來來說只有一句話:這是一個APP臃腫的時代!所以為了告別APP臃腫的時代,讓我們進入一個U盤時代,每個業務模組都是一個具備獨立執行的U盤,插在哪
Android業務元件化之Gradle和Sonatype Nexus搭建私有maven倉庫
前言: 公司的業務元件化推進的已經差不多三四個月的時間了,各個業務元件之間的解耦工作已經基本完成,各個業務元件以module的形式存在專案中,然後專案依賴本地的module,多少有點不太利於專案的並行開發維護了,本質原因就是如果是依賴本地的,必須要將依賴的module和主工程放在一個project裡
Android業務元件化之子模組SubModule的拆分以及它們之間的路由Router實現
前言: 前面分析了APP的現狀以及業務元件化的一些探討(Android業務元件化之現狀分析與探討),以及通訊的橋樑Scheme的使用(Android業務元件化之URL Scheme使用),今天重點來聊下子模組SubModule的拆分以及它們之間的路由Router實現。本篇涉及的相關知識比較多,閱讀
Android業務元件化之URL Scheme使用
前言: 最近公司業務發展迅速,單一的專案工程不再適合公司發展需要,所以開始推進公司APP業務元件化,很榮幸自己能夠牽頭做這件事,經過研究實現元件化的通訊方案通過URL Scheme,所以想著現在還是在預研階段,很有必要先了解一下URL Scheme,看看是如何使用的?其實在之前做Hybrid混合程