Android 内核剖析

10 AMS内部原理

10.2.4 释放内存

1 activityIdleInternal() 主要进行了Acitivity的状态整理与分类(Stop,Finish)
通知进程回收对应内存,mProcessToGC列表包含了所有需要回收内存的进程,
将对应状态的activity放置入对应的列表中mStoppingActivities,mFinishesActivities
调用destoryActivityLocked通知客户进程销毁Activity。

2 trimApplication 真正的进行内存回收
删除mRemovedProcesses中包含的进程,包括Crash,ANR,KILL_BACKGROUND_PROCESSED
updateOomAdjLocked()更新进程优先级,确定合适adj值,越小越优先保存,剩下交由Linux系统进程OOM_Killer负责
如果不支持OOM_Killer 则进行具体分析

11 从输入设备中获取消息

流程图

Linux层:
一:读取系统输入
InputReader线程调用输入设备的驱动,读取所有输入消息
InputDispatcher线程从消息队列中获取消息,消息类型为Motion的直接分发到客户窗口,为KeyEvent的先派发到WMS,再有WMS决定是否继续分发。

二:窗口创建传递
WMS里面的Session.AddWindows时 会创建一个InputMonitor,保存窗口所有信息。利用InputManager类将信息传递到InputDispatcher线程里,InputManger同事会调用JNI代码,将其传递到NativeInputManager中,InputDispatcher得到用户消息后,会根据NativeInputManager判断活动窗口,把消息传活动窗口。

三:管道的建立
当WMS 创建窗口时,也会创建两个管道,一个用于InpuDispatcher 传递消息,另一个负责向InputDispatcher报告消息处理结果。
消息的处理是线性的,如果上个消息处理结果未告知InputDispatcher,则不进行分发下一个消息。

12 屏幕绘制基础

1 屏幕绘制架构
绘制框架
2 Java端绘制流程

############1 WindowManagerSerivce在reLayoutWindows时,会调用createSurfaceControl
############2 createSurfaceControl里面,会new WindowSurfaceController
############3 WindowSurfaceController 调用 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(); //创建一个SurfaceControl

############4 SurfaceControl构造函数会创建一个新的Surface,调用nativeCreate函数,利用SurfaceComposerClinet->createSurface创建真正的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函数创建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);
    /**
    关键代码,client的类型为surfaceComposeClient
    SurfaceComposeClient正是navtie层面上SurfaceFlinger服务的客户端对象**/
    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 创建好Surface以后,利用lockCanvas获取Canvas对象,
5.1 将navtiveObject转为surface对象
5.2 获取屏幕缓冲区,ANativeWindow_Buffer 即为屏幕缓冲区对象
5.3 创建SkImageInfo,构建SkBitmap 对象,
5.4 将bitmap对象设置到相机,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线程从输入设备不断读取消息,然后用InputDispathcer分发消息,不多讲,WMS能获取到消息,不多讲。详见12

消息类型分为两类,一类是按键消息,Home,Menu,Back等,需要经过WMS的默认处理,才会分发。另外一类就是大众所熟知的TouchEvent,不需要经过WMS的默认处理,直接分发,TouchEvent一般只有一个Down消息,紧接一堆MOVE消息,然后是UP消息,这是一个事件序列,如果想拦截Touch消息,就必须拦截Down消息才可以继续处理一个事件序列接下来的消息。
很好理解,事件序列由DOWN+MOVE....MOVE+UP格式组成,要处理则都必须处理DOWN。

TouchEvent分发时,先判断是否为应用窗口,如果是,则DecorView获取到消息,调用Activity的dispatchTouchEvent,如果非应用窗口,直接调用ViewGroup的DispatchTouchEvent。
其中Activity的事件分发众所周知了,大概就是从外向内逐渐传递,经过Activity->PhoneWindow->DecorView->目标View,如果View本身不处理的话,事件又会从View传递回Acitvity,等待处理。
类似于领导安排任务,层层安排下去,你最后处理不了,把任务返回,终有一个层级的人要处理

**由外向内传递,由内向外返回

13.5 View内部处理逻辑

