Android 內(nèi)核剖析

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繪制原理

通用圖形系統(tǒng)的消息處理過程

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ā)送變化。

performTraversals.png

##########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é)


ViewRootImpl.png

##########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()


requestLayout.png

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è)置最終的大小。


具體對應(yīng)表

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)用程序客戶端的接口

WMS的結(jié)構(gòu)

每個窗口對應(yīng)一個WindowState,負責記錄窗口的全部屬性(例如大小,層級)
WindowToken描述窗口對應(yīng)的Token屬性,但是子窗口與父窗口共用同一個WindowToken,如果窗口屬于Activity創(chuàng)建的,則還具有AppWindowToken屬性。
即WindowState最多(每個窗口必有一個),WindowToken 較少(父子窗口共用一個),AppWindowToken 最少(Activity創(chuàng)建的才有),

14.2 InputMonitor

InputMonitor

InputWindow只保存了為尋找焦點窗口所需要的信息,而WindowState則保存窗口的全部信息。

14.3 創(chuàng)建窗口的時機

主要調(diào)用過程

Android8.0 改為ViewRootImpl.setView->session.addToDisplay->wms.addWindow

添加窗口的時機

刪除窗口的時機


刪除窗口的時機
刪除窗口的過程

removeWindowInnerLock 流程


removeWindowInnerLock 流程

14.4 計算窗口的大小

mPolicy.beginLayoutLw()計算窗口大小。
屏幕大小減去狀態(tài)欄大小即為有效區(qū)大小。
mDockxxx系列變量為有效區(qū)大小
mContentXXX = mCurXXX 為黑色矩形區(qū)域大小

調(diào)用過程

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的流程。


image.png

窗口每次的變化 relayout都會導致surface的重建或銷毀。

ConfigChange的三種情況


image.png

1 Acitivity的切換(從Portrait到landscape)
2 wms的addWindow,removeWindow,relayoutWindow等其他操作
3 用戶主動旋轉(zhuǎn)屏幕

15 資源訪問機制

styleable 實際上就是只是attr集合,便于開發(fā)所以定制出這套規(guī)則。


Attr,Style,Styleable的關(guān)系

styleable基本上定義的都是視圖屬性,更應(yīng)該叫做viewParams;

15.5獲取Resources的過程(app換膚)

獲取Resource流程

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 程序包管理

包管理結(jié)構(gòu)

利用兩個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等集中保存起來。

功能類
應(yīng)用程序安裝過程
應(yīng)用程序的卸載過程

16.5 Intent匹配機制

Intent匹配的重要變量
image.png

17 輸入法框架

輸入法結(jié)構(gòu)

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

輸入法的啟動流程
處理過程
startInputInner函數(shù)的過程

17.3.4 顯示輸入法

輸入法的顯示實際有三個:
1 當前窗口變成交互窗口,并且調(diào)用到IMMS的windowGainedFoucs
2 程序員調(diào)用showSoftInput()
3 點擊可編輯框,系統(tǒng)自動彈出

如圖

17.4 輸入法窗口內(nèi)部的顯示過程

showSoftInput內(nèi)部調(diào)用過程
showWindow執(zhí)行過程

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
Android編譯系統(tǒng)功能模塊圖
18.3.4 生成編譯規(guī)則(java利用javac,c/c++利用gcc/g++)
生成編譯規(guī)則的三步驟
  • 提取環(huán)境變量:即子項目將自己所需要的變量賦值給編譯中樞。


    環(huán)境變量

    在裝在assert跟res時,packages.mk還會從PRODUCT_PACKAGE_OVERLAY跟DEVICE_PACKAGE_OVERLAY里面加載。各大廠商可以據(jù)此,拓展自己的資源。

  • 包含特定的基礎(chǔ)腳本文件。對于BUILD_PACKAGES而言,就是java.mk,其中包含了definenation.mk 真正定義了用何種編譯器(javac,gcc)
  • 增加額外的目標條件。
    1. 增加R.java,幾乎所有項目都引用資源文件,資源文件又被aapt2編譯為R.java,因此需要先添加此文件。注意,這里是系統(tǒng)的android.R。
    2. 增加JNI。如上
    3. 增加對簽名文件的依賴,因為Java項目最后都會被簽名
    4. 增加對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(哈哈,有點太夸張了)怎么辦?


編譯系統(tǒng)對應(yīng)的類型
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項目。


具體流程.png
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系統(tǒng)內(nèi)存分區(qū)

以上配圖大多出自《Android內(nèi)核剖析》柯元旦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容