之前有写过文章分析LMK,那篇主要是分析LMK实现原理,并没有仔细分析AMS中OOM Adj的调整。这次参考Android 9.0的代码来分析一下,主要是分析代码实现。首先看一下OOM Adj的定义都有哪些。

Adj

Value

Comments

UNKNOWN_ADJ

1001

无法确定的Adj,通常是将要缓存的进程

CACHED_APP_MAX_ADJ

906

不可见进程的Adj最大值

CACHED_APP_MIN_ADJ

900

不可见进程的Adj最小值

SERVICE_B_ADJ

800

B List中的Service,和A list相比,他们对用户的黏合度要小些

PREVIOUS_APP_ADJ

700

用户前一次交互的进程

HOME_APP_ADJ

600

Launcher进程

SERVICE_ADJ

500

应用服务进程

HEAVY_WEIGHT_APP_ADJ

400

后台的重量级进程

BACKUP_APP_ADJ

300

承载backup相关操作的进程

PERCEPTIBLE_APP_ADJ

200

可感知进程,比如后台音乐播放

VISIBLE_APP_ADJ

100

前台可见的Activity进程

FOREGROUND_APP_ADJ

0

当前正在前台运行的进程,也就是用户正在交互的那个程序

PERSISTENT_SERVICE_ADJ

-700

与系统进程或Persistent进程绑定的进程

PERSISTENT_PROC_ADJ

-800

Persistent属性的进程,如telephony

SYSTEM_ADJ

-900

系统进程

NATIVE_ADJ

-1000

Native进程,不被系统管理

updateOomAdj

OOM Adj的更新是通过AMS中updateOomAdjLocked() 函数完成的。这个函数中不仅仅更新了OOM Adj,同时还进行了内存调整。我们先看一下OOM Adj的调整实现。

final void updateOomAdjLocked() {

......

boolean retryCycles = false;

// 因为service连接,需要重置进程的cycle状态,

for (int i=N-1; i>=0; i--) {

ProcessRecord app = mLruProcesses.get(i);

app.containsCycle = false;

}

for (int i=N-1; i>=0; i--) {

ProcessRecord app = mLruProcesses.get(i);

if (!app.killedByAm && app.thread != null) {

app.procStateChanged = false;

// 计算app的OOM adj

computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);

// 如果任意一个进程处于cycle中,需要增加一次循环

retryCycles |= app.containsCycle;

// 对于没有分配Adj的后台缓存进程,在这里进行分配。

if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {

......

}

}

}

// 存在处于cycle中的进程时,重新计数OOM Adj,直到没有进程提高优先级

int cycleCount = 0;

while (retryCycles) {

cycleCount++;

retryCycles = false;

for (int i=0; i

ProcessRecord app = mLruProcesses.get(i);

// 恢复处于cycle中进程的Adj序列号

if (!app.killedByAm && app.thread != null && app.containsCycle == true) {

app.adjSeq--;

app.completedAdjSeq--;

}

}

for (int i=0; i

ProcessRecord app = mLruProcesses.get(i);

// 重新计算处于cycle中进程的OOM Adj

if (!app.killedByAm && app.thread != null && app.containsCycle == true) {

if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now)) {

retryCycles = true;

}

}

}

}

for (int i=N-1; i>=0; i--) {

ProcessRecord app = mLruProcesses.get(i);

if (!app.killedByAm && app.thread != null) {

// 设置进程的OOM Adj

applyOomAdjLocked(app, true, now, nowElapsed);

// 统计各种进程类型的数量,并杀掉超过限制的后台缓存进程和empty进程

switch (app.curProcState) {

case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:

case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:

......

break;

case ActivityManager.PROCESS_STATE_CACHED_EMPTY:

......

break;

default:

mNumNonCachedProcs++;

break;

}

if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {

// 杀掉孤立进程

app.kill("isolated not needed", true);

} else {

// 保留的进程,更新uid

final UidRecord uidRec = app.uidRecord;

......

}

// 统计进程状态大于HOME的数量,也就是不太重要的进程

if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME

&& !app.killedByAm) {

numTrimming++;

}

}

}