##########1 调用onFilterTouchEventForSecurity处理窗口处于模糊显示状态下的消息。
##########2 调用监听者的onTouch,如果没有 进行到3
##########3 调用自己的onTouchEvent.
############3.1 判断View是否disable,如果是,返回true(Disable视图不需要响应触摸消息)
############3.2 如果有TouchDelegate,则使用TouchDelegate的onTouchEvent。
TouchDelegate源码里说的是,如果想要view的响应边界,大于实际显示边界,则使用这个。
############3.3 判断是否可点击,不可点击返回false
3.3.1 处理ACTION_DOWN,主要发送异步消息,检测是否长按
3.3.2 处理ACTION_MOVE,不断检测是否移除VIEW外,如果是,取消所有检测,将按钮Press状态设置为false.
3.3.3 处理ACTION_UP,判断发生时间段。
3.3.3.1 Pre-Pressed时间段,将prepressed 设置为true
3.3.3.2 Press时间段,无论是上个阶段还是这个阶段,都应该将VIEW获取焦点,更新按钮状态
3.3.3.3 Press之后,即长按,什么都不做。
3.3.3.4 判断focusTaken,一般情况下视图在Touch模式下不能获取焦点,所以会执行post(performClick),不直接调用performClick是因为可以让其他人看到视图的状态更新。
3.3.3.5 分别处理tap跟press动作,如果是Press,发送延时异步消息。
tap同理
3.3.3.6 关闭tap检测
##########4 处理Action_Cancel消息,还原按钮状态,取消所有监测,清除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 关键处理代码
        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的状态改变(如pressed)
二 添加或删除了子树
三 View的大小及可见性发送变化。

performTraversals.png

##########13.6.1 setVisiablity()
setFlags()->mBGDrawable.setVisible(),
判断VISIBLE状态是否有点笨,如果到Gone会引起requestLayout()->invaildate(),到Invisiable则只会引起invaildate()
Notice:View中的requestLayout跟ViewRootImpl中的大不一样

##########13.6.2 setEnable()
不会导致重新布局,仅仅重绘调用invaildate()

##########13.6.3 setSelect()


流程图

##########13.6.4 invaildate()
基本作用: 请求重绘
基本流程: 先绘制根视图,再绘制子视图,但并不是所有View树都会被重绘,根据mPrivateFlags的DRAWN标志位来判断,计算出哪个区域需要重绘,继而存放到mDirty变量中,然后直接重绘所有mDirty中的视图。
ViewRootImpl中的invaildate总结


ViewRootImpl.png

##########13.6.5 requestFocus() 视图到底能不能获取焦点
前期检测代码不多做解释,主要是利用了handlerFocusGainInternal()进行具体的焦点获取操作
1 获得焦点直接返回
2 如果没有焦点,设置标志,意味获取到焦点
3 寻找焦点视图
4 调用mParent.requestChildFocus,第一个this代表自己,第二个this代表想要获取焦点的视图。假设C包含B,B包含A,在B中调用,实际上是C.requestChildFocus(B,A)。为requestFocus的核心,最终递归调用到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() 测量

其本质是 将视图布局使用时候的相对值(WRAP_CONTENT,MATCH_PARENT),转为具体值(手机上实际显示的大小should have),

13.8.1 内部设计思路

多次重复测量,主要根据Parent剩余空间 + Child意图空间 进行具体测算。
parentMeasure代表父视图剩余空间
lp.height 代表子视图 layout_height的时设定的值
childMeasureSpec代表的是最终的测量规格
为什么是最终测量规格而不是测量值,是因为 parentMeasure代表父视图希望的大小,lp.height代表程序员希望的大小,而XXView自身,则也可以onMeasure里面调用setMeasureDimension设置最终的大小。


具体对应表

13.9 Layout()

基本流程
具体原理

13.10 Draw()

会将View对象绘制到屏幕上,如果是个ViewGroup对象,则会递归绘制其所有child.
主要绘制四个方面的内容:
1 View的背景,可以是color,drawable,image等
2 View的内容,TextView就是文字(带大小颜色的)
3 View的边框,渐变效果,Shader等
4 滚动条

13.10.5 ViewGroup中DispatchDraw()

为子视图提供合适的画布(Canvas)

13.10.6 ViewGroup类中drawChild()

为子视图分配合适的Canvas剪切区,取决于child的布局大小。


主要流程
13.10.11 动画的绘制

属性动画与补间动画的区别 主要是将基本动画的 平移,缩放,旋转,透明度变换 由View为对象的动画,修改成以属性为主要对象的动画,例如View的背景,形状,颜色变换。
且 属性动画会真正改变View的属性(如果是平移,则真正将view位移到目标点),而补间动画则不会改变View的属性,只是描绘一个虚假的View在目标点(点击目标View 不会有任何响应事件)

API11之后,Google推荐使用属性动画。

