10 AMS內(nèi)部原理
10.2.4 釋放內(nèi)存
1 activityIdleInternal() 主要進行了Acitivity的狀態(tài)整理與分類(Stop,Finish)
通知進程回收對應(yīng)內(nèi)存,mProcessToGC列表包含了所有需要回收內(nèi)存的進程,
將對應(yīng)狀態(tài)的activity放置入對應(yīng)的列表中mStoppingActivities,mFinishesActivities
調(diào)用destoryActivityLocked通知客戶進程銷毀Activity。
2 trimApplication 真正的進行內(nèi)存回收
刪除mRemovedProcesses中包含的進程,包括Crash,ANR,KILL_BACKGROUND_PROCESSED
updateOomAdjLocked()更新進程優(yōu)先級,確定合適adj值,越小越優(yōu)先保存,剩下交由Linux系統(tǒng)進程OOM_Killer負責
如果不支持OOM_Killer 則進行具體分析
11 從輸入設(shè)備中獲取消息
Linux層:
一:讀取系統(tǒng)輸入
InputReader線程調(diào)用輸入設(shè)備的驅(qū)動,讀取所有輸入消息
InputDispatcher線程從消息隊列中獲取消息,消息類型為Motion的直接分發(fā)到客戶窗口,為KeyEvent的先派發(fā)到WMS,再有WMS決定是否繼續(xù)分發(fā)。
二:窗口創(chuàng)建傳遞
WMS里面的Session.AddWindows時 會創(chuàng)建一個InputMonitor,保存窗口所有信息。利用InputManager類將信息傳遞到InputDispatcher線程里,InputManger同事會調(diào)用JNI代碼,將其傳遞到NativeInputManager中,InputDispatcher得到用戶消息后,會根據(jù)NativeInputManager判斷活動窗口,把消息傳活動窗口。
三:管道的建立
當WMS 創(chuàng)建窗口時,也會創(chuàng)建兩個管道,一個用于InpuDispatcher 傳遞消息,另一個負責向InputDispatcher報告消息處理結(jié)果。
消息的處理是線性的,如果上個消息處理結(jié)果未告知InputDispatcher,則不進行分發(fā)下一個消息。
12 屏幕繪制基礎(chǔ)
1 屏幕繪制架構(gòu)
2 Java端繪制流程
############1 WindowManagerSerivce在reLayoutWindows時,會調(diào)用createSurfaceControl
############2 createSurfaceControl里面,會new WindowSurfaceController
############3 WindowSurfaceController 調(diào)用 makeSurface
final SurfaceControl.Builder b = win.makeSurface()
.setParent(win.getSurfaceControl())
.setName(name)
.setSize(w, h)
.setFormat(format)
.setFlags(flags)
.setMetadata(windowType, ownerUid);
mSurfaceControl = b.build(); //創(chuàng)建一個SurfaceControl
############4 SurfaceControl構(gòu)造函數(shù)會創(chuàng)建一個新的Surface,調(diào)用nativeCreate函數(shù),利用SurfaceComposerClinet->createSurface創(chuàng)建真正的Surface
public SurfaceControl build() {
if (mWidth <= 0 || mHeight <= 0) {
throw new IllegalArgumentException(
"width and height must be set");
}
return new SurfaceControl(mSession, mName, mWidth, mHeight, mFormat,
mFlags, mParent, mWindowType, mOwnerUid);
}
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
SurfaceControl parent, int windowType, int ownerUid)
throws OutOfResourcesException, IllegalArgumentException {
/**
檢測代碼
**/
mName = name;
mWidth = w;
mHeight = h;
mNativeObject = nativeCreate(session, name, w, h, format, flags,
parent != null ? parent.mNativeObject : 0, windowType, ownerUid);
if (mNativeObject == 0) {
throw new OutOfResourcesException(
"Couldn't allocate SurfaceControl native object");
}
mCloseGuard.open("release");
}
android_view_surfacecontrol.cpp里面的nativeCreate
利用SurfaceComposeClient的createSurface函數(shù)創(chuàng)建Surface。
static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
jstring nameStr, jint w, jint h, jint format, jint flags, jlong parentObject,
jint windowType, jint ownerUid) {
ScopedUtfChars name(env, nameStr);
sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
SurfaceControl *parent = reinterpret_cast<SurfaceControl*>(parentObject);
/**
關(guān)鍵代碼,client的類型為surfaceComposeClient
SurfaceComposeClient正是navtie層面上SurfaceFlinger服務(wù)的客戶端對象**/
sp<SurfaceControl> surface = client->createSurface(
String8(name.c_str()), w, h, format, flags, parent, windowType, ownerUid);
if (surface == NULL) {
jniThrowException(env, OutOfResourcesException, NULL);
return 0;
}
surface->incStrong((void *)nativeCreate);
return reinterpret_cast<jlong>(surface.get());
}
############5 創(chuàng)建好Surface以后,利用lockCanvas獲取Canvas對象,
5.1 將navtiveObject轉(zhuǎn)為surface對象
5.2 獲取屏幕緩沖區(qū),ANativeWindow_Buffer 即為屏幕緩沖區(qū)對象
5.3 創(chuàng)建SkImageInfo,構(gòu)建SkBitmap 對象,
5.4 將bitmap對象設(shè)置到相機,Canvas* 才是底層真正的繪制功能類的對象,Java端僅僅是對其封裝
5.5 返回Surface對象
在android_view_surface.cpp里面
static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
// 5.1
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
//省略檢測代碼
//5.2
ANativeWindow_Buffer outBuffer;
status_t err = surface->lock(&outBuffer, dirtyRectPtr);
if (err < 0) {
const char* const exception = (err == NO_MEMORY) ?
OutOfResourcesException :
"java/lang/IllegalArgumentException";
jniThrowException(env, exception, NULL);
return 0;
}
//5.3
SkImageInfo info = SkImageInfo::Make(outBuffer.width, outBuffer.height,
convertPixelFormat(outBuffer.format),
outBuffer.format == PIXEL_FORMAT_RGBX_8888
? kOpaque_SkAlphaType : kPremul_SkAlphaType,
GraphicsJNI::defaultColorSpace());
SkBitmap bitmap;
ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
bitmap.setInfo(info, bpr);
if (outBuffer.width > 0 && outBuffer.height > 0) {
bitmap.setPixels(outBuffer.bits);
} else {
// be safe with an empty bitmap.
bitmap.setPixels(NULL);
}
//5.4
Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
nativeCanvas->setBitmap(bitmap);
if (dirtyRectPtr) {
nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect);
}
// Create another reference to the surface and return it. This reference
// should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
// because the latter could be replaced while the surface is locked.
sp<Surface> lockedSurface(surface);
lockedSurface->incStrong(&sRefBaseOwner);
//5.5返回Surface
return (jlong) lockedSurface.get();
}
13 View繪制原理
InputReader線程從輸入設(shè)備不斷讀取消息,然后用InputDispathcer分發(fā)消息,不多講,WMS能獲取到消息,不多講。詳見12
消息類型分為兩類,一類是按鍵消息,Home,Menu,Back等,需要經(jīng)過WMS的默認處理,才會分發(fā)。另外一類就是大眾所熟知的TouchEvent,不需要經(jīng)過WMS的默認處理,直接分發(fā),TouchEvent一般只有一個Down消息,緊接一堆MOVE消息,然后是UP消息,這是一個事件序列,如果想攔截Touch消息,就必須攔截Down消息才可以繼續(xù)處理一個事件序列接下來的消息。
很好理解,事件序列由DOWN+MOVE....MOVE+UP格式組成,要處理則都必須處理DOWN。
TouchEvent分發(fā)時,先判斷是否為應(yīng)用窗口,如果是,則DecorView獲取到消息,調(diào)用Activity的dispatchTouchEvent,如果非應(yīng)用窗口,直接調(diào)用ViewGroup的DispatchTouchEvent。
其中Activity的事件分發(fā)眾所周知了,大概就是從外向內(nèi)逐漸傳遞,經(jīng)過Activity->PhoneWindow->DecorView->目標View,如果View本身不處理的話,事件又會從View傳遞回Acitvity,等待處理。
類似于領(lǐng)導安排任務(wù),層層安排下去,你最后處理不了,把任務(wù)返回,終有一個層級的人要處理
**由外向內(nèi)傳遞,由內(nèi)向外返回
13.5 View內(nèi)部處理邏輯
##########1 調(diào)用onFilterTouchEventForSecurity處理窗口處于模糊顯示狀態(tài)下的消息。
##########2 調(diào)用監(jiān)聽者的onTouch,如果沒有 進行到3
##########3 調(diào)用自己的onTouchEvent.
############3.1 判斷View是否disable,如果是,返回true(Disable視圖不需要響應(yīng)觸摸消息)
############3.2 如果有TouchDelegate,則使用TouchDelegate的onTouchEvent。
TouchDelegate源碼里說的是,如果想要view的響應(yīng)邊界,大于實際顯示邊界,則使用這個。
############3.3 判斷是否可點擊,不可點擊返回false
3.3.1 處理ACTION_DOWN,主要發(fā)送異步消息,檢測是否長按
3.3.2 處理ACTION_MOVE,不斷檢測是否移除VIEW外,如果是,取消所有檢測,將按鈕Press狀態(tài)設(shè)置為false.
3.3.3 處理ACTION_UP,判斷發(fā)生時間段。
3.3.3.1 Pre-Pressed時間段,將prepressed 設(shè)置為true
3.3.3.2 Press時間段,無論是上個階段還是這個階段,都應(yīng)該將VIEW獲取焦點,更新按鈕狀態(tài)
3.3.3.3 Press之后,即長按,什么都不做。
3.3.3.4 判斷focusTaken,一般情況下視圖在Touch模式下不能獲取焦點,所以會執(zhí)行post(performClick),不直接調(diào)用performClick是因為可以讓其他人看到視圖的狀態(tài)更新。
3.3.3.5 分別處理tap跟press動作,如果是Press,發(fā)送延時異步消息。
tap同理
3.3.3.6 關(guān)閉tap檢測
##########4 處理Action_Cancel消息,還原按鈕狀態(tài),取消所有監(jiān)測,清除PFLAG3_FINGER_DOWN標志。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//3.1
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//3.2
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//3.3 關(guān)鍵處理代碼
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
//3.3.3.1
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
//3.3.3.2
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
//3.3.3.3
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
//3.3.3.4
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
//3.3.3.5
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
// 3.3.3.6
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
13.6 View樹重新遍歷的時機
一般來說有三種情況,會引起View樹重新遍歷,
一 View樹里面View的狀態(tài)改變(如pressed)
二 添加或刪除了子樹
三 View的大小及可見性發(fā)送變化。
##########13.6.1 setVisiablity()
setFlags()->mBGDrawable.setVisible(),
判斷VISIBLE狀態(tài)是否有點笨,如果到Gone會引起requestLayout()->invaildate(),到Invisiable則只會引起invaildate()
Notice:View中的requestLayout跟ViewRootImpl中的大不一樣
##########13.6.2 setEnable()
不會導致重新布局,僅僅重繪調(diào)用invaildate()
##########13.6.3 setSelect()
##########13.6.4 invaildate()
基本作用: 請求重繪
基本流程: 先繪制根視圖,再繪制子視圖,但并不是所有View樹都會被重繪,根據(jù)mPrivateFlags的DRAWN標志位來判斷,計算出哪個區(qū)域需要重繪,繼而存放到mDirty變量中,然后直接重繪所有mDirty中的視圖。
ViewRootImpl中的invaildate總結(jié)
##########13.6.5 requestFocus() 視圖到底能不能獲取焦點
前期檢測代碼不多做解釋,主要是利用了handlerFocusGainInternal()進行具體的焦點獲取操作
1 獲得焦點直接返回
2 如果沒有焦點,設(shè)置標志,意味獲取到焦點
3 尋找焦點視圖
4 調(diào)用mParent.requestChildFocus,第一個this代表自己,第二個this代表想要獲取焦點的視圖。假設(shè)C包含B,B包含A,在B中調(diào)用,實際上是C.requestChildFocus(B,A)。為requestFocus的核心,最終遞歸調(diào)用到ViewRootImpl里面requestChildFocus
void handleFocusGainInternal(@FocusRealDirection int direction, Rect previouslyFocusedRect) {
if (DBG) {
System.out.println(this + " requestFocus()");
}
//1
if ((mPrivateFlags & PFLAG_FOCUSED) == 0) {
//2
mPrivateFlags |= PFLAG_FOCUSED;
//3
View oldFocus = (mAttachInfo != null) ? getRootView().findFocus() : null;
if (mParent != null) {
//4
mParent.requestChildFocus(this, this);
updateFocusedInCluster(oldFocus, direction);
}
if (mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(oldFocus, this);
}
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
}
}
########## 13.6.6 requestLayout()
13.7 performTraversals()
13.8 Measure() 測量
其本質(zhì)是 將視圖布局使用時候的相對值(WRAP_CONTENT,MATCH_PARENT),轉(zhuǎn)為具體值(手機上實際顯示的大小should have),
13.8.1 內(nèi)部設(shè)計思路
多次重復測量,主要根據(jù)Parent剩余空間 + Child意圖空間 進行具體測算。
parentMeasure代表父視圖剩余空間
lp.height 代表子視圖 layout_height的時設(shè)定的值
childMeasureSpec代表的是最終的測量規(guī)格
為什么是最終測量規(guī)格而不是測量值,是因為 parentMeasure代表父視圖希望的大小,lp.height代表程序員希望的大小,而XXView自身,則也可以onMeasure里面調(diào)用setMeasureDimension設(shè)置最終的大小。
13.9 Layout()
13.10 Draw()
會將View對象繪制到屏幕上,如果是個ViewGroup對象,則會遞歸繪制其所有child.
主要繪制四個方面的內(nèi)容:
1 View的背景,可以是color,drawable,image等
2 View的內(nèi)容,TextView就是文字(帶大小顏色的)
3 View的邊框,漸變效果,Shader等
4 滾動條
13.10.5 ViewGroup中DispatchDraw()
為子視圖提供合適的畫布(Canvas)
13.10.6 ViewGroup類中drawChild()
為子視圖分配合適的Canvas剪切區(qū),取決于child的布局大小。
13.10.11 動畫的繪制
屬性動畫與補間動畫的區(qū)別 主要是將基本動畫的 平移,縮放,旋轉(zhuǎn),透明度變換 由View為對象的動畫,修改成以屬性為主要對象的動畫,例如View的背景,形狀,顏色變換。
且 屬性動畫會真正改變View的屬性(如果是平移,則真正將view位移到目標點),而補間動畫則不會改變View的屬性,只是描繪一個虛假的View在目標點(點擊目標View 不會有任何響應(yīng)事件)
API11之后,Google推薦使用屬性動畫。
估值器是屬性動畫里最重要的控制器,可以控制屬性的變化,自定義估值器需要利用屬性的get,set方法,然后分段將值 賦予屬性,從而引起對象的變化。
14 WMS原理
14.1 WMS的結(jié)構(gòu)
Wms功能模塊與其他模塊的交互,主要包括與AMS模塊及應(yīng)用程序客戶端的接口
每個窗口對應(yīng)一個WindowState,負責記錄窗口的全部屬性(例如大小,層級)
WindowToken描述窗口對應(yīng)的Token屬性,但是子窗口與父窗口共用同一個WindowToken,如果窗口屬于Activity創(chuàng)建的,則還具有AppWindowToken屬性。
即WindowState最多(每個窗口必有一個),WindowToken 較少(父子窗口共用一個),AppWindowToken 最少(Activity創(chuàng)建的才有),
14.2 InputMonitor
InputWindow只保存了為尋找焦點窗口所需要的信息,而WindowState則保存窗口的全部信息。
14.3 創(chuàng)建窗口的時機
Android8.0 改為ViewRootImpl.setView->session.addToDisplay->wms.addWindow
刪除窗口的時機
removeWindowInnerLock 流程
14.4 計算窗口的大小
mPolicy.beginLayoutLw()計算窗口大小。
屏幕大小減去狀態(tài)欄大小即為有效區(qū)大小。
mDockxxx系列變量為有效區(qū)大小
mContentXXX = mCurXXX 為黑色矩形區(qū)域大小
14.5 切換窗口
窗口的切換就是從將輸入焦點從老窗口移動到新窗口,同時伴隨一定的Activity切換,所以與Activity有一定的同步關(guān)系。
WMS的主要功能有兩個,一是將所有窗口保持一定的層級關(guān)系,便于SurfaceFlinger據(jù)此繪制屏幕。二是將窗口信息發(fā)給InputManager,便于InputDispatcher線程能夠?qū)⑤斎胂⑴砂l(fā)給和屏幕上顯示一致的窗口。
**
Activity的管理主要是靠三個類,一: AMS 負責啟動等。二:ActivityRecord用來表示一個Activity,保存各種Activity的屬性(kyrie感覺有點類似WindowState跟Window的關(guān)系)。三:ActivityStack描述Task,所有和Task相關(guān)的數(shù)據(jù)以及控制都有ActivityStack來實現(xiàn)
**
Wms和InputManager之間的交互流程:
Wms保證內(nèi)部窗口的正確順序,再向InputManager傳遞窗口信息(即InputManager類中updateInputWindowsLw()的執(zhí)行過程),
否則將會導致窗口顯示錯誤,接受輸入消息的窗口不知道去哪了。
從A到B的切換,用戶選擇一個程序啟動。
從B回到A的流程。
窗口每次的變化 relayout都會導致surface的重建或銷毀。
ConfigChange的三種情況
1 Acitivity的切換(從Portrait到landscape)
2 wms的addWindow,removeWindow,relayoutWindow等其他操作
3 用戶主動旋轉(zhuǎn)屏幕
15 資源訪問機制
styleable 實際上就是只是attr集合,便于開發(fā)所以定制出這套規(guī)則。
styleable基本上定義的都是視圖屬性,更應(yīng)該叫做viewParams;
15.5獲取Resources的過程(app換膚)
8.0中使用的是ResourceManager.getInstance.createRessource -> getOrCreateResource->getOrCreateActivityResourceStructLocked
其中mActivityResourceRefence是WeakHashMap<IBinder,ResourceManager>類型,利用ActivityToken作為key,來緩存所有的resource
15.5.2 通過PackageManager獲取(訪問其他apk的資源)
8.0中并未找到具體實現(xiàn)代碼,有空自己去找。
15.6 FrameWork資源
ZygoteInit.java中Main()->preLoad->preLoadRes() 開始加載存放于framework/base/core/res/res目錄下的系統(tǒng)資源,且存放在com.android.Internal.R.java中,而不是android.R.java,而android.R.java可以視作為internal.R.java的子集,這也是區(qū)分私有資源與公有資源的關(guān)鍵。
AXMLPrinter2.jar 可以將AndroidManifest.xml的二進制文件反編譯成正常代碼文件,實際上是可以將任何二進制的xml文件都反編譯成為正常XML。
15.6.3 實現(xiàn)手機系統(tǒng)主題切換
兩種思路:
一:利用C++實現(xiàn),在Java層提供一個JNI接口,接受新的資源文件位置假設(shè)為new-framework-res.apk,然后在AssertManager類初始化時,利用傳入new-framework-res.apk的路徑替換原來的路徑即可
此方法注意事項:
1 zygote需要重新preload系統(tǒng)資源
2 需要將Resource里面的非靜態(tài)緩存全部清除
3 在新的主題包中,需要使用原始的public.xml文件即framework-res.apk里的public.xml,來保持資源id的一致性
4 新資源包的資源名稱跟數(shù)量必須與原始的相同(直接copy一份修改原始的即可)
二:Java層實現(xiàn),修改Resource源碼,當讀取系統(tǒng)資源時(系統(tǒng)資源開頭為0x01,),則利用PackageManager讀取主題包的資源,從而達到替換效果。
注意事項:
1 必須區(qū)分先讀取主題資源還是系統(tǒng)資源,如果沒找到主題資源則用原系統(tǒng)資源代替。
2 必須清空Resource的靜態(tài)緩存
3 資源id的一致性,或者使用原始id獲取name,再load值。
16 程序包管理
利用兩個xml文件保存相關(guān)的包信息
1 /system/etc/permission.xml 定義系統(tǒng)所具有的feature(并非權(quán)限)
2 /data/app/packages.xml 獲取程序名稱,路徑,權(quán)限,(此文件類似注冊表)
pms通過解析此文件建立龐大的信息樹。
16.4 應(yīng)用程序的安裝與卸載
程序的安裝可以分為兩個過程,第一個是將原始apk復制到對應(yīng)目錄,第二個過程是為apk創(chuàng)建對應(yīng)的數(shù)據(jù)目錄及提取dex。
scanPackageLi 完成了第二個過程。
scanPackageLi(file)函數(shù)分兩步執(zhí)行
1 調(diào)用PackageParser 解析安裝包,將結(jié)果保存到變量pkg中
2 調(diào)用scanPageLi(package)將上一部的結(jié)果轉(zhuǎn)換到PMS的內(nèi)部中
最后會將四大組件以及Broadcast等集中保存起來。
16.5 Intent匹配機制
17 輸入法框架
IMM包含兩個binder,一個是editText對應(yīng)的binder,便于將按鍵消息傳遞給EditText,使其顯示。另外一個是InputMethodManager自己的binder,將其傳遞給IMMS,然后IMMF會將具體輸入法服務(wù)交給此binder,便于InputMethodManager直接訪問輸入法服務(wù)進程
具體的輸入法進程中也包含兩個binder,其一是輸入法服務(wù)的的binder,IMMS通過這個對象控制輸入法的狀態(tài),顯示與隱藏,其二是供客戶端調(diào)用的binder,傳遞事件使用。
17.3 輸入法主要操作過程
SessionBinder指的是IMS中為客戶端創(chuàng)建的Binder,便于通信。
Connection Binder指的是客戶端自己創(chuàng)建的binder,便于向客戶端寫消息
InputMethod Binder是IMS對應(yīng)的Binder
17.3.4 顯示輸入法
輸入法的顯示實際有三個:
1 當前窗口變成交互窗口,并且調(diào)用到IMMS的windowGainedFoucs
2 程序員調(diào)用showSoftInput()
3 點擊可編輯框,系統(tǒng)自動彈出
17.4 輸入法窗口內(nèi)部的顯示過程
18 Android 編譯系統(tǒng) 如何將android源代碼編譯成可執(zhí)行程序.
18.1 Android系統(tǒng)編譯
18.1.1 makefile基本語法:
目標(target):條件(condition)
(tab鍵) 命令
.PHONY 用于聲明一個目標,第一個.PHONY為默認目標(直接make生成)
hello:hello.c
gcc hello.c -o hello
.Phony: he
he:hello.c
gcc hello.c -o hello.bin
18.3.4 生成編譯規(guī)則(java利用javac,c/c++利用gcc/g++)
-
提取環(huán)境變量:即子項目將自己所需要的變量賦值給編譯中樞。
環(huán)境變量
在裝在assert跟res時,packages.mk還會從PRODUCT_PACKAGE_OVERLAY跟DEVICE_PACKAGE_OVERLAY里面加載。各大廠商可以據(jù)此,拓展自己的資源。
- 包含特定的基礎(chǔ)腳本文件。對于BUILD_PACKAGES而言,就是java.mk,其中包含了definenation.mk 真正定義了用何種編譯器(javac,gcc)
- 增加額外的目標條件。
- 增加R.java,幾乎所有項目都引用資源文件,資源文件又被aapt2編譯為R.java,因此需要先添加此文件。注意,這里是系統(tǒng)的android.R。
- 增加JNI。如上
- 增加對簽名文件的依賴,因為Java項目最后都會被簽名
- 增加對AAPT2及ZIPALIGN的條件依賴,aapt2用于apk打包,zipalign用于壓縮打包文件,優(yōu)化內(nèi)部存儲。
18.4.3 增加一個product
- mkdir -p koller/product
- 新建AndroidProducts.mk
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/customDefine.mk
*新建customDefine.mk
PRODUCT_PACKAGES := \
helloMake
PRODUCT_NAME := full_customDefine
PRODUCT_DEVICE := customDefine
PRODUCT_BRAND := Android
*新建一個BoardConfig.mk文件,即使為空也可以。Android編譯系統(tǒng)必須
TARGET_CPU_ABI := armeabi-v7a
TARGET_NO_BOOTLOADER := true
TARGET_NO_KERNEL := true
#no real audio,so use generic
BOARD_USER_GENERIC_AUDIO := true
#no hardware camera
USE_CAMERA_STUB := true
- 修改Android根目錄下的Android.mk
include build/core/main.mk
$(call dump-product,device/koller/product/customDefine.mk)
18.5 如何增加一個項目
其意思是,在android系統(tǒng)編譯的過程中,如何將我們想要的目標程序,添加進去,例如出廠自帶的apk,如果我想多增加一個美顏相機apk怎么辦。
如果我想增加一個c/c++工具,例如gdb(哈哈,有點太夸張了)怎么辦?
18.5.2 添加一個C項目 helloMake
- 選擇根目錄下子目錄,最好是external
- 寫代碼,hello.h,main.c
/*This is hello.h*/
#include <stdio.h>
#include <stdlib.h>
#ifndef _HELLO_H
#define _HELLO_H
void makePrint(Char* str)
{
println("%s",str)
}
#endif
#include "hello.h"
int main(){
makePrint("hello~~~make")
return 0;
}
- 添加Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
main.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)
LOCLA_MODULE := helloMake
LOCLA_MODULE_TAGS := eng
include $(BUILD_EXECUTABLE)
要點:1 C/C++文件必須手動添加.
2 include 自定義頭文件,需要在mk中指定具體路徑。系統(tǒng)C函數(shù)不需要制定。因為其已經(jīng)被定義在config.mk的SRC_HEARDERS中
3 將其添加到customDefine.mk里的PRODUCT_PACKAGES變量中。
4 項目TAG值為eng,需要編譯時指定eng類才會被打包進system.img。即make PRODUCT-full_crespo-eng
- make PRODUCT-full_crespo-eng 編譯整個android工程,得到system.img,輸入設(shè)備,啟動adb shell,輸入helloMake,輸出hello~~~make
18.5.3 添加一個APK項目
1 源碼方式添加,假設(shè)我們已經(jīng)有了一個Hello的Android項目。
- 放入/vendor/yourCompany/moduleName/apps目錄下
- 刪除其build目錄
- 新建Android.mk文件,將其放入到Hello目錄下
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_PACKAGE_NAME := Hello
LOCAL_SDK_VERSION := current
LOCLA_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform
LOCAL_PROGURAD_FLAG_FILES := proguard-rules.pro
include $(BUILD_PACKAGE)
2 apk方式添加
- 在customDefine.mk里面增加如下代碼,即源路徑至目標路徑,因為是系統(tǒng)級別的app,所以放在/system/app目錄下
PRODUCT_COPY_FILES := \
vendor/koller/product/helloapk/helloapk.apk:/system/app
18.6 APK編譯過程
18.6.1 總體編譯過程
18.7 framewrok編譯過程
指的是android源碼根目錄下framework文件夾的編譯,不是整個android項目。
18.8.1 aapt編譯資源文件
aapt2可以將res目錄下的資源文件編譯成為flat文件,如果是res目錄,則會編譯為zip壓縮包。
18.10.2
利用Javadoc工具可以將新添加的java文件添加到公開sdk的android.jar中,如果想要私有,只需要添加@hide標簽即可,因為Java文件僅生成一次,需調(diào)用make update-api
19 編譯自己的ROM
ROM是CPU引腳上固定的一段嵌入式程序,是CPU出廠就確定的引導程序,大小不超過1kb,也就是BootLoader。又稱onChip引導程序。
- 電源鍵導致電流變化,導致CPU上電引腳的變化(硬件邏輯),從而運行BootLoader,負責查找并加載次引導加載程序(第二階段)
- SecondBootLoader 加載 Linux 內(nèi)核和可選的初始 RAM 磁盤。
- 內(nèi)核啟動后,就會啟動熟知的Init進程,并讀取init.rc配置
- Zygote啟動
- SystemServer啟動。
以上配圖大多出自《Android內(nèi)核剖析》柯元旦。