// 检查是否有uid从后台切换到前台或从前台切换到后台,并通知需要去阻止的应用

incrementProcStateSeqAndNotifyAppsLocked();

mNumServiceProcs = mNewNumServiceProcs;

......

// 进行内存调整和回收

......

// 如果设置总是销毁后台Activity

if (mAlwaysFinishActivities) {

mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish");

}

if (allChanged) {

requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());

}

ArrayList becameIdle = null;

// 更新UidRecord

if (mLocalPowerManager != null) {

mLocalPowerManager.startUidChanges();

}

for (int i=mActiveUids.size()-1; i>=0; i--) {

......

}

if (mLocalPowerManager != null) {

mLocalPowerManager.finishUidChanges();

}

......

}

OOM Adj的更新过程主要完成以下工作,

重新计算进程的OOM Adj值,并进行更新。

未分配Adj值的进程根据进程状态分为后台缓存进程和empty进程,在CACHED_APP_MIN_ADJ到CACHED_APP_MAX_ADJ分配Adj值。后台缓存进程和empty进程的Adj值交叉递增,每一个级别上的进程个数都不超过预先计算的最大值。

逆序处理LRU中的进程,回收超过限制的后台缓存进程和empty进程。默认的限制时后台缓存进程和empty进程各16个。

isolated进程果已经不包含服务,直接回收。

更新进程的UidRecord。

接下来分析一下updateOomAdjLocked() 中关于内存调整的部分。Android将系统内存的状态分为了4个等级,定义如下。

Adj

Value

Comments

ADJ_MEM_FACTOR_NORMAL

0

系统内存正常,不需要调整

ADJ_MEM_FACTOR_MODERATE

1

系统内存中等,低于正常状态

ADJ_MEM_FACTOR_LOW

2

系统内存低,需要回收内存

ADJ_MEM_FACTOR_CRITICAL

3

系统内存紧张,必须回收些内存

在ComponentCallbacks2中还定义了内存回收的级别,其中前三个是后台缓存的回收级别,后三个是进程运行时的回收级别。

Adj

Value

Comments

TRIM_MEMORY_COMPLETE

80

处于后台LRU列表尾部的进程,如果找不到更多内存,很快将被杀死。

TRIM_MEMORY_MODERATE

60

处于后台LRU列表中部的进程,清理内存可以让后续运行的进程获得更好的性能。

TRIM_MEMORY_BACKGROUND

40

后台进程,处于LRU列表的头部,这时清理内存可以让进程更高效的返回前台。

TRIM_MEMORY_UI_HIDDEN

20

进程UI已经不可见,可以释放UI资源。

TRIM_MEMORY_RUNNING_CRITICAL

15

设备正运行在低内存上,无法保证后台进程存活。应该尽可能的释放非关键资源。接下来要调用onLowMemory()报告系统内存低,已经显著影响用户

TRIM_MEMORY_RUNNING_LOW

10

设备正运行在低内存上,应释放不必要的资源。

TRIM_MEMORY_RUNNING_MODERATE

5

设备的运行内存偏低,可能需要释放不必要的资源

Android系统是根据后台缓存进程和empty进程的数量来区分内存等级的。因为系统总是尽可能多的保留后台进程,以便于进程再次启动时可以减少启动时间,用户体验更好。但当系统内存不足时,lowmemeorykiller机制会优先杀死不重要的后台进程,所以可以认为后台进程的数量是与lowmemrorykiller的触发挂钩的。剩余的后台进程越少,表明通过Lowmemroykiller需要回收的内存越多,整个系统的内存就越紧张。

