https://blog.csdn.net/windskier/article/details/6317867
1 . binder 通信概述
binder 通信是一種 client-server 的通信結構,
1. 從表面上來看,是 client 通過獲得一個 server 的代理接口,對 server 進行直接調用;
2. 實際上,代理接口中定義的方法與 server 中定義的方法是一一對應的;
3.client 調用某個代理接口中的方法時,代理接口的方法會將 client 傳遞的參數打包成為 Parcel 對象;
4. 代理接口將該 Parcel 發送給內核中的 binder driver.
5.server 會讀取 binder driver 中的請求數據,如果是發送給自己的,解包 Parcel 對象,處理並將結果返回;
6. 整個的調用過程是一個同步過程,在 server 處理的時候, client 會 block 住。
2 . service manager
Service Manager 是一個 linux 級的進程 , 顧名思義,就是 service 的管理器。這裡的 service 是什麼概念呢?這裡的 service 的概念和 init 過程中 init.rc 中的 service 是不同, init.rc 中的 service 是都是 linux 進程,但是這裡的 service 它並不一定是一個進程,也就是說可能一個或多個 service 屬於同一個 linux 進程。在這篇文章中不加特殊說明均指 android native 端的 service 。
任何 service 在被使用之前,均要向 SM(Service Manager) 註冊,同時客戶端需要訪問某個 service 時,應該首先向 SM 查詢是否存在該服務。如果 SM 存在這個 service ,那麼會將該 service 的 handle 返回給 client , handle 是每個 service 的唯一標識符。
SM 的入口函數在 service_manager.c 中,下面是 SM 的代碼部分
int main(int argc, char **argv)
{
struct binder_state *bs;
void *svcmgr = BINDER_SERVICE_MANAGER;
bs = binder_open(128*1024);
if (binder_become_context_manager(bs)) {
LOGE("cannot become context manager (%s)/n", strerror(errno));
return -1;
}
svcmgr_handle = svcmgr;
binder_loop(bs, svcmgr_handler);
return 0;
}
這個進程的主要工作如下:
1. 初始化 binder ,打開 /dev/binder 設備;在內存中為 binder 映射 128K 字節空間;
2. 指定 SM 對應的代理 binder 的 handle 為 0 ,當 client 嘗試與 SM 通信時,需要創建一個 handle 為 0 的代理 binder ,這裡的代理 binder 其實就是第一節中描述的那個代理接口;
3. 通知 binder driver(BD) 使 SM 成為 BD 的 context manager ;
4. 維護一個死循環,在這個死循環中,不停地去讀內核中 binder driver ,查看是否有可讀的內容;即是否有對 service 的操作要求 , 如果有,則調用 svcmgr_handler 回調來處理請求的操作。
5.SM 維護了一個 svclist 列表來存儲 service 的信息。
這裡需要聲明一下,當 service 在向 SM 註冊時,該 service 就是一個 client ,而 SM 則作為了 server 。而某個進程需要與 service 通信時,此時這個進程為 client , service 才作為 server 。因此 service 不一定為 server ,有時它也是作為 client 存在的。
由於下面幾節會介紹一些與 binder 通信相關的幾個概念,所以將 SM 的功能介紹放在了後面的部分來講。
應用和 service 之間的通信會涉及到 2 次 binder 通信。
1. 應用向 SM 查詢 service 是否存在,如果存在獲得該 service 的代理 binder ,此為一次 binder 通信;
2. 應用通過代理 binder 調用 service 的方法,此為第二次 binder 通信。
3 . ProcessState
ProcessState 是以單例模式設計的。每個進程在使用 binder 機制通信時,均需要維護一個 ProcessState 實例來描述當前進程在 binder 通信時的 binder 狀態。
ProcessState 有如下 2 個主要功能:
1. 創建一個 thread, 該線程負責與內核中的 binder 模塊進行通信,稱該線程為 Pool thread ;
2. 為指定的 handle 創建一個 BpBinder 對象,並管理該進程中所有的 BpBinder 對象。
3.1 Pool thread
在 Binder IPC 中,所有進程均會啟動一個 thread 來負責與 BD 來直接通信,也就是不停的讀寫 BD ,這個線程的實現主體是一個 IPCThreadState 對象,下面會介紹這個類型。
下面是 Pool thread 的啟動方式:
ProcessState::self()->startThreadPool();
3.2 BpBinder 獲取
BpBinder 主要功能是負責 client 向 BD 發送調用請求的數據。它是 client 端 binder 通信的核心對象,通過調用 transact 函數向 BD 發送調用請求的數據,它的構造函數如下:
BpBinder(int32_t handle);
通過 BpBinder 的構造函數發現, BpBinder 會將當前通信中 server 的 handle 記錄下來,當有數據發送時,會通知 BD 數據的發送目標。
ProcessState 通過如下方式來獲取 BpBinder 對象:
ProcessState::self()->getContextObject(handle);
在這個過程中, ProcessState 會維護一個 BpBinder 的 vector mHandleToObject ,每當 ProcessState 創建一個 BpBinder 的實例時,回去查詢 mHandleToObject ,如果對應的 handle 已經有 binder 指針,那麼不再創建,否則創建 binder 並插入到 mHandleToObject 中。
ProcessState 創建的 BpBinder 實例,一般情況下會作為參數構建一個 client 端的代理接口,這個代理接口的形式為 BpINTERFACE , 例如在與 SM 通信時, client 會創建一個代理接口 BpServiceManager .
4 . IPCThreadState
IPCThreadState 也是以單例模式設計的。由於每個進程只維護了一個 ProcessState 實例,同時 ProcessState 只啟動一個 Pool thread ,也就是說每一個進程只會啟動一個 Pool thread ,因此每個進程則只需要一個 IPCThreadState 即可。
Pool thread 的實際內容則為:
IPCThreadState::self()->joinThreadPool();
ProcessState 中有 2 個 Parcel 成員, mIn 和 mOut , Pool thread 會不停的查詢 BD 中是否有數據可讀,如果有將其讀出並保存到 mIn ,同時不停的檢查 mOut 是否有數據需要向 BD 發送,如果有,則將其內容寫入到 BD 中,總而言之,從 BD 中讀出的數據保存到 mIn ,待寫入到 BD 中的數據保存在了 mOut 中。
ProcessState 中生成的 BpBinder 實例通過調用 IPCThreadState 的 transact 函數來向 mOut 中寫入數據,這樣的話這個 binder IPC 過程的 client 端的調用請求的發送過程就明了了 。
IPCThreadState 有兩個重要的函數, talkWithDriver 函數負責從 BD 讀寫數據, executeCommand 函數負責解析並執行 mIn 中的數據。
5. 主要基類
5.1 基類 IInterface
為 server 端提供接口,它的子類聲明了 service 能夠實現的所有的方法;
5.2 基類 IBinder
BBinder 與 BpBinder 均為 IBinder 的子類,因此可以看出 IBinder 定義了 binder IPC 的通信協議, BBinder 與 BpBinder 在這個協議框架內進行的收和發操作,構建了基本的 binder IPC 機制。
5.3 基類 BpRefBase
client 端在查詢 SM 獲得所需的的 BpBinder 後, BpRefBase 負責管理當前獲得的 BpBinder 實例。
6. 兩個接口類
6.1 BpINTERFACE
如果 client 想要使用 binder IPC 來通信,那麼首先會從 SM 出查詢並獲得 server 端 service 的 BpBinder ,在 client 端,這個對象被認為是 server 端的遠程代理。為了能夠使 client 能夠想本地調用一樣調用一個遠程 server , server 端需要向 client 提供一個接口, client 在在這個接口的基礎上創建一個 BpINTERFACE ,使用這個對象, client 的應用能夠想本地調用一樣直接調用 server 端的方法。而不用去關心具體的 binder IPC 實現。
下面看一下 BpINTERFACE 的原型:
class BpINTERFACE : public BpInterface<IINTERFACE>
順著繼承關係再往上看
template<typename INTERFACE>
class BpInterface : public INTERFACE, public BpRefBase
BpINTERFACE 分別繼承自 INTERFACE ,和 BpRefBase ;
● BpINTERFACE 既實現了 service 中各方法的本地操作,將每個方法的參數以 Parcel 的形式發送給 BD 。
例如 BpServiceManager 的
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
● 同時又將 BpBinder 作為了自己的成員來管理,將 BpBinder 存儲在 mRemote 中, BpServiceManager 通過調用 BpRefBase 的 remote() 來獲得 BpBinder 指針。
6.2 BnINTERFACE
在定義 android native 端的 service 時,每個 service 均繼承自 BnINTERFACE(INTERFACE 為 service name) 。 BnINTERFACE 類型定義了一個 onTransact 函數,這個函數負責解包收到的 Parcel 並執行 client 端的請求的方法。
順著 BnINTERFACE 的繼承關係再往上看,
class BnINTERFACE: public BnInterface<IINTERFACE>
IINTERFACE 為 client 端的代理接口 BpINTERFACE 和 server 端的 BnINTERFACE 的共同接口類,這個共同接口類的目的就是保證 service 方法在 C-S 兩端的一致性。
再往上看
class BnInterface : public INTERFACE, public BBinder
同時我們發現了 BBinder 類型,這個類型又是干什麼用的呢?既然每個 service 均可視為一個 binder ,那麼真正的 server 端的 binder 的操作及狀態的維護就是通過繼承自 BBinder 來實現的。可見 BBinder 是 service 作為 binder 的本質所在。
那麼 BBinder 與 BpBinder 的區別又是什麼呢?
其實它們的區別很簡單, BpBinder 是 client 端創建的用於消息發送的代理,而 BBinder 是 server 端用於接收消息的通道。查看各自的代碼就會發現,雖然兩個類型均有 transact 的方法,但是兩者的作用不同, BpBinder 的 transact 方法是向 IPCThreadState 實例發送消息,通知其有消息要發送給 BD ;而 BBinder 則是當 IPCThreadState 實例收到 BD 消息時,通過 BBinder 的 transact 的方法將其傳遞給它的子類 BnSERVICE 的 onTransact 函數執行 server 端的操作。
7. Parcel
Parcel 是 binder IPC 中的最基本的通信單元,它存儲 C-S 間函數調用的參數 . 但是 Parcel 只能存儲基本的數據類型,如果是複雜的數據類型的話,在存儲時,需要將其拆分為基本的數據類型來存儲。
簡單的 Parcel 讀寫不再介紹,下面著重介紹一下 2 個函數
7.1 writeStrongBinder
當 client 需要將一個 binder 向 server 發送時,可以調用此函數。例如
virtual status_t addService(const String16& name, const sp<IBinder>& service)
{
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
data.writeString16(name);
data.writeStrongBinder(service);
status_t err = remote()->transact(ADD_SERVICE_TRANSACTION, data, &reply);
return err == NO_ERROR ? reply.readExceptionCode() : err;
}
看一下 writeStrongBinder 的實體
status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
return flatten_binder(ProcessState::self(), val, this);
}
接著往裡看 flatten_binder
status_t flatten_binder(const sp<ProcessState>& proc,
const sp<IBinder>& binder, Parcel* out)
{
flat_binder_object obj;
obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
if (binder != NULL) {
IBinder *local = binder->localBinder();
if (!local) {
BpBinder *proxy = binder->remoteBinder();
if (proxy == NULL) {
LOGE("null proxy");
}
const int32_t handle = proxy ? proxy->handle() : 0;
obj.type = BINDER_TYPE_HANDLE;
obj.handle = handle;
obj.cookie = NULL;
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = local->getWeakRefs();
obj.cookie = local;
}
} else {
obj.type = BINDER_TYPE_BINDER;
obj.binder = NULL;
obj.cookie = NULL;
}
return finish_flatten_binder(binder, obj, out);
}
還是拿 addService 為例,它的參數為一個 BnINTERFACE 類型指針, BnINTERFACE 又繼承自 BBinder ,
BBinder* BBinder::localBinder()
{
return this;
}
所以寫入到 Parcel 的 binder 類型為 BINDER_TYPE_BINDER ,同時你在閱讀 SM 的代碼時會發現如果 SM 收到的 service 的 binder 類型不為 BINDER_TYPE_HANDLE 時, SM 將不會將此 service 添加到 svclist ,但是很顯然每個 service 的添加都是成功的, addService 在開始傳遞的 binder 類型為 BINDER_TYPE_BINDER , SM 收到的 binder 類型為 BINDER_TYPE_HANDLE ,那麼這個過程當中究竟發生了什麼?
為了搞明白這個問題,花費我很多的事件,最終發現了問題的所在,原來在 BD 中做了如下操作 (drivers/staging/android/Binder.c) :
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
..........................................
if (fp->type == BINDER_TYPE_BINDER)
fp->type = BINDER_TYPE_HANDLE;
else
fp->type = BINDER_TYPE_WEAK_HANDLE;
fp->handle = ref->desc;
..........................................
}
閱讀完 addService 的代碼,你會發現 SM 只是保存了 service binder 的 handle 和 service 的 name ,那麼當 client 需要和某個 service 通信了,如何獲得 service 的 binder 呢?看下一個函數
7.2 readStrongBinder
當 server 端收到 client 的調用請求之後,如果需要返回一個 binder 時,可以向 BD 發送這個 binder ,當 IPCThreadState 實例收到這個返回的 Parcel 時, client 可以通過這個函數將這個被 server 返回的 binder 讀出。
sp<IBinder> Parcel::readStrongBinder() const
{
sp<IBinder> val;
unflatten_binder(ProcessState::self(), *this, &val);
return val;
}
往裡查看 unflatten_binder
status_t unflatten_binder(const sp<ProcessState>& proc,
const Parcel& in, sp<IBinder>* out)
{
const flat_binder_object* flat = in.readObject(false);
if (flat) {
switch (flat->type) {
case BINDER_TYPE_BINDER:
*out = static_cast<IBinder*>(flat->cookie);
return finish_unflatten_binder(NULL, *flat, in);
case BINDER_TYPE_HANDLE:
*out = proc->getStrongProxyForHandle(flat->handle);
return finish_unflatten_binder(
static_cast<BpBinder*>(out->get()), *flat, in);
}
}
return BAD_TYPE;
}
發現如果 server 返回的 binder 類型為 BINDER_TYPE_BINDER 的話,也就是返回一個 binder 引用的話,直接獲取這個 binder ;如果 server 返回的 binder 類型為 BINDER_TYPE_HANDLE 時,也就是 server 返回的僅僅是 binder 的 handle ,那麼需要重新創建一個 BpBinder 返回給 client 。
有上面的代碼可以看出, SM 保存的 service 的 binder 僅僅是一個 handle ,而 client 則是通過向 SM 獲得這個 handle ,從而重新構建代理 binder 與 server 通信。
這裡順帶提一下一種特殊的情況, binder 通信的雙方即可作為 client ,也可以作為 server. 也就是說此時的 binder 通信是一個半雙工的通信。那麼在這種情況下,操作的過程會比單工的情況複雜,但是基本的原理是一樣的,有興趣可以分析一下 MediaPlayer 和 MediaPlayerService 的例子。
8. 經典橋段分析
main_ mediaserver.cpp
int main(int argc, char** argv)
{
// 創建進程 mediaserver 的 ProcessState 實例
sp<ProcessState> proc(ProcessState::self());
// 獲得 SM 的 BpServiceManager
sp<IServiceManager> sm = defaultServiceManager();
LOGI("ServiceManager: %p", sm.get());
// 添加 mediaserver 中支持的 service 。
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantiate();
// 啟動 ProcessState 的 pool thread
ProcessState::self()->startThreadPool();
// 這一步有重複之嫌,加不加無關緊要。
IPCThreadState::self()->joinThreadPool();
}
9. Java 層的 binder 機制
瞭解了 native 通信機制後,再去分析 JAVA 層的 binder 機制,就會很好理解了。它只是對 native 的 binder 做了一個封裝。這一部分基本上沒有太複雜的過程,這裡不再贅述了。
---------------------
作者:windskier
来源:CSDN
原文:https://blog.csdn.net/windskier/article/details/6317867
版权声明:本文为博主原创文章,转载请附上博文链接!
2018年11月17日 星期六
2017年5月21日 星期日
[轉] Android4.4 無Proximity Sensor的設備撥號中實現自動滅屏
http://www.codes51.com/article/detail_108527.html
設置一個值後調用updatePowerStateLocked方法,這個方法是PowerManagerService的關鍵所在。
關於這個方法所作的工作,前面給的連接裡面解釋的很清楚,我也就不多說了,我們需要知道的是屬於用戶的操作而使電源狀態發生更改一定會調用updateUserActivitySummaryLocked(now, dirtyPhase1)方法,比如你觸摸了屏幕,點擊了一個Button等等,都會調用此方法,用以改變電源的狀態,使你的屏幕亮起來,不進入滅屏狀態(已經處於滅屏狀態只能通過按power鍵喚醒)。
其中有final int screenOffTimeout = getScreenOffTimeoutLocked(); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);還有一個變量nextTimeout,這個變量就是控制下次滅屏時間的,大致是等於screenOffTimeout 減去screenDimDuration的值,getScreenOffTimeoutLocked() 和 getScreenDimDurationLocked(screenOffTimeout)是什麼呢?看代碼:
其中的mScreenOffTimeoutSetting就是取的Settings中用戶設置的休眠時間,MINIMUM_SCREEN_OFF_TIMEOUT是一個常量值10 * 1000,也就是十秒,通常情況下,
好,PowerManagerService中的東東加好了,再來PowerManager中加入setTimeout接口,讓外部類可以調用:
當然,別忘記aidl,還需要在IPowerManager.aidl中加入 void setTimeout(long timeout);
這個方法一開始就執行了這一句:PhoneConstants.State state = mCM.getState();這是獲取電話的狀態,是在撥號中,還是撥通了,還是掛斷了沒有活動,在PhoneConstants中分別定義了這三種狀態:
至此,讓沒有Proximity Sensor的設備在通話過程中自動滅屏的功能告一段落。
現在的電子產品越來越人性化,用戶友好化,在給用戶帶來全新體驗的同時,也在改變著人們的日常生活,所以說科技是偉大的,創新是偉大的。
隨著移動設備的多元化發展,各種微型芯片的嵌入,使得它的功能越來越強大。比如各種各樣的Sensor,最常見的一種是Proximity Sensor,現在的品牌機幾乎都具備,也就是在打電話的時候,為了避免誤操作,在電話接近耳朵的時候讓手機處於滅屏狀態,要實現這一功能使用Proximity Sensor是再好不過的了。
但是也有一些設備不具備Proximity Sensor(比如我們的平板設備-_-,因為其主要功能並非打電話,所以沒有添加接近傳感器),為了做到用戶友好化,就必須得在沒有傳感器的狀況下添加自動滅屏功能。
首先找到撥打電話的界面,4.4和之前的系統代碼架構有了很大的改變,之前的撥號程序就是Phone,現在Phone基本上廢掉了,而且之前提供了一個叫void setPokeLock(int pokey, IBinder lock, String tag)的方法,可以實現幾秒後滅屏,還比較好用,之後的系統這個方法給刪掉了。但是加了個Telephony的程序,代碼路徑packages/service/Telephony,按下撥打電話的按鈕後,經過一系列的流程轉換,最終會進入到PhoneGlobals.java中,代碼路徑:packages/services/Telephony/src/com/android/phone,其中有這麼個方法:
/* package */ void updateWakeState() { PhoneConstants.State state = mCM.getState(); // True if the speakerphone is in use. (If so, we *always* use // the default timeout. Since the user is obviously not holding // the phone up to his/her face, we don't need to worry about // false touches, and thus don't need to turn the screen off so // aggressively.) // Note that we need to make a fresh call to this method any // time the speaker state changes. (That happens in // PhoneUtils.turnOnSpeaker().) boolean isSpeakerInUse = (state == PhoneConstants.State.OFFHOOK) && PhoneUtils.isSpeakerOn(this); // TODO (bug 1440854): The screen timeout *might* also need to // depend on the bluetooth state, but this isn't as clear-cut as // the speaker state (since while using BT it's common for the // user to put the phone straight into a pocket, in which case the // timeout should probably still be short.) // Decide whether to force the screen on or not. // // Force the screen to be on if the phone is ringing or dialing, // or if we're displaying the "Call ended" UI for a connection in // the "disconnected" state. // However, if the phone is disconnected while the user is in the // middle of selecting a quick response message, we should not force // the screen to be on. // boolean isRinging = (state == PhoneConstants.State.RINGING); boolean isDialing = (phone.getForegroundCall().getState() == Call.State.DIALING); boolean isVideoCallActive = PhoneUtils.isImsVideoCallActive(mCM.getActiveFgCall()); boolean keepScreenOn = isRinging || isDialing || isVideoCallActive; // keepScreenOn == true means we'll hold a full wake lock: requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP); }
/* package */ void requestWakeState(WakeState ws) { if (VDBG) Log.d(LOG_TAG, "requestWakeState(" + ws + ")..."); synchronized (this) { if (mWakeState != ws) { switch (ws) { case PARTIAL: // acquire the processor wake lock, and release the FULL // lock if it is being held. mPartialWakeLock.acquire(); if (mWakeLock.isHeld()) { mWakeLock.release(); } break; case FULL: // acquire the full wake lock, and release the PARTIAL // lock if it is being held. mWakeLock.acquire(); if (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } break; case SLEEP: default: // release both the PARTIAL and FULL locks. if (mWakeLock.isHeld()) { mWakeLock.release(); } if (mPartialWakeLock.isHeld()) { mPartialWakeLock.release(); } break; } mWakeState = ws; } } }這個updateWakestate就是更新通話狀態中屏幕的狀態的,如果有Proximity Sensor,會使用WakeLock鎖去更新屏幕狀態,WakeLock定義在PowerManager中,是一個內部類,這個類主要是通過申請和釋放一個鎖來控制屏幕的變化,最終還是調用到PowerManagerService中,PowerManagerService就是我們通常所說的Android電源管理類。
沒有Proximity Sensor的狀態下,keepScreenOn的狀態最終會被置為false,也就進入到case SLEEP這個分支,什麼也不干,這個時候,PowerManagerService就會調用Settings中用戶設置的休眠時間去使屏幕休眠,如果設置中設置的是30分鐘,那你撥打一個電話後30分鐘後才會休眠。
最簡單的使屏幕休眠的方法是goToSleep(long time),PowerManager中提供了接口以供調用,我們只需要獲得PowerManager服務,就可以調用,當然在AndroidManifest中需要添加相應的權限。但是在撥號中這個方法是不可行的,會造成很不好的用戶體驗,原因就不多說了,直接說我改後測試成功的方法。
在PowerManagerService中,定義了一個標誌位mDirty,其中有十二種狀態的變化,通過各種邏輯處理和複雜的判斷,最終達到管理電源的目的,由於這個電源管理框架是google直接維護的,代碼寫的精簡而富於活力,其中的繁多的狀態變化實在是跟的眼花繚亂,我實在無心去慢慢的打Log一步步的跟進流程,但是也有牛人把這套東西一步步跟的清清楚楚並分享出來了,參考:http://wenku.baidu.com/link?url=Ph3fYPtSmbOFpNAvgNIvLJkbo7SW7XWMuRsgLQ0640wPTvXo0DdfIHcXqHpRDN5JHrQb7saiKjAgFvS1Q4kHYqosnze97mIi3iFJjTefS3W
有興趣的可以去深入瞭解。
繼續講我的,在我看到了一個叫private long mUserActivityTimeoutOverrideFromWindowManager = -1;的變量時,我覺得離解決這個問題不遠了。這個官方還有註釋,大概意思是,可通過應用程序設置這個值,臨時的調整屏幕的亮度,-1為禁用。繼續看這個變量在什麼地方用了:
private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) { synchronized (mLock) { if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) { mUserActivityTimeoutOverrideFromWindowManager = timeoutMillis; mDirty |= DIRTY_SETTINGS; updatePowerStateLocked(); } } }
設置一個值後調用updatePowerStateLocked方法,這個方法是PowerManagerService的關鍵所在。
private void updatePowerStateLocked() { if (!mSystemReady || mDirty == 0) { return; } // Phase 0: Basic state updates. updateIsPoweredLocked(mDirty); updateStayOnLocked(mDirty); // Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced // by changes in wakefulness. final long now = SystemClock.uptimeMillis(); int dirtyPhase2 = 0; for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; updateWakeLockSummaryLocked(dirtyPhase1); updateUserActivitySummaryLocked(now, dirtyPhase1); if (!updateWakefulnessLocked(dirtyPhase1)) { break; } } // Phase 2: Update dreams and display power state. updateDreamLocked(dirtyPhase2); updateDisplayPowerStateLocked(dirtyPhase2); // Phase 3: Send notifications, if needed. if (mDisplayReady) { sendPendingNotificationsLocked(); } // Phase 4: Update suspend blocker. // Because we might release the last suspend blocker here, we need to make sure // we finished everything else first! updateSuspendBlockerLocked(); }
關於這個方法所作的工作,前面給的連接裡面解釋的很清楚,我也就不多說了,我們需要知道的是屬於用戶的操作而使電源狀態發生更改一定會調用updateUserActivitySummaryLocked(now, dirtyPhase1)方法,比如你觸摸了屏幕,點擊了一個Button等等,都會調用此方法,用以改變電源的狀態,使你的屏幕亮起來,不進入滅屏狀態(已經處於滅屏狀態只能通過按power鍵喚醒)。
那麼,看看updateUserActivitySummaryLocked幹了什麼:
private void updateUserActivitySummaryLocked(long now, int dirty) { // Update the status of the user activity timeout timer. if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) { mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT); long nextTimeout = 0; if (mWakefulness != WAKEFULNESS_ASLEEP) { final int screenOffTimeout = getScreenOffTimeoutLocked(); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout); mUserActivitySummary = 0; if (mLastUserActivityTime >= mLastWakeTime) { nextTimeout = mLastUserActivityTime + screenOffTimeout - screenDimDuration; if (now < nextTimeout) { mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT; } else { nextTimeout = mLastUserActivityTime + screenOffTimeout; if (now < nextTimeout) { mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM; } } } if (mUserActivitySummary == 0 && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) { nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout; if (now < nextTimeout && mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { mUserActivitySummary = mDisplayPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT ? USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM; } } if (mUserActivitySummary != 0) { Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextTimeout); } } else { mUserActivitySummary = 0; } if (DEBUG_SPEW) { Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness=" + wakefulnessToString(mWakefulness) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout)); } } }
其中有final int screenOffTimeout = getScreenOffTimeoutLocked(); final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);還有一個變量nextTimeout,這個變量就是控制下次滅屏時間的,大致是等於screenOffTimeout 減去screenDimDuration的值,getScreenOffTimeoutLocked() 和 getScreenDimDurationLocked(screenOffTimeout)是什麼呢?看代碼:
private int getScreenOffTimeoutLocked() { int timeout = mScreenOffTimeoutSetting; if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) { timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin); } if (mUserActivityTimeoutOverrideFromWindowManager >= 0) { timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager); } return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT); } private int getScreenDimDurationLocked(int screenOffTimeout) { return Math.min(SCREEN_DIM_DURATION, (int)(screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO)); }
getScreenOffTimeoutLocked()的返回值是settings中用戶設置的休眠值,而此時getScreenDimDurationLocked返回的是SCREEN_DIM_DURATION,也是個常量值,7 * 1000,7秒。每次當用戶觸摸屏幕(或者其它的操作),都會重新設置此值,比如你設置的屏幕休眠時間為5分鐘,你沒有對你的設備進行任何操作,在4分59秒的時候觸摸了一下屏幕,那麼你的設備休眠時間又會重新計時,無操作五分鐘後滅屏,如此循環下去,就是在這個地方處理的。
只有一種情況例外(這裡指的是自然狀態下,沒有外來力量參與的情況,比如有了Proximity Sensor,那就是另外一回事了),那就是設備重啟後進入鎖屏狀態時,會調用setUserActivityTimeoutOverrideFromWindowManager方法,將mUserActivityTimeoutOverrideFromWindowManager的值設為10000,也就是10秒,這個時候getScreenOffTimeoutLocked的返回值就不再是Settings中設置的休眠時間,而是10000,getScreenDimDurationLocked的返回值也不是SCREEN_DIM_DURATION,而是screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO,也就是2000,那麼進入updateUserActivitySummaryLocked方法中計算出來的結果就是8秒滅屏,這也是為什麼在鎖屏狀態下我們的設備會很快的進入休眠的原因。
在鎖屏狀態解除的時候,會在WindowManagerService中調用mPowerManager.setUserActivityTimeoutOverrideFromWindowManager(
mInnerFields.mUserActivityTimeout);
mInnerFields.mUserActivityTimeout);
在這裡將mUserActivityTimeoutOverrideFromWindowManager值設回-1,此時,設備的休眠時間又會回覆到Settings中設置的時間。
看到這裡我們就可以仿照這套流程添加一個變量來控制撥打電話的時候的電源狀態。 在PowerManagerService中加入:
private long mUserActivityTimeoutOverrideFromCall = -1;
在getScreenOffTimeoutLocked()方法中加入:
if (mUserActivityTimeoutOverrideFromCall >= 0) {
timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromCall);
}
timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromCall);
}
還需要加入一個接口以供外部調用:
// Add liao --2015-01-23 private void setUserActivityTimeoutOverrideFromCallInternal(long timeoutMillis) { synchronized (mLock) { if (mUserActivityTimeoutOverrideFromCall != timeoutMillis) { mUserActivityTimeoutOverrideFromCall = timeoutMillis; mDirty |= DIRTY_SETTINGS; updatePowerStateLocked(); } } } // Add liao --2015-01-23 public void setTimeout(long timeoutMillis) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null); final long ident = Binder.clearCallingIdentity(); try { setUserActivityTimeoutOverrideFromCallInternal(timeoutMillis); } finally { Binder.restoreCallingIdentity(ident); } }
好,PowerManagerService中的東東加好了,再來PowerManager中加入setTimeout接口,讓外部類可以調用:
public static final long POKE_LOCK_SHORT_TIMEOUT = 1000L; public static final long POKE_LOCK_TIMEOUT_STOP = -1L;
public void setTimeout(long timeout) { try { mService.setTimeout(timeout); } catch (RemoteException e) { } }
當然,別忘記aidl,還需要在IPowerManager.aidl中加入 void setTimeout(long timeout);
對於aidl怎麼在android系統中使用可以參考我前面寫的文章。
對外的接口都添加完了,接下來就是撥號的地方去更改mUserActivityTimeoutOverrideFromCall的值了,在PhoneGlobals的updateWakeState()方法中加入:
mPowerManager.setTimeout(PowerManager.POKE_LOCK_SHORT_TIMEOUT); if (state == PhoneConstants.State.IDLE) { mPowerManager.setTimeout(PowerManager.POKE_LOCK_TIMEOUT_STOP); }
這個方法一開始就執行了這一句:PhoneConstants.State state = mCM.getState();這是獲取電話的狀態,是在撥號中,還是撥通了,還是掛斷了沒有活動,在PhoneConstants中分別定義了這三種狀態:
public enum State { IDLE, RINGING, OFFHOOK; };
當電話撥打的時候,就會調用setTimeout進入到PowerManagerService中的setUserActivityTimeoutOverrideFromCallInternal方法,更新mUserActivityTimeoutOverrideFromCall的值,並且調用updatePowerStateLocked(),從而進入到updateUserActivitySummaryLocked方法中,通過一系列的算法,最終得出的值和鎖屏狀態的滅屏時間是一樣的,大概8秒,如果嫌8秒太長,可以將MINIMUM_SCREEN_OFF_TIMEOUT這個常量改小一點,比如改成7 * 1000,那麼最終得出的滅屏時間就是5秒,撥號後3秒屏幕變暗,5秒滅屏。
其中,IDLE就表示沒有撥號,電話掛斷的時候必然會將狀態改為IDLE,所以在電話掛斷的時候在將mUserActivityTimeoutOverrideFromCall的值設置為-1,讓滅屏時間回覆到Settings中設置的時間。至此,讓沒有Proximity Sensor的設備在通話過程中自動滅屏的功能告一段落。
以上就介紹了Android4.4 無Proximity Sensor的設備撥號中實現自動滅屏,包括了方面的內容,希望對Android開發有興趣的朋友有所幫助。
2017年3月7日 星期二
android input system
Android 5.0(Lollipop)事件輸入系统(Input System)
Android 用戶輸入處理
http://m.blog.csdn.net/article/details?id=43561773
Android Touch事件分發詳解
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0102/2250.html
Android触摸屏事件派发机制详解与源码分析一(View篇)
http://blog.csdn.net/yanbober/article/details/45887547
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
http://blog.csdn.net/yanbober/article/details/45912661
Android触摸屏事件派发机制详解与源码分析三(Activity篇)
http://blog.csdn.net/yanbober/article/details/45932123
Android Touch事件分發詳解
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0102/2250.html
Android触摸屏事件派发机制详解与源码分析一(View篇)
http://blog.csdn.net/yanbober/article/details/45887547
Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
http://blog.csdn.net/yanbober/article/details/45912661
Android触摸屏事件派发机制详解与源码分析三(Activity篇)
http://blog.csdn.net/yanbober/article/details/45932123
2017年3月1日 星期三
[轉] Android MotionEvent詳解
Android MotionEvent詳解
http://www.jianshu.com/p/0c863bbde8eb
圖解Android事件傳遞之ViewGroup篇
http://remcarpediem.com/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
圖解Android事件傳遞之View篇
http://remcarpediem.com/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
http://www.jianshu.com/p/0c863bbde8eb
圖解Android事件傳遞之ViewGroup篇
http://remcarpediem.com/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
圖解Android事件傳遞之View篇
http://remcarpediem.com/2016/02/04/%E5%9B%BE%E8%A7%A3Android%E4%BA%8B%E4%BB%B6%E4%BC%A0%E9%80%92%E4%B9%8BView%E7%AF%87/
2017年1月9日 星期一
2016年11月20日 星期日
FragmentPagerAdapter and FragmentStatePagerAdapter
http://blog.csdn.net/fangkailong/article/details/38268509
http://www.jianshu.com/p/abf592a71d02
https://read01.com/dkjG3.html
https://read01.com/34on4.html
http://blog.csdn.net/quanjin24k/article/details/17318151
http://www.jianshu.com/p/abf592a71d02
https://read01.com/dkjG3.html
https://read01.com/34on4.html
http://blog.csdn.net/quanjin24k/article/details/17318151
Passing data between a fragment and its container activity
getActivity()
disable the preloading in a viewpager?
@Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { loadData(); //Replace with your method } }
http://stackoverflow.com/questions/18375288/fragment-lifecycle-which-method-is-called-upon-show-hide
2016年11月16日 星期三
Android遠程接口之AIDL
Android遠程接口之AIDL——Parcelable、in、out、inout說明
http://blog.csdn.net/yangzhaomuma/article/details/50576017
想像AIDL IPC機制中, clinet和server中間有一塊buffer, 以buffer對client的角度來看, client把數值寫進buffer則屬性就是in, client把數值從buffer拿出來則屬性就是out
Android Service学习之AIDL, Parcelable和远程服务
http://android.blog.51cto.com/268543/537684/
Android序列化——Serializable與Parcelable
http://blog.csdn.net/yangzhaomuma/article/details/50452651
利用Parcelable在Activity之间传输对象
http://blog.csdn.net/tounaobun/article/details/8185855
http://blog.csdn.net/yangzhaomuma/article/details/50576017
想像AIDL IPC機制中, clinet和server中間有一塊buffer, 以buffer對client的角度來看, client把數值寫進buffer則屬性就是in, client把數值從buffer拿出來則屬性就是out
Android Service学习之AIDL, Parcelable和远程服务
http://android.blog.51cto.com/268543/537684/
Android序列化——Serializable與Parcelable
http://blog.csdn.net/yangzhaomuma/article/details/50452651
利用Parcelable在Activity之间传输对象
http://blog.csdn.net/tounaobun/article/details/8185855
2016年11月8日 星期二
[轉] Android 打包流程
Ⅱ Apk 的組成結構
本文脈絡採用的是一個由結果反推至過程的流程。 在開始時,我們需要先弄明白,apk 文件也就是 Android 打包流程的產物,是一個什麼類型的文件,它包含了一些什麼內容。 弄清楚了這些,我們就可以帶著目的性,去分析打包流程,可以更好的關注 apk 文件中的這些內容是在打包流程的哪個過程中產生,以及是如何產生的。
眾所周知,apk 文件本質上其實是一個 zip 包。想要知道其中包含了什麼,改後綴直接解壓便可以看到了。 這裡筆者挑軟柿子,解壓了某個未經過加固或者其他手段加密的 Android 安裝包文件,以下為結果截圖:

主要注意紅色標註部分,這一些文件和文件夾是一個 Android 應用基本都具備的。而其他的一些文件和文件夾則是一些第三方庫,或者是其他一些代碼生成的。 接下來,依次大概介紹一下這些文件和文件夾的作用。
這裡強烈安利大家升級一下 Android Studio 2.2 Preview,其新功能 Analyze apk是一個很不錯的 apk 分析(~~hack 入門~~)工具。我們可以通過它直接反編譯看到原始的 AndroidManifest.xml 文件,如下圖。




先說 MANIFEST.MF,這個文件保存了整個 apk 文件中所有文件的文件名 + SHA-1後的 base64 編碼值。這也就意味著,MANIFEST.MF 象徵著 apk 包的完整性。再說 CERT.RSA,這個文件保存了公鑰和加密方式的信息。最後說 CERT.SF,這個文件與 MANIFEST.MF 的結構一樣,只是其編碼會被被私鑰加密。這樣一來每次安裝時,通過該文件夾中的文件,就可以完成驗證的過程。如果 apk 包被改變了,而篡改者沒有私鑰生成 CERT.SF,則無法完成校驗。
它記錄了資源文件,資源文件位置(各個維度的路徑)和資源 id 的映射關係。並且將所有的 string 都存放在了 string pool 中,節省了在查找資源時,字符串處理的開銷。
我們可以使用 Androdi Studio 2.2 Preview 中的新功能 Analyze apk (這個新功能用來分析 apk 非常好用,強烈推薦各位讀者可以嘗試一下)來看看它到底包含了些什麼,一圖勝過千言:

我們可以看到,首先是有個 package 可選,實際上 resource.arsc 是可以包含多個 package 的資源的。 然後可以看到一個 Resource Types 的列表。這裡看到的是 drawable 的 type。 右邊顯示了有多少個 drawable 以及多少項 configurations,以及表的具體內容為 ID - Name - 各個維度的值(在這裡即是資源的路徑),通過這個,我們可以完成通過 id + 對應的 configuration 獲取對應資源的操作。
而後面要提到資源混淆的原理,就是修改這裡各個維度的值,並修改對應 res 裡面的文件夾以及文件名實現的。
具體其完整的數據結構比較複雜,在這裡就不展開說了,有興趣的讀者可以自行查閱信息,甚至寫一個 parser 出來也是非常有意思的。
Ⅲ Android 打包流程
在了解了 apk 文件的結構後,我們開始分析 Android 的打包流程。
在這個步驟中,起主要作用的是 aapt。
剛剛提及的 AndroidManifest.xml, res 文件夾,resource.arsc 文件的生成都與其有關,簡單來說,aapt 解析項目代碼中的 AndroidManifest.xml,收集項目中 res 文件夾的資源文件及 xml 文件,對其做壓縮以及編譯的處理。在此過程中,分配了資源 id 並生成了 R.java 文件 以及 arsc 文件。
到這一步,實際上 apk 所需要的主要內容已經大致齊全了。只需要把上面生成的 AndroidManifest.xml,classes.dex,res文件夾,resource.arsc 打包進 apk,並且將項目工程中的 assets 以及 lib 目錄一併放入,就有了一個未經簽名的 Android 安裝包了。
以上便是 Android 打包的基本流程,宏觀來看實際並不複雜,但是其中的一些步驟展開來講,卻是很有內容的,比如 appt 對資源處理的那個部分,R.java 是如何生成的,resource.arsc 又是如何生成的,Android 是怎樣完成對資源的獲取的,這些內容筆者之後會繼續整理分享。
Ⅳ 業內有關技術小結
分析完流程之後,我們來看看目前業內有哪一些與編譯有關的技術可以運用起來的。
一個比較典型的加固流程如下圖:

實際上是通過外包一層解殼 apk,將我們自己的 dex 文藏在加密 apk 的 dex 中。由於破壞了正常的 dex 結構,所以一般的反編譯工具,如 apktool 並不能直接反編譯 apk。但是如果了解了加密的方式以及方案,實際上要破解得到脫殼 dex 並不複雜,這只是一定程度上提高了 hack 的成本,不過針對一般的 hack 依然很有效。
在之前,開發者一般通過 AndroidManifest.xml 中的meta 信息來區分渠道。在了解了打包流程後,大家應該明白,一旦改變了 AndroidManifest.xml 就意味著要重新打包。
ts(多渠道包的時間) = t (打每個包的耗時) * n (n為渠道數量), n 在大到一定程度後,ts 就會變得非常的大。這顯然是讓開發者十分痛苦的。
要解決這個問題,實際上需要攻破的是,META-INF 的完整性校驗機制。
目前業界比較常用的兩種方案是:
1. META-INF 下添加空文件不會破壞簽名(文件名為渠道號,若 Google 更改簽名機制,有可能失效)
2. apk 文件末尾寫入信息(本質是利用了 zip 文件可以添加 comment 數據結構的特點)
這兩種改動方案都不會導致 MANIFEST.MF 文件的改變,也就不需要再次打包,只需要簡單的讀改文件即可。為廣大開發者節省了上線前漫長的等待渠道包的時間。
這樣不但可以減少安裝包的體積,一定程度上也可以提高破解難度。
該方案目前業內也有兩種實現,但是原理基本一致。入手點皆為resource.arsc。
第一種方案是修改我們剛剛提及的 aapt,使得在生成 resource.arsc 的過程中,就修改掉項目中資源的名稱,實現了資源的混淆。
第二種方案則是在打出 apk 包之後,讀入已生成的 resource.arsc 文件,進行混淆,改寫,同時修改掉 res 文件夾下的資源名稱,完成混淆。最後再重新打包得到混淆資源後的新 apk 文件。
既可以減小體積,又可以一定程度增加被 hack 的難度,如果還有沒有用起來的同學,可以嘗試用起來了。
目前業界已經有比較多的技術方案了,在這裡我們提及兩種比較有代表性的方案。
1. 通過 native hook 的方式,替換 class 中的方法實現完成熱補丁。
2. classloader 加載新 dex 覆蓋原有 class 完成替換的方案。
因為實際上每種方案都並非幾句話可以講解清楚,本文主要目的也只是做個大概介紹,若讀者有興趣可以谷歌后進一步研究,這裡就不具體展開講了。
實際上還有一些技術,比如使用 Anotation 自動生成代碼,buck exopackage 提高打包速度等。有興趣的話,可以自行查閱,相信可以學到不少知識的。
本文脈絡採用的是一個由結果反推至過程的流程。 在開始時,我們需要先弄明白,apk 文件也就是 Android 打包流程的產物,是一個什麼類型的文件,它包含了一些什麼內容。 弄清楚了這些,我們就可以帶著目的性,去分析打包流程,可以更好的關注 apk 文件中的這些內容是在打包流程的哪個過程中產生,以及是如何產生的。
眾所周知,apk 文件本質上其實是一個 zip 包。想要知道其中包含了什麼,改後綴直接解壓便可以看到了。 這裡筆者挑軟柿子,解壓了某個未經過加固或者其他手段加密的 Android 安裝包文件,以下為結果截圖:
主要注意紅色標註部分,這一些文件和文件夾是一個 Android 應用基本都具備的。而其他的一些文件和文件夾則是一些第三方庫,或者是其他一些代碼生成的。 接下來,依次大概介紹一下這些文件和文件夾的作用。
2.1. AndroidManifest.xml
這是 Android 應用的全局配置文件,它包含了這個應用的很多配置信息,例如包名、版本號、所需權限、註冊的服務等。可以根據這個文件在相當程度上了解這個應用的一些信息。該文件目前狀態是被編譯為二進位的 XML 文件,可以通過一些工具(如 apktool)反編譯後進行查看。這裡強烈安利大家升級一下 Android Studio 2.2 Preview,其新功能 Analyze apk是一個很不錯的 apk 分析(~~hack 入門~~)工具。我們可以通過它直接反編譯看到原始的 AndroidManifest.xml 文件,如下圖。
2.2. assets 文件夾
assets 文件夾用於保存需要保持原始文件的資源文件夾,開發過程中拖了什麼到裡面,打包完之后里面還是什麼。一般用於存放音頻,網頁(幫助頁面之類的),字體等文件。主要需要知道的點是,它與 res 文件夾的區分以及如何在應用中訪問該文件夾的資源,如它可以有多級目錄而 res 則只有兩級。2.3. dex 文件
classes.dex 文件是 Android 系統運行於 Dalvik Virtual Machine 上的可執行文件,也是Android 應用程式的核心所在。項目工程中的 Java 源碼通過 javac 生成 class 文件,再通過 dx 工具轉換為 classes.dex,注意到我們這裡有 classes2.dex 和 classes3.dex。這是方法數超過一個 dex 的上限,分 dex 的結果。分 dex 在 Android 5.0 之前需要開發者自行完成,5.0 後 dx 自帶支持。dex 文件的數據結構不算複雜,如下圖所示。目前一些熱補丁有關的技術,主要便是對 dex 做各種處理。2.4. lib 文件夾
該目錄存放著應用需要的 native 庫文件。比如一些底層實現的圖片處理、音視頻處理、數據加密的庫以 so 庫的形式在該文件夾中。而該文件夾下有時會多一個層級,這是根據不同CPU 型號而劃分的,如 ARM,ARM-v7a,x86等,如下圖:2.5. META-INF 文件夾
該目錄的主要作用是用於保證 APK 的完整性以及安全性。該文件夾下,主要有三個文件。如下圖:先說 MANIFEST.MF,這個文件保存了整個 apk 文件中所有文件的文件名 + SHA-1後的 base64 編碼值。這也就意味著,MANIFEST.MF 象徵著 apk 包的完整性。再說 CERT.RSA,這個文件保存了公鑰和加密方式的信息。最後說 CERT.SF,這個文件與 MANIFEST.MF 的結構一樣,只是其編碼會被被私鑰加密。這樣一來每次安裝時,通過該文件夾中的文件,就可以完成驗證的過程。如果 apk 包被改變了,而篡改者沒有私鑰生成 CERT.SF,則無法完成校驗。
2.6. res 文件夾
顧名思義,該文件夾是資源文件夾。它裡面存放的所有文件都會被映射到 R 文件中,生成對應的資源 ID,便於代碼中通過 ID 直接訪問。其中的資源文件包括了動畫(anim),圖像(drwable),布局(layout),常量值(values),顏色值(colors),尺寸值(dimens),字符串(strings),自定義樣式(styles)等。2.7. resource.arsc 文件
這個文件可以說是所有文件中結構最複雜的。它記錄了資源文件,資源文件位置(各個維度的路徑)和資源 id 的映射關係。並且將所有的 string 都存放在了 string pool 中,節省了在查找資源時,字符串處理的開銷。
我們可以使用 Androdi Studio 2.2 Preview 中的新功能 Analyze apk (這個新功能用來分析 apk 非常好用,強烈推薦各位讀者可以嘗試一下)來看看它到底包含了些什麼,一圖勝過千言:
我們可以看到,首先是有個 package 可選,實際上 resource.arsc 是可以包含多個 package 的資源的。 然後可以看到一個 Resource Types 的列表。這裡看到的是 drawable 的 type。 右邊顯示了有多少個 drawable 以及多少項 configurations,以及表的具體內容為 ID - Name - 各個維度的值(在這裡即是資源的路徑),通過這個,我們可以完成通過 id + 對應的 configuration 獲取對應資源的操作。
而後面要提到資源混淆的原理,就是修改這裡各個維度的值,並修改對應 res 裡面的文件夾以及文件名實現的。
具體其完整的數據結構比較複雜,在這裡就不展開說了,有興趣的讀者可以自行查閱信息,甚至寫一個 parser 出來也是非常有意思的。
Ⅲ Android 打包流程
在了解了 apk 文件的結構後,我們開始分析 Android 的打包流程。
3.1 資源
Android 打包流程的第一步,是處理資源文件。在這個步驟中,起主要作用的是 aapt。
剛剛提及的 AndroidManifest.xml, res 文件夾,resource.arsc 文件的生成都與其有關,簡單來說,aapt 解析項目代碼中的 AndroidManifest.xml,收集項目中 res 文件夾的資源文件及 xml 文件,對其做壓縮以及編譯的處理。在此過程中,分配了資源 id 並生成了 R.java 文件 以及 arsc 文件。
3.2 代碼
上一步得到了 R.java 文件後,將其與項目代碼一起編譯得到 .class文件,然後打包為 jar 包。這個過程中,還會有混淆代碼這一步驟。之後,再通過 dx 工具,將生成的 jar 包與第三方庫的 jar 包一起編譯為 dex 文件。這個過程中,如果是 5.0 以前的系統且超過了 65535 方法數的限制,需要人為的分 dex,5.0 以後則由 dx 工具包辦。到這一步,實際上 apk 所需要的主要內容已經大致齊全了。只需要把上面生成的 AndroidManifest.xml,classes.dex,res文件夾,resource.arsc 打包進 apk,並且將項目工程中的 assets 以及 lib 目錄一併放入,就有了一個未經簽名的 Android 安裝包了。
3.3 簽名
接下來還缺簡單但是卻關鍵的最後一步,那便是 apk 包的簽名,這一步在之前對 META-INF 的介紹中,實際已有提及。只需要按步驟生成 MANIFEST.MF, CERT.RSA,CERT.SF 並放入META-INF 文件夾即可。以上便是 Android 打包的基本流程,宏觀來看實際並不複雜,但是其中的一些步驟展開來講,卻是很有內容的,比如 appt 對資源處理的那個部分,R.java 是如何生成的,resource.arsc 又是如何生成的,Android 是怎樣完成對資源的獲取的,這些內容筆者之後會繼續整理分享。
Ⅳ 業內有關技術小結
分析完流程之後,我們來看看目前業內有哪一些與編譯有關的技術可以運用起來的。
4.1 apk 加固
目前業內已經有很多 apk 加固的服務,這裡提及最常見的一種加固方案。一個比較典型的加固流程如下圖:
實際上是通過外包一層解殼 apk,將我們自己的 dex 文藏在加密 apk 的 dex 中。由於破壞了正常的 dex 結構,所以一般的反編譯工具,如 apktool 並不能直接反編譯 apk。但是如果了解了加密的方式以及方案,實際上要破解得到脫殼 dex 並不複雜,這只是一定程度上提高了 hack 的成本,不過針對一般的 hack 依然很有效。
4.2 快速多渠道包
由於國內有著數不勝數的 Android 應用市場,所以越來越多的渠道包成為了每一個應用的必須。在之前,開發者一般通過 AndroidManifest.xml 中的meta 信息來區分渠道。在了解了打包流程後,大家應該明白,一旦改變了 AndroidManifest.xml 就意味著要重新打包。
ts(多渠道包的時間) = t (打每個包的耗時) * n (n為渠道數量), n 在大到一定程度後,ts 就會變得非常的大。這顯然是讓開發者十分痛苦的。
要解決這個問題,實際上需要攻破的是,META-INF 的完整性校驗機制。
目前業界比較常用的兩種方案是:
1. META-INF 下添加空文件不會破壞簽名(文件名為渠道號,若 Google 更改簽名機制,有可能失效)
2. apk 文件末尾寫入信息(本質是利用了 zip 文件可以添加 comment 數據結構的特點)
這兩種改動方案都不會導致 MANIFEST.MF 文件的改變,也就不需要再次打包,只需要簡單的讀改文件即可。為廣大開發者節省了上線前漫長的等待渠道包的時間。
4.3 資源混淆
資源混淆通過混淆資源路徑名以及資源文件名,比如:res/drawable/icon -> r/s/a
這樣不但可以減少安裝包的體積,一定程度上也可以提高破解難度。
該方案目前業內也有兩種實現,但是原理基本一致。入手點皆為resource.arsc。
第一種方案是修改我們剛剛提及的 aapt,使得在生成 resource.arsc 的過程中,就修改掉項目中資源的名稱,實現了資源的混淆。
第二種方案則是在打出 apk 包之後,讀入已生成的 resource.arsc 文件,進行混淆,改寫,同時修改掉 res 文件夾下的資源名稱,完成混淆。最後再重新打包得到混淆資源後的新 apk 文件。
既可以減小體積,又可以一定程度增加被 hack 的難度,如果還有沒有用起來的同學,可以嘗試用起來了。
4.4 熱補丁
由於移動平台的特性,移動應用版本的升級率並不高。這種時候,如果應用應用也可以像網頁那般動態部署,無疑可以帶來非常大的優勢,比如在線修復 bug,小版本的升級,臨時特性的上線等。目前業界已經有比較多的技術方案了,在這裡我們提及兩種比較有代表性的方案。
1. 通過 native hook 的方式,替換 class 中的方法實現完成熱補丁。
2. classloader 加載新 dex 覆蓋原有 class 完成替換的方案。
因為實際上每種方案都並非幾句話可以講解清楚,本文主要目的也只是做個大概介紹,若讀者有興趣可以谷歌后進一步研究,這裡就不具體展開講了。
實際上還有一些技術,比如使用 Anotation 自動生成代碼,buck exopackage 提高打包速度等。有興趣的話,可以自行查閱,相信可以學到不少知識的。
2016年9月5日 星期一
[轉] Android Process & Thread
Android Process & Thread
Native Service and Android Service
- Native Service:In every main() method of NativeService, which is called by init process through parseing init.rc, the globale object of ProcessState will be created by calling ProcessState::self(),and then startThreadPool and created main thread by calling IPCThreadPool.self()->joinThreadPool().
- Android Service:All Android Service is created by SystemServer and running in the same process which is system server.
New Process with main()
1 int main(int argc, char** argv) 2 { 3 sp<ProcessState> proc(ProcessState::self()); 4 sp<IServiceManager> sm = defaultServiceManager(); 5 LOGI("ServiceManager: %p", sm.get()); 6 AudioFlinger::instantiate(); 7 MediaPlayerService::instantiate(); 8 CameraService::instantiate(); 9 AudioPolicyService::instantiate(); 10 ProcessState::self()->startThreadPool(); 11 IPCThreadState::self()->joinThreadPool(); 12 }
Static: private/binder/Static.h
- Static.h
1 #include <utils/threads.h> 2 #include <binder/IBinder.h> 3 #include <binder/IMemory.h> 4 #include <binder/ProcessState.h> 5 #include <binder/IPermissionController.h> 6 #include <binder/IServiceManager.h> 7 namespace android { 8 // For ProcessState.cpp 9 extern Mutex gProcessMutex; 10 extern sp<ProcessState> gProcess; 11 // For ServiceManager.cpp 12 extern Mutex gDefaultServiceManagerLock; 13 extern sp<IServiceManager> gDefaultServiceManager; 14 extern sp<IPermissionController> gPermissionController; 15 }
- Static.cpp
1 #include <private/binder/Static.h> 2 #include <binder/IPCThreadState.h> 3 #include <utils/Log.h> 4 namespace android { 5 // ------------ ProcessState.cpp 6 Mutex gProcessMutex; 7 sp<ProcessState> gProcess; 8 class LibUtilsIPCtStatics 9 { 10 public: 11 LibUtilsIPCtStatics() 12 { 13 } 14 15 ~LibUtilsIPCtStatics() 16 { 17 IPCThreadState::shutdown(); 18 } 19 }; 20 static LibUtilsIPCtStatics gIPCStatics; 21 // ------------ ServiceManager.cpp 22 Mutex gDefaultServiceManagerLock; 23 sp<IServiceManager> gDefaultServiceManager; 24 sp<IPermissionController> gPermissionController; 25 }
- When we create new process and call main()..........,the gloable vairables will be created
Mutex gProcessMutex; sp<ProcessState> gProcess; Mutex gDefaultServiceManagerLock; sp<IServiceManager> gDefaultServiceManager; sp<IPermissionController> gPermissionController;
Threads
- android.threads wrap something related to thread using linux's pthread.h
threads.h
Threads.cpp
- The definition of Mutex in thread
Mutex
- The interface of thread
Thread
- Create a thread on linux as global function
1 inline bool createThread(thread_func_t f, void *a) { 2 return androidCreateThread(f, a) ? true : false; 3 } 4 int androidCreateThread(android_thread_func_t fn, void* arg) 5 { 6 return createThreadEtc(fn, arg); 7 } 8 9 // Create thread with lots of parameters 10 inline bool createThreadEtc(thread_func_t entryFunction, 11 void *userData, 12 const char* threadName = "android:unnamed_thread", 13 int32_t threadPriority = PRIORITY_DEFAULT, 14 size_t threadStackSize = 0, 15 thread_id_t *threadId = 0) 16 { 17 return androidCreateThreadEtc(entryFunction, userData, threadName, 18 threadPriority, threadStackSize, threadId) ? true : false; 19 } 20 static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc; 21 int androidCreateRawThreadEtc(android_thread_func_t entryFunction, 22 void *userData, 23 const char* threadName, 24 int32_t threadPriority, 25 size_t threadStackSize, 26 android_thread_id_t *threadId) 27 { 28 pthread_attr_t attr; 29 pthread_attr_init(&attr); 30 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 31 #ifdef HAVE_ANDROID_OS /** valgrind is rejecting RT-priority create reqs */ 32 if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) { 33 // We could avoid the trampoline if there was a way to get to the 34 // android_thread_id_t (pid) from pthread_t 35 thread_data_t* t = new thread_data_t; 36 t->priority = threadPriority; 37 t->threadName = threadName ? strdup(threadName) : NULL; 38 t->entryFunction = entryFunction; 39 t->userData = userData; 40 entryFunction = (android_thread_func_t)&thread_data_t::trampoline; 41 userData = t; 42 } 43 #endif 44 if (threadStackSize) { 45 pthread_attr_setstacksize(&attr, threadStackSize); 46 } 47 errno = 0; 48 pthread_t thread; 49 int result = pthread_create(&thread, &attr,android_pthread_entry)entryFunction, userData); 50 pthread_attr_destroy(&attr); 51 if (result != 0) { 52 LOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, errno=%d)\n" 53 "(android threadPriority=%d)", 54 entryFunction, result, errno, threadPriority); 55 return 0; 56 } 57 // Note that *threadID is directly available to the parent only, as it is 58 // assigned after the child starts. Use memory barrier / lock if the child 59 // or other threads also need access. 60 if (threadId != NULL) { 61 *threadId = (android_thread_id_t)thread; // XXX: this is not portable 62 } 63 return 1; 64 }
- Create a new thread on win32 as global function
1 int androidCreateRawThreadEtc(android_thread_func_t fn, 2 void *userData, 3 const char* threadName, 4 int32_t threadPriority, 5 size_t threadStackSize, 6 android_thread_id_t *threadId) 7 { 8 return doCreateThread( fn, userData, threadId); 9 } 10 static bool doCreateThread(android_thread_func_t fn, void* arg, android_thread_id_t *id) 11 { 12 HANDLE hThread; 13 struct threadDetails* pDetails = new threadDetails; // must be on heap 14 unsigned int thrdaddr; 15 pDetails->func = fn; 16 pDetails->arg = arg; 17 #if defined(HAVE__BEGINTHREADEX) 18 hThread = (HANDLE) _beginthreadex(NULL, 0, threadIntermediary, pDetails, 0,&thrdaddr); 19 if (hThread == 0) 20 #elif defined(HAVE_CREATETHREAD) 21 hThread = CreateThread(NULL, 0,LPTHREAD_START_ROUTINE) threadIntermediary, 22 (void*) pDetails, 0, (DWORD*) &thrdaddr); 23 if (hThread == NULL) 24 #endif 25 { 26 LOG(LOG_WARN, "thread", "WARNING: thread create failed\n"); 27 return false; 28 } 29 #if defined(HAVE_CREATETHREAD) 30 /** close the management handle */ 31 CloseHandle(hThread); 32 #endif 33 if (id != NULL) { 34 *id = (android_thread_id_t)thrdaddr; 35 } 36 return true; 37 }
- Create a thead in thread object
1 status_t Thread::run(const char* name, int32_t priority, size_t stack) 2 { 3 Mutex::Autolock _l(mLock); 4 if (mRunning) { 5 // thread already started 6 return INVALID_OPERATION; 7 }) 8 mStatus = NO_ERROR; 9 mExitPending = false; 10 mThread = thread_id_t(-1); 11 mHoldSelf = this; 12 mRunning = true; 13 bool res; 14 if (mCanCallJava) { 15 res = createThreadEtc(_threadLoop, 16 this, name, priority, stack, &mThread); 17 } else { 18 res = androidCreateRawThreadEtc(_threadLoop, 19 this, name, priority, stack, &mThread); 20 } 21 if (res == false) { 22 mStatus = UNKNOWN_ERROR; // something happened! 23 mRunning = false; 24 mThread = thread_id_t(-1); 25 mHoldSelf.clear(); // "this" may have gone away after this. 26 27 return UNKNOWN_ERROR; 28 } 29 return NO_ERROR; 30 } 31 int Thread::_threadLoop(void* user) 32 { 33 Thread* const self = static_cast<Thread*>(user); 34 sp<Thread> strong(self->mHoldSelf); 35 wp<Thread> weak(strong); 36 self->mHoldSelf.clear(); 37 #ifdef HAVE_ANDROID_OS 38 // this is very useful for debugging with gdb 39 self->mTid = gettid(); 40 #endif 41 bool first = true; 42 43 do { 44 bool result; 45 if (first) { 46 first = false; 47 self->mStatus = self->readyToRun(); 48 result = (self->mStatus == NO_ERROR); 49 50 if (result && !self->exitPending()) { 51 result = self->threadLoop(); 52 } 53 } else { 54 result = self->threadLoop(); 55 } 56 // establish a scope for mLock 57 { 58 Mutex::Autolock _l(self->mLock); 59 if (result == false || self->mExitPending) { 60 self->mExitPending = true; 61 self->mRunning = false; 62 // clear thread ID so that requestExitAndWait() does not exit if 63 // called by a new thread using the same thread ID as this one. 64 self->mThread = thread_id_t(-1); 65 // note that interested observers blocked in requestExitAndWait are 66 // awoken by broadcast, but blocked on mLock until break exits scope 67 self->mThreadExitedCondition.broadcast(); 68 break; 69 } 70 } 71 strong.clear(); 72 strong = weak.promote(); 73 } while(strong != 0); 74 return 0; 75 }
ProcessState
- ProcessState.h
ProcessState.h
- ProcessState.cpp
ProcessState.cpp
- Create a new object of ProcessState and assign to global sp<ProcessState> gProcess;
- Open the binder to get handler of binder
1 static int open_driver()//every process where the service run or client run will have its own default binder's handle 2 { 3 int fd = open("/dev/binder", O_RDWR); 4 if (fd >= 0) { 5 fcntl(fd, F_SETFD, FD_CLOEXEC); 6 int vers; 7 status_t result = ioctl(fd, BINDER_VERSION, &vers); 8 if (result == -1) { 9 LOGE("Binder ioctl to obtain version failed: %s", strerror(errno)); 10 close(fd); 11 fd = -1; 12 } 13 if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) { 14 LOGE("Binder driver protocol does not match user space protocol!"); 15 close(fd); 16 fd = -1; 17 } 18 size_t maxThreads = 15; 19 result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); 20 if (result == -1) { 21 LOGE("Binder ioctl to set max threads failed: %s", strerror(errno)); 22 } 23 } else { 24 LOGW("Opening '/dev/binder' failed: %s\n", strerror(errno)); 25 } 26 return fd; 27 }
PrecessState's Constructor
- PoolThread of ProcessState
- Start thread pool
1 void ProcessState::startThreadPool() 2 { 3 AutoMutex _l(mLock); 4 if (!mThreadPoolStarted) { 5 mThreadPoolStarted = true; 6spawnPooledThread(true); 7 } 8 } 9 void ProcessState::spawnPooledThread(bool isMain) 10 { 11 if (mThreadPoolStarted) { 12 int32_t s = android_atomic_add(1, &mThreadPoolSeq); 13 char buf[32]; 14 sprintf(buf, "Binder Thread #%d", s); 15 LOGV("Spawning new pooled thread, name=%s\n", buf); 16 sp<Thread> t = new PoolThread(isMain); 17 t->run(buf);//thread run by calling threadLoop
18 } 19 }
IPCThreadState
- IPCThreadState.h
IPCThreadState.h
- IPCThreadState.cpp
IPCThreadState.cpp
- IPCThread.Self()
1 static pthread_mutex_t gTLSMutex = PTHREAD_MUTEX_INITIALIZER; 2 static bool gHaveTLS = false; 3 static pthread_key_t gTLS = 0; 4 static bool gShutdown = false; 5 static bool gDisableBackgroundScheduling = false; 6 IPCThreadState* IPCThreadState::self() 7 { 8 if (gHaveTLS) { 9 restart: 10 const pthread_key_t k = gTLS; 11 IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k); 12 if (st) return st; 13 return new IPCThreadState; 14 } 15 if (gShutdown) return NULL; 16 pthread_mutex_lock(&gTLSMutex); 17 if (!gHaveTLS) { 18 if (pthread_key_create(&gTLS, threadDestructor) != 0) { 19 pthread_mutex_unlock(&gTLSMutex); 20 return NULL; 21 } 22 gHaveTLS = true; 23 } 24 pthread_mutex_unlock(&gTLSMutex); 25 goto restart; 26 } 27 IPCThreadState::IPCThreadState() 28 : mProcess(ProcessState::self()), 29 mMyThreadId(androidGetTid()), 30 mStrictModePolicy(0), 31 mLastTransactionBinderFlags(0) 32 { 33 pthread_setspecific(gTLS, this); 34 clearCaller(); 35 mIn.setDataCapacity(256); 36 mOut.setDataCapacity(256); 37 }
- joinThreadPool
1 void IPCThreadState::joinThreadPool(bool isMain) 2 { 3 LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(), getpid()); 4 mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER); 5 androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT); 6 status_t result; 7 do { 8 int32_t cmd; 9 if (mIn.dataPosition() >= mIn.dataSize()) { 10 size_t numPending = mPendingWeakDerefs.size(); 11 if (numPending > 0) { 12 for (size_t i = 0; i < numPending; i++) { 13 RefBase::weakref_type* refs = mPendingWeakDerefs[i]; 14 refs->decWeak(mProcess.get()); 15 } 16 mPendingWeakDerefs.clear(); 17 } 18 numPending = mPendingStrongDerefs.size(); 19 if (numPending > 0) { 20 for (size_t i = 0; i < numPending; i++) { 21 BBinder* obj = mPendingStrongDerefs[i]; 22 obj->decStrong(mProcess.get()); 23 } 24 mPendingStrongDerefs.clear(); 25 } 26 } 27 // now get the next command to be processed, waiting if necessary 28 result = talkWithDriver(); 29 if (result >= NO_ERROR) { 30 size_t IN = mIn.dataAvail(); 31 if (IN < sizeof(int32_t)) continue; 32 cmd = mIn.readInt32(); 33 IF_LOG_COMMANDS() { 34 alog << "Processing top-level Command: " 35 << getReturnString(cmd) << endl; 36 } 37 result = executeCommand(cmd); 38 } 39 androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT); 40 if(result == TIMED_OUT && !isMain) { 41 break; 42 } 43 } while (result != -ECONNREFUSED && result != -EBADF); 44 LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n", 45 (void*)pthread_self(), getpid(), (void*)result); 46 mOut.writeInt32(BC_EXIT_LOOPER); 47 talkWithDriver(false); 48 }
訂閱:
文章 (Atom)