估值器是属性动画里最重要的控制器,可以控制属性的变化,自定义估值器需要利用属性的get,set方法,然后分段将值 赋予属性,从而引起对象的变化。


属性动画

14 WMS原理

14.1 WMS的结构

Wms功能模块与其他模块的交互,主要包括与AMS模块及应用程序客户端的接口

WMS的结构

每个窗口对应一个WindowState,负责记录窗口的全部属性(例如大小,层级)
WindowToken描述窗口对应的Token属性,但是子窗口与父窗口共用同一个WindowToken,如果窗口属于Activity创建的,则还具有AppWindowToken属性。
即WindowState最多(每个窗口必有一个),WindowToken 较少(父子窗口共用一个),AppWindowToken 最少(Activity创建的才有),

14.2 InputMonitor

InputMonitor

InputWindow只保存了为寻找焦点窗口所需要的信息,而WindowState则保存窗口的全部信息。

14.3 创建窗口的时机

主要调用过程

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

添加窗口的时机

删除窗口的时机


删除窗口的时机
删除窗口的过程

removeWindowInnerLock 流程


removeWindowInnerLock 流程

14.4 计算窗口的大小

mPolicy.beginLayoutLw()计算窗口大小。
屏幕大小减去状态栏大小即为有效区大小。
mDockxxx系列变量为有效区大小
mContentXXX = mCurXXX 为黑色矩形区域大小

调用过程

14.5 切换窗口

窗口的切换就是从将输入焦点从老窗口移动到新窗口,同时伴随一定的Activity切换,所以与Activity有一定的同步关系。

WMS的主要功能有两个,一是将所有窗口保持一定的层级关系,便于SurfaceFlinger据此绘制屏幕。二是将窗口信息发给InputManager,便于InputDispatcher线程能够将输入消息派发给和屏幕上显示一致的窗口。
**
Activity的管理主要是靠三个类,一: AMS 负责启动等。二:ActivityRecord用来表示一个Activity,保存各种Activity的属性(kyrie感觉有点类似WindowState跟Window的关系)。三:ActivityStack描述Task,所有和Task相关的数据以及控制都有ActivityStack来实现
**

Wms和InputManager之间的交互流程:
Wms保证内部窗口的正确顺序,再向InputManager传递窗口信息(即InputManager类中updateInputWindowsLw()的执行过程),
否则将会导致窗口显示错误,接受输入消息的窗口不知道去哪了。

从A到B的切换,用户选择一个程序启动。


启动流程

从B回到A的流程。


image.png

窗口每次的变化 relayout都会导致surface的重建或销毁。

ConfigChange的三种情况


image.png

1 Acitivity的切换(从Portrait到landscape)
2 wms的addWindow,removeWindow,relayoutWindow等其他操作
3 用户主动旋转屏幕

15 资源访问机制

styleable 实际上就是只是attr集合,便于开发所以定制出这套规则。


Attr,Style,Styleable的关系

styleable基本上定义的都是视图属性,更应该叫做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中并未找到具体实现代码,有空自己去找。

15.6 FrameWork资源

ZygoteInit.java中Main()->preLoad->preLoadRes() 开始加载存放于framework/base/core/res/res目录下的系统资源,且存放在com.android.Internal.R.java中,而不是android.R.java,而android.R.java可以视作为internal.R.java的子集,这也是区分私有资源与公有资源的关键。
AXMLPrinter2.jar 可以将AndroidManifest.xml的二进制文件反编译成正常代码文件,实际上是可以将任何二进制的xml文件都反编译成为正常XML。

15.6.3 实现手机系统主题切换

两种思路:
一:利用C++实现,在Java层提供一个JNI接口,接受新的资源文件位置假设为new-framework-res.apk,然后在AssertManager类初始化时,利用传入new-framework-res.apk的路径替换原来的路径即可
此方法注意事项:
1 zygote需要重新preload系统资源
2 需要将Resource里面的非静态缓存全部清除
3 在新的主题包中,需要使用原始的public.xml文件即framework-res.apk里的public.xml,来保持资源id的一致性
4 新资源包的资源名称跟数量必须与原始的相同(直接copy一份修改原始的即可)

二:Java层实现,修改Resource源码,当读取系统资源时(系统资源开头为0x01,),则利用PackageManager读取主题包的资源,从而达到替换效果。
注意事项:
1 必须区分先读取主题资源还是系统资源,如果没找到主题资源则用原系统资源代替。
2 必须清空Resource的静态缓存
3 资源id的一致性,或者使用原始id获取name,再load值。