final void updateOomAdjLocked() {

......

// 后台缓存进程与empty进程的总和

final int numCachedAndEmpty = numCached + numEmpty;

int memFactor;

// 只有cache进程和empty进程同时小于各自的TRIM值时,才认为存在内存不足的情况

// 默认情况下CUR_TRIM_EMPTY_PROCESSES=8,CUR_TRIM_CACHED_PROCESSES=5

if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES

&& numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {

if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {

// 当cache+empty进程数小于3时,表明系统内存紧张

memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;

} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {

// 当cache+empty进程数小于5时,表明系统内存低

memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;

} else {

// 其他情况表明内存中等

memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;

}

} else {

memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;

}

......

mLastMemoryLevel = memFactor;

mLastNumProcesses = mLruProcesses.size();

// 设置内存调整等级,如果成功返回true

boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleepingLocked(), now);

final int trackerMemFactor = mProcessStats.getMemFactorLocked();

// 内存不处于正常级别时,需要回收内存

if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {

if (mLowRamStartTime == 0) {

mLowRamStartTime = now;

}

int step = 0;

int fgTrimLevel;

// 根据内存等级获取fgTrimLevel,在ComponentCallbacks2定义

switch (memFactor) {

......

}

// 计算factory,用于每个trimLevel上的进程数

int factor = numTrimming/3;

int minFactor = 2;

if (mHomeProcess != null) minFactor++;

if (mPreviousProcess != null) minFactor++;

if (factor < minFactor) factor = minFactor;

// 默认的trimLevel,为最高

int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;

// 逆序处理LRU中的所有进程

for (int i=N-1; i>=0; i--) {

ProcessRecord app = mLruProcesses.get(i);

......

// 处理不太重要的而进程,进程状态大于HOME

if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME

&& !app.killedByAm) {

// 进程的trimLevel小于当前级别,则进行回收

if (app.trimMemoryLevel < curLevel && app.thread != null) {

try {

app.thread.scheduleTrimMemory(curLevel);

} catch (RemoteException e) {

}

......

}

// 更新进程的trimLevel,根据factor逐渐降低级别:COMPLETE->MODERATE->BACKGROUND

app.trimMemoryLevel = curLevel;

step++;

if (step >= factor) {

......

}

} else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT

&& !app.killedByAm) {

// heavy weight进程以TRIM_MEMORY_BACKGROUND时进行回收

if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND

&& app.thread != null) {

try {

app.thread.scheduleTrimMemory(

ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);

} catch (RemoteException e) {

}

}

app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;

} else {

// 进程处于后台并带有UI时,以TRIM_MEMORY_UI_HIDDEN进行回收

if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND

|| app.systemNoUi) && app.pendingUiClean) {

final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;

if (app.trimMemoryLevel < level && app.thread != null) {

try {

app.thread.scheduleTrimMemory(level);

} catch (RemoteException e) {

}

}

app.pendingUiClean = false;

}

// 当fgTrimLevel大于当前trimLevel时,以fgTrimLevel进行回收

if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {

try {

app.thread.scheduleTrimMemory(fgTrimLevel);

} catch (RemoteException e) {

}

}

app.trimMemoryLevel = fgTrimLevel;

}

}

} else {

// 内存正常时的处理

if (mLowRamStartTime != 0) {

mLowRamTimeSinceLastIdle += now - mLowRamStartTime;

mLowRamStartTime = 0;

}

for (int i=N-1; i>=0; i--) {

ProcessRecord app = mLruProcesses.get(i);

if (allChanged || app.procStateChanged) {

setProcessTrackerStateLocked(app, trackerMemFactor, now);

app.procStateChanged = false;

}

// 后台带有UI的进程以TRIM_MEMORY_UI_HIDDEN进行回收

if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND

|| app.systemNoUi) && app.pendingUiClean) {

if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN

&& app.thread != null) {

try {

app.thread.scheduleTrimMemory(

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);

} catch (RemoteException e) {

}

}

app.pendingUiClean = false;

}

app.trimMemoryLevel = 0;

}

}

......

}

