1. 程式人生 > >android service外掛化之二

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混合程