16 程序包管理

包管理结构

利用两个xml文件保存相关的包信息
1 /system/etc/permission.xml 定义系统所具有的feature(并非权限)
2 /data/app/packages.xml 获取程序名称,路径,权限,(此文件类似注册表)
pms通过解析此文件建立庞大的信息树。

16.4 应用程序的安装与卸载

程序的安装可以分为两个过程,第一个是将原始apk复制到对应目录,第二个过程是为apk创建对应的数据目录及提取dex。
scanPackageLi 完成了第二个过程。
scanPackageLi(file)函数分两步执行
1 调用PackageParser 解析安装包,将结果保存到变量pkg中
2 调用scanPageLi(package)将上一部的结果转换到PMS的内部中
最后会将四大组件以及Broadcast等集中保存起来。

功能类
应用程序安装过程
应用程序的卸载过程

16.5 Intent匹配机制

Intent匹配的重要变量
image.png

17 输入法框架

输入法结构

IMM包含两个binder,一个是editText对应的binder,便于将按键消息传递给EditText,使其显示。另外一个是InputMethodManager自己的binder,将其传递给IMMS,然后IMMF会将具体输入法服务交给此binder,便于InputMethodManager直接访问输入法服务进程

具体的输入法进程中也包含两个binder,其一是输入法服务的的binder,IMMS通过这个对象控制输入法的状态,显示与隐藏,其二是供客户端调用的binder,传递事件使用。

17.3 输入法主要操作过程

SessionBinder指的是IMS中为客户端创建的Binder,便于通信。
Connection Binder指的是客户端自己创建的binder,便于向客户端写消息
InputMethod Binder是IMS对应的Binder

输入法的启动流程
处理过程
startInputInner函数的过程

17.3.4 显示输入法

输入法的显示实际有三个:
1 当前窗口变成交互窗口,并且调用到IMMS的windowGainedFoucs
2 程序员调用showSoftInput()
3 点击可编辑框,系统自动弹出

如图

17.4 输入法窗口内部的显示过程

showSoftInput内部调用过程
showWindow执行过程

18 Android 编译系统 如何将android源代码编译成可执行程序.

18.1 Android系统编译

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编译系统功能模块图
18.3.4 生成编译规则(java利用javac,c/c++利用gcc/g++)
生成编译规则的三步骤
  • 提取环境变量:即子项目将自己所需要的变量赋值给编译中枢。


    环境变量

    在装在assert跟res时,packages.mk还会从PRODUCT_PACKAGE_OVERLAY跟DEVICE_PACKAGE_OVERLAY里面加载。各大厂商可以据此,拓展自己的资源。

  • 包含特定的基础脚本文件。对于BUILD_PACKAGES而言,就是java.mk,其中包含了definenation.mk 真正定义了用何种编译器(javac,gcc)
  • 增加额外的目标条件。
    1. 增加R.java,几乎所有项目都引用资源文件,资源文件又被aapt2编译为R.java,因此需要先添加此文件。注意,这里是系统的android.R。
    2. 增加JNI。如上
    3. 增加对签名文件的依赖,因为Java项目最后都会被签名
    4. 增加对AAPT2及ZIPALIGN的条件依赖,aapt2用于apk打包,zipalign用于压缩打包文件,优化内部存储。
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编译系统必须

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系统编译的过程中,如何将我们想要的目标程序,添加进去,例如出厂自带的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中指定具体路径。系统C函数不需要制定。因为其已经被定义在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,输入设备,启动adb shell,输入helloMake,输出hello~~~make
18.5.3 添加一个APK项目

1 源码方式添加,假设我们已经有了一个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里面增加如下代码,即源路径至目标路径,因为是系统级别的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文件仅生成一次,需调用make update-api

19 编译自己的ROM

ROM是CPU引脚上固定的一段嵌入式程序,是CPU出厂就确定的引导程序,大小不超过1kb,也就是BootLoader。又称onChip引导程序。

  • 电源键导致电流变化,导致CPU上电引脚的变化(硬件逻辑),从而运行BootLoader,负责查找并加载次引导加载程序(第二阶段)
  • SecondBootLoader 加载 Linux 内核和可选的初始 RAM 磁盘。
  • 内核启动后,就会启动熟知的Init进程,并读取init.rc配置
  • Zygote启动
  • SystemServer启动。
android系统内存分区

以上配图大多出自《Android内核剖析》柯元旦。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容