内存调整的主要工作是在不杀死进程的情况下,根据需要对内存进行回收。

内存低时,对于进程状态大于HOME的不太重要的进程,根据LRU倒序内存回收级别逐渐降低。

内存低时,对于重要的进程,越重要内存回收等级越高。

内存正常时,对后台带有UI的进程进行内存回收。

computeOomAdj

上面分析updateOomAdjLocked() 的大致流程,接着分析一下其中的一个重要函数computeOomAdjLocked(),是如何计算OOM Adj的。

private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,

ProcessRecord TOP_APP, boolean doingAll, long now) {

// 判断Adj序列号,相等表示已经计算过或在计算中

if (mAdjSeq == app.adjSeq) {

......

}

// 设置空进程的Adj

if (app.thread == null) {

......

}

......

// 计算Adj最大值小于FOREGROUND进程的Adj,系统进程或Persistent进程

if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {

// 设置Adj,SchedGroup,ProcState,UI状态等

......

app.curAdj = app.maxAdj;

app.completedAdjSeq = app.adjSeq;

// 如果Adj小于计算前的值,则进程Adj被提升

return app.curAdj < prevAppAdj;

}

......

// 根据进程的状态设置相应的Adj,SchedGroup,ProcState

if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {

// 前台进程

......

} else if (app.runningRemoteAnimation) {

// 正在运行远端的动画

......

} else if (app.instr != null) {

// 正在运行测试程序

......

} else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {

// 正在处理广播

......

} else if (app.executingServices.size() > 0) {

// 正在执行Service的回调

......

} else if (app == TOP_APP) {

// 前台进程,但系统灭屏

......

} else {

// 空进程

......

}

// 非前台的activities,继续调整Adj

if (!foregroundActivities && activitiesSize > 0) {

int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX;

for (int j = 0; j < activitiesSize; j++) {

......

if (r.visible) {

// 如果进程包含可见activity,Adj仅升级调整,升至VISIBLE

......

} else if (r.isState(ActivityState.PAUSING, ActivityState.PAUSED)) {

// 如果进程activity处理暂停状态,Adj升至PERCEPTIBLE

......

} else if (r.isState(ActivityState.STOPPING)) {

// 如果进程activity处理正在停止状态,Adj升至PERCEPTIBLE

......

} else {

// 如果进程只包含cached-activity,仅调整procState

......

}

}

// 非前台包含可见activity进程的Adj跟随层次变化,越往下Adj越大

if (adj == ProcessList.VISIBLE_APP_ADJ) {

adj += minLayer;

}

}

......

if (adj > ProcessList.PERCEPTIBLE_APP_ADJ

|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {

// 对于前台服务进程或显示overylay UI的进程,Adj设置为PERCEPTIBLE,但procState不同

......

}

if (adj > ProcessList.PERCEPTIBLE_APP_ADJ

|| procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {

// 进程显示toasts,Adj升至PERCEPTIBLE

......

}

if (app == mHeavyWeightProcess) {

// 重量级进程,Adj升至HEAVY_WEIGHT

......

}

if (app == mHomeProcess) {

// home进程,Adj升至HOME

......

}

if (app == mPreviousProcess && app.activities.size() > 0) {

// 上一个前台进程,Adj升至PREVIOUS

......

}

......

if (mBackupTarget != null && app == mBackupTarget.app) {

// 进程正在执行备份时,应该避免被啥掉,Adj升至BACKUP

......

}

......

// 处理service的Adj

for (int is = app.services.size()-1;

is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ

|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND

|| procState > ActivityManager.PROCESS_STATE_TOP);

is--) {

ServiceRecord s = app.services.valueAt(is);

if (s.startRequested) {

// 当进程中含有Unbounded Service时

......

}

for (int conni = s.connections.size()-1;

conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ

|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND

|| procState > ActivityManager.PROCESS_STATE_TOP);

conni--) {

// 当进程中含有Bounded Service时

......

}

}

// 处理含有ContentProvider的进程

for (int provi = app.pubProviders.size()-1;

provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ

|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND

|| procState > ActivityManager.PROCESS_STATE_TOP);

provi--) {

ContentProviderRecord cpr = app.pubProviders.valueAt(provi);

......

}

