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的大小及可见性发送变化。
##########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总结
##########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()
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模块及应用程序客户端的接口
每个窗口对应一个WindowState,负责记录窗口的全部属性(例如大小,层级)
WindowToken描述窗口对应的Token属性,但是子窗口与父窗口共用同一个WindowToken,如果窗口属于Activity创建的,则还具有AppWindowToken属性。
即WindowState最多(每个窗口必有一个),WindowToken 较少(父子窗口共用一个),AppWindowToken 最少(Activity创建的才有),
14.2 InputMonitor
InputWindow只保存了为寻找焦点窗口所需要的信息,而WindowState则保存窗口的全部信息。
14.3 创建窗口的时机
Android8.0 改为ViewRootImpl.setView->session.addToDisplay->wms.addWindow
删除窗口的时机
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的流程。
窗口每次的变化 relayout都会导致surface的重建或销毁。
ConfigChange的三种情况
1 Acitivity的切换(从Portrait到landscape)
2 wms的addWindow,removeWindow,relayoutWindow等其他操作
3 用户主动旋转屏幕
15 资源访问机制
styleable 实际上就是只是attr集合,便于开发所以定制出这套规则。
styleable基本上定义的都是视图属性,更应该叫做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中并未找到具体实现代码,有空自己去找。
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匹配机制
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
17.3.4 显示输入法
输入法的显示实际有三个:
1 当前窗口变成交互窗口,并且调用到IMMS的windowGainedFoucs
2 程序员调用showSoftInput()
3 点击可编辑框,系统自动弹出
17.4 输入法窗口内部的显示过程
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
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)
- 增加额外的目标条件。
- 增加R.java,几乎所有项目都引用资源文件,资源文件又被aapt2编译为R.java,因此需要先添加此文件。注意,这里是系统的android.R。
- 增加JNI。如上
- 增加对签名文件的依赖,因为Java项目最后都会被签名
- 增加对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项目。
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内核剖析》柯元旦。