// 如果之前运行ContentProvider存活时间没有超时,Adj升至PREVIOU

if (app.lastProviderTime > 0 &&

(app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {

......

}

// 如果services或providers的客户端处于top状态,进一步处理

if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {

......

}

// Cache进程,进一步处理

if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {

......

}

// 对service进程做特殊处理

if (adj == ProcessList.SERVICE_ADJ) {

......

}

......

return app.curAdj < prevAppAdj;

}

computeOomAdjLocked()的代码量很大,逻辑非常复杂。简单来说就是根据进程的各种状态,来调整Adj、schedGroup、procState等。这里只是简单撸了一下大致的流程,细节没有写,太多了。

applyOomAdj

计算完进程的OOM Adj后,需要通过applyOomAdjLocked()将Adj值设置到Android LowMemoryKiller(LMK)机制中去,具体源码如下。

private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,

long nowElapsed) {

......

if (app.curAdj != app.setAdj) {

// 将curAdj设置到LMK系统中,Adj值最终会写入到线程对应的Proc文件中

ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);

app.setAdj = app.curAdj;

app.verifiedAdj = ProcessList.INVALID_ADJ;

}

if (app.setSchedGroup != app.curSchedGroup) {

int oldSchedGroup = app.setSchedGroup;

app.setSchedGroup = app.curSchedGroup;

if (app.waitingToKill != null && app.curReceivers.isEmpty()

&& app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {

// 当进程处于后台等待被杀时,杀掉进程

app.kill(app.waitingToKill, true);

success = false;

} else {

......

try {

// 设置整个进程的Group

setProcessGroup(app.pid, processGroup);

if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {

// 如果进程的Group从非TOP变为TOP时,提高UI线程和Render线程的调度优先级。

// 或者使用RT调度策略,或者在标准调度策略下将优先级设置为-10。

......

} else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&

app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {

// 如果进程的Group从TOP变为非TOP时,降低UI线程和Render线程的调度优先级。

// 或者将调度策略改为SCHED_OTHER,或者将优先级恢为0。

......

} catch (Exception e) {

......

}

}

// 调整上一次上报的前台activities和ProcessState的状态

......

if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT

|| ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) {

// 关于内存的进程状态发生变化时更新下次收集PSS数据得到时间

......

} else {

// 如果定时时间到了,收集PSS数据

......

}

if (app.setProcState != app.curProcState) {

// 更新进程状态

......

} else if (app.reportedInteraction && (nowElapsed-app.interactionEventTime)

> mConstants.USAGE_STATS_INTERACTION_INTERVAL) {

// 长时间处于交互状态的进程,每天至少上报一次使用状态

maybeUpdateUsageStatsLocked(app, nowElapsed);

}

// 进程activities发生改变时需要发送广播,放入待处理队列。

if (changes != 0) {

......

}

return success;

}

applyOomAdjLocked()不仅仅设置了LMK的Adj值,还完成了调整了TOP进程的调度策略或优先级、收集PSS数据、发送状态改变广播等工作。

android中的oom,Android OOM Adjustments相关推荐

  1. android中textcolor属性,android – EditText和TextView textColorPrimary不遵循API lt;21的主题颜色...

    在设计工具栏视图以使其适用于API 21及以下版本时存在一些问题,但我认为我有这个styles.xml @color/colorPrimary @color/colorPrimaryDark @col ...

  2. android中px单位,android中像素单位dp、px、pt、sp的比较

    dp(dip): device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA.HVGA和QVGA 推荐使用这个,不依赖 ...

  3. android中内存泄露,Android中的内存泄露

    编辑推荐: 本文来自于csdn,本文主要从java的内存模型讲起,最终举出几个内存泄露的例子和解决方案. java运行时内存模型 具体信息:http://gityuan.com/2016/01/09/ ...

  4. android中的 listview,Android中ListView的初步认识(一)

    ListView是安卓开发中常用的组件之一,它的作用是在一个垂直的列表中展现出所需的项目.接下来,我们看一下ListView的实现方法: 第一种 是常见的在XML中定义然后在activity中使用fi ...

  5. android中viewpager+fragment,Android开发之ViewPager+Fragment

    使用步骤 1.Activity的布局文件 android:id="@+id/viewpager" android:layout_width="wrap_content&q ...

  6. Matrix: android 中的Matrix (android.graphics.Matrix) (转)

    本篇博客主要讲解一下如何处理对一个Bitmap对象进行处理,包括:缩放.旋转.位移.倾斜等.在最后将以一个简单的Demo来演示图片特效的变换. 1. Matrix概述 对于一个图片变换的处理,需要Ma ...

  7. android ffmpeg 优点_在Android中使用FFmpeg(android studio环境)

    1.首先我们需要一个已经编译好的libffmpeg.so文件.(怎么编译是个大坑,可以参考windows环境下编译android中使用的FFmpeg,也可以用网上下载的现成的,本文相关的github项 ...

  8. android中自定义 toast,android 自定义Toast样式和显示方式

    问题: 1.android 开发中如果不停的触发显示Toast,会造成Toast一个接一个的弹出,非常影响用户体验. 2.android设备有千万个,每个设备的Toast的背景有可能不一样,造成在应用 ...

  9. android中搜索对话框,android – 如何使用onSearchRequested()调用搜索对话框

    我正在尝试实现搜索对话框,我无法显示活动中的搜索. 我在我的清单文件中定义了我的主要活动,此活动向用户显示他们必须选择的选项列表.其中一个选项是"搜索"选项. android:na ...

  10. android中point pt1,Android dip,px,pt,sp 的区别详解

    dip: device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA.HVGA和QVGA 推荐使用这个,不依赖像素. ...

最新文章

  1. 对 WEB 标准以及 W3C 的理解与认识?
  2. 最近的一次敏捷项目Scrum经验总结
  3. (转载)虚幻引擎3--【UnrealScript教程】章节一:8.Enums
  4. org.apache.hadoop.util.PlatformName //cgywin下Hadoop-0.21.0 错误问题
  5. 聊聊程序员的成长与价值提升
  6. raid卡组不同raid_RAID磁盘阵列是如何运作的?
  7. python棋类程序_python棋类游戏编写入门
  8. 关于udelay(); mdelay(); ndelay(); msleep();
  9. 剑指offer 变态跳台阶 特别sb的一道题
  10. mysql数据库安全配置规范_MySQL数据库安全配置
  11. 微信小程序之旅一(页面渲染)
  12. 一文详解opencv摄像头数字识别
  13. MySQL 第六次练习(索引)
  14. Windows10更新后,如何删除多出来的OEM分区?
  15. 揭秘!苏宁“信息基础设施”型零售实践大解析
  16. Salesforce收购Slack背后的原因,你知道多少?
  17. 计算机怎么复制公式,excel怎么复制公式 -电脑资料
  18. oul可以用作c语言常量吗,STL chips
  19. iview table 导出csv文件错行问题
  20. 文本生成任务常见评估指标

热门文章

  1. python数据分析之(7)简单绘图pylab
  2. 致敬ATSS | Dynamic ATSS再造ATSS辉煌!!!
  3. 综述|线结构光中心提取算法研究
  4. JS-两个空数组为什么不相等?
  5. 第一百六十天 how can I 坚持
  6. tornado autoreload 模式
  7. Expected authority at index 7: hdfs://
  8. Python统计磁盘代码文件行数
  9. ENVI入门系列教程---一、数据预处理---2.1自定义坐标系
  10. Pytorch——计算机视觉工具包:torchvision