android 内存管理之adj 《二》

2024-02-11 16:59
文章标签 android 内存 管理 adj

本文主要是介绍android 内存管理之adj 《二》,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

前面一张我们介绍了updateLruProcessLocked,但是updateLruProcessLocked仅仅粗略地定义了不同进程的优先级。实际上,Android通过oom-adj 对进程进行了更加细致的进程分类,
而AMS中的updateOomAdjLocked函数,就是用于更新进程的oom_adj值。

一、updateOomAdjLocked

在进入updateOomAdjLocked ,我们先介绍一下进程状态。

进程状态

procState场景
PROCESS_STATE_NONEXISTENT-1未知
PROCESS_STATE_PERSISTENT0maxAdj<=FOREGROUND
PROCESS_STATE_PERSISTENT_UI1常驻进程并且正在显示UI
PROCESS_STATE_TOP21) 当前进程正在显示Activity
2) 该APP中有可见的Activity
3) Activity正在或已经暂停
PROCESS_STATE_BOUND_FOREGROUND_SERVICE31) instrumentation正在运行
2) 绑定了前台Service
3) 绑定的Service进程为前台
4) 使用的provider进程为前台
PROCESS_STATE_FOREGROUND_SERVICE4正在运行前台Service
PROCESS_STATE_TOP_SLEEPING5进入睡眠状态
PROCESS_STATE_IMPORTANT_FOREGROUND6Notification为Service设置了前台token
PROCESS_STATE_IMPORTANT_BACKGROUND71) 备份进程
2) provider被其它进程使用
PROCESS_STATE_TRANSIENT_BACKGROUND8
PROCESS_STATE_BACKUP9备份进程
PROCESS_STATE_HEAVY_WEIGHT10heavy weight进程
PROCESS_STATE_SERVICE11正在运行Service
PROCESS_STATE_RECEIVER12正在等待接收广播
PROCESS_STATE_HOME13home进程
PROCESS_STATE_LAST_ACTIVITY14运行过上一个显示的Activity
PROCESS_STATE_CACHED_ACTIVITY15后台进程但是有Activity
PROCESS_STATE_CACHED_ACTIVITY_CLIENT16后台进程,,同时是另一个进程的客户端。
PROCESS_STATE_CACHED_EMPTY17空进程


前面介绍过android 将所有的进程保存在一个名为mLruProcesses的列表里面,一般列表的顶端(end端)保存的就是当前显示的App进程,进程在列表的位置对于内存回收具有很大的参考意义,但是仅有列表位置还不够,android 对于每一个进程还提供了进程状态(procState)以及进程adj。 上表列出了常见的进程状态。

mLruProcesses

PROCESS_STATE_TOP 通常就是mLruProcesses列表顶端的那个进程,也就是当前显示的进程。

PROCESS_STATE_CACHED_ACTIVITY 一般就是一个进程进入后台时候的状态,前提是这个进程需要具有UI也就是包含Activity。

PROCESS_STATE_CACHED_ACTIVITY_CLIENT 一般一个处于后台的进程但是这个开启了另一个进程的Service或是ContentProvider。

假设一个仅有一个Service的进程处于后台那么它就是PROCESS_STATE_SERVICE。

假设一个仅有一个Receiver的进程处于后台那么它就是PROCESS_STATE_RECEIVER。

如果一个进程处于后台其既有activity 又有service 那么它的状态是PROCESS_STATE_CACHED_ACTIVITY 还是PROCESS_STATE_SERVICE呢?

答案是PROCESS_STATE_SERVICE,这个我们可以在下一章看到源码。

日常中常见的就是这几种系统状态。下面我们进入updateOomAdjLocked

 第一部分

    final void updateOomAdjLocked() {//第一部分//正常情况下,resumedAppLocked返回当前正处于前台的Activity,即AMS维护的mResumedActivity//如果没有mResumedActivity,则返回前台Task中的mPausingActivity或最顶端的Activityfinal ActivityRecord TOP_ACT = resumedAppLocked();final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;final long now = SystemClock.uptimeMillis();final long oldTime = now - ProcessList.MAX_EMPTY_TIME;final int N = mLruProcesses.size();mAdjSeq++;mNewNumServiceProcs = 0;mNewNumAServiceProcs = 0;
//        emptyProcessLimit和cachedProcessLimit分别为根据mProcessLimit的值计算出的允许的后台进程
//        和empty进程的最大数量。
//        当mProcessLimit不小于0且不等于1时,后台进程和empty进程和后台进程的数量各占mProcessLimit的一半。
//        本文讲的后台进程是指cached进程里的非empty的进程,而不包含后台服务进程。final int emptyProcessLimit;final int cachedProcessLimit;//mProcessLimit表示的是系统允许保留的后台进程和empty进程的总和,初始化为ProcessList.MAX_CACHED_APPS常量,默认为32。// mProcessLimit默认值等于32,通过开发者选择可设置,或者厂商会自行调整if (mProcessLimit <= 0) {emptyProcessLimit = cachedProcessLimit = 0;} else if (mProcessLimit == 1) {emptyProcessLimit = 1;cachedProcessLimit = 0;} else {//则emptyProcessLimit = 16, cachedProcessLimit = 16emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);cachedProcessLimit = mProcessLimit - emptyProcessLimit;}// Let's determine how many processes we have running vs.// how many slots we have for background processes; we may want// to put multiple processes in a slot of there are enough of// them.//经过计算得 numSlots =(15-9+1)/2=3.//ProcessList.CACHED_APP_MAX_ADJ 到ProcessList.CACHED_APP_MIN_ADJ  分为2 组,每一组3个数字。//其中第一组对应不可见进程9,11,13;第二组10,12,14对应空进程。//15 比较特殊既可以对应不可见进程也可以对应空进程。//每一个adj例如CACHED_APP_MIN_ADJ(9) 都可以对应若干个进程。int numSlots = (ProcessList.CACHED_APP_MAX_ADJ- ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;//确保空进程个数不大于cached进程数if (numEmptyProcs > cachedProcessLimit) {numEmptyProcs = cachedProcessLimit;}//emptyFactor和cachedFactor分别代表每个adj 数值对应进程个数,大于或等于1int emptyFactor = numEmptyProcs/numSlots;if (emptyFactor < 1) emptyFactor = 1;//cachedFactor 最终是1int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots;if (cachedFactor < 1) cachedFactor = 1;int stepCached = 0;int stepEmpty = 0;int numCached = 0;int numEmpty = 0;int numTrimming = 0;mNumNonCachedProcs = 0;mNumCachedHiddenProcs = 0;//更新所有进程状态(基于当前状态)//ProcessList.CACHED_APP_MIN_ADJ =9int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;int nextCachedAdj = curCachedAdj+1;int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;int nextEmptyAdj = curEmptyAdj+2;for (int i=N-1; i>=0; i--) {ProcessRecord app = mLruProcesses.get(i);if (!app.killedByAm && app.thread != null) {app.procStateChanged = false;//通过computeOomAdjLocked()计算该App的 oom_adj值,非常重要!后面详细分析computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);//当进程未分配adj的情况下,更新adj(cached和empty算法是相同的)//创建进程的时候默认是UNKNOWN_ADJ?if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {switch (app.curProcState) {case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:// This process is a cached process holding activities...// assign it the next cached value for that type, and then// step that cached level.//对cachedProcess进程的oom_adj计算的过程。// 当进程为包含activity的cached进程即文中所表达的后台进程或者其客户端进程时,//当进程procState=14或15,则设置adj=9;app.curRawAdj = curCachedAdj;app.curAdj = app.modifyRawOomAdj(curCachedAdj);if (curCachedAdj != nextCachedAdj) {//stepCached 记录某个adj 已经对应的进程数stepCached++;if (stepCached >= cachedFactor) {stepCached = 0;curCachedAdj = nextCachedAdj;//加2,所以9,11,13是一组,10,12,14 是一组。nextCachedAdj += 2;if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;}}}break;default:处理empty进程,方式与上面的cachedProcess相似app.curRawAdj = curEmptyAdj;app.curAdj = app.modifyRawOomAdj(curEmptyAdj);if (curEmptyAdj != nextEmptyAdj) {stepEmpty++;if (stepEmpty >= emptyFactor) {stepEmpty = 0;curEmptyAdj = nextEmptyAdj;nextEmptyAdj += 2;if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;}}}break;}}//后面分析applyOomAdjLocked(app, TOP_APP, true, now);// Count the number of process types.switch (app.curProcState) {case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT://这两个case 都表示当前app 是缓存进程也就是不可见进程。mNumCachedHiddenProcs++;numCached++;if (numCached > cachedProcessLimit) {//后台进程多余规定的数目,杀死进程//注意这遍历的是mLruProcesses列表app.kill("cached #" + numCached, true);}break;case ActivityManager.PROCESS_STATE_CACHED_EMPTY://该case 表示app  是空进程if (numEmpty > ProcessList.TRIM_EMPTY_APPS&& app.lastActivityTime < oldTime) {//空进程超过规定的数目,杀死进程//超过trim门限,同时empty进程存活时间大于30min时,被kill掉app.kill("empty for "+ ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)/ 1000) + "s", true);} else {numEmpty++;///empty进程的数量超过门限后,kill掉if (numEmpty > emptyProcessLimit) {app.kill("empty #" + numEmpty, true);}}break;default:mNumNonCachedProcs++;break;}//最后是对isolated process单独的处理,对于设置了isolated属性的进程为true的进程如果已经不包含服务了,则立刻kill掉该进程,//好像之后Service组件可以配置isolated属性,Activity 不可以。if (app.isolated && app.services.size() <= 0) {app.kill("isolated not needed", true);}//记录当前adj值大于等于桌面进程的所有进程数量,保存到numTrimming中。// //home本身就是个后台进程,若当前进程状态大于home//        //则该进程是个不重要的后台进程,或empty进程if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME&& !app.killedByAm) {numTrimming++;}}}
//xxxx 第二部分
}
CACHED_APP_MIN_ADJ 到CACHED_APP_MAX_ADJ  这7个adj 都是用于标示处于后台同时具有activity且activity 不可见的进程,英文注释是这样说的,但是实际上空进程也可以使用这几个adj。但是android  后台的进程的数量是不确定的,高级的手机可能达到几十个,而CACHED_APP_MIN_ADJ 到CACHED_APP_MAX_ADJ 仅仅具有7个数字,因为难免有若干个进程使用同一个adj。

android  将CACHED_APP_MIN_ADJ 到CACHED_APP_MAX_ADJ 分为了两个小组,一组是9,11,13 这一组给后台有activity 的进程;另一组是10,12,14 给空进程,空进程是android 提供的一种快速启动机制,主要是为了再次打开app的时候提升速度。

每一个adj 可能对应对个若干个进程。cachedFactor表示间隔数量,假设cachedFactor=3,那么后面三个不可见有activity的进程就使用这个9;再后面三个不可见有activity的进程就使用11,再再后面就使用13,如果后面还有进程就重新从9开始,一次类推。

这里掉用computeOomAdjLocked更新进程的adj,但是有些进程的adj可能没有计算出来,此时会通过proState来设置对应的adj。如果该进程是后台不可见且有activity的页面那么就给他9,11,13中的一个,其余的情形(例如空进程)就给10,12,14中的一个数字。

设置adj 之后会检查当前系统内部具有的空进程数量以及缓存进程的数量,如果数量操作系统规定的数量调用kill 方法彻底杀死该进程。这样就回收了一部分内存,这里涉及了一点android在非lowmemory情况下的内存回收机制。

我们继续来看完updateOomAdjLocked方法在更新完adj值后续的动作。 首先根据当前的cachedProcess和emptyProcess进程的数量来综合判定当前进程的等级,这两类进程的数量越少, 表示系统内存越紧张,内存等级越高。由低至高(对应的数值也是由低至高) 分别为ADJ_MEM_FACTOR_NORMAL、ADJ_MEM_FACTOR_MODERATE、ADJ_MEM_FACTOR_LOW、ADJ_MEM_FACTOR_CRITICAL这四个等级。 为什么能根据后台进程和空进程数量来判断出系统的内存等级呢?因为根据之前的分析可以知道, Android系统在后台进程和空进程不超过数量上限时总是尽可能多的保留后台进程和空进程, 这样用户便可再再次启动这些进程时减少启动时间从而提高了用户体验; 而lowmemeorykiller的机制又会在系统可用内存不足时杀死这些进程, 所以在后台进程和空进程数量少于一定数量时,便表示了系统以及触发了lowmemrorykiller的机制, 而剩余的后台进程和空进程的数量则正好体现了Lowmemroykiller杀进程的程度,即表示当前系统内存的紧张程度。

第二部分

mNumServiceProcs = mNewNumServiceProcs;// Now determine the memory trimming level of background processes.// Unfortunately we need to start at the back of the list to do this// properly.  We only do this if the number of background apps we// are managing to keep around is less than half the maximum we desire;// if we are keeping a good number around, we'll let them use whatever// memory they want.// //根据CachedAndEmpty个数来调整内存因子memFactorfinal int numCachedAndEmpty = numCached + numEmpty;int memFactor;if (numCached <= ProcessList.TRIM_CACHED_APPS&& numEmpty <= ProcessList.TRIM_EMPTY_APPS) {if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {//总数小于3时,内存回收等级为critical(3)memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {//总数小于5时,内存回收等级为low(2)memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;} else {//否则内存回收等级为moderate(1)memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;}//总结:后台进程与空进程越多表示内存越富裕,越少表示内存月紧张,感觉跟自己的理解有点反过来;额} else {//内存正常//后台和empty进程足够时,内存回收等级为normal(0)memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;}// We always allow the memory level to go up (better).  We only allow it to go// down if we are in a state where that is allowed, *and* the total number of processes// has gone down since last time.//根据条件判断,是否需要修改memFactor (见注释)//一般情况下,内存回收等级变高时,不允许主动降低它。//memFactor > mLastMemoryLevel 表示内存中缓存进程变少了。if (memFactor > mLastMemoryLevel) {// memFactor > mLastMemoryLevel与mLruProcesses.size() >= mLastNumProcesses//组合表示缓存进程变少了,但是空进程增多了。if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) {memFactor = mLastMemoryLevel;if (DEBUG_OOM_ADJ) Slog.d(TAG, "Keeping last mem factor!");}}mLastMemoryLevel = memFactor;mLastNumProcesses = mLruProcesses.size();//memFactor保存到mProcessStats中,与之前不等时,返回true//即内存回收等级与之前不一致时,返回trueboolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleeping(), now);final int trackerMemFactor = mProcessStats.getMemFactorLocked();//内存状态不等于normal,表示所有进程都要进行内存回收工作if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {if (mLowRamStartTime == 0) {mLowRamStartTime = now;}int step = 0;int fgTrimLevel;//根据内存回收等级,得到对应的fgTrimLevel (即前台进程的内存回收等级)//ComponentCallbacks2中定义的回收等级,值越大,越是会尽可能的回收switch (memFactor) {case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;break;case ProcessStats.ADJ_MEM_FACTOR_LOW:fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;break;default:fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;break;}//前面已经分析过,numTrimming中记录的是重要性低于home进程的后台进程数量//factor代表每个slot可以分配进程的数量,每个slot有分为后台进程与空进程int factor = numTrimming/3;int minFactor = 2;if (mHomeProcess != null) minFactor++;if (mPreviousProcess != null) minFactor++;if (factor < minFactor) factor = minFactor;//初始level为TRIM_MEMORY_COMPLETE (回收的最高等级)int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;//这个循环很有意思,逆序遍历。因为LRU集合中越在后面的进程,优先级越高,代表用户使用的频率高//LRU集合的排序是依据进程中四大组件的状态,往往存在组件尤其是activity的情况//该进程在LRU集合的位置就越靠后,也就意味着其占有的内存也较多,因此他就越需要进行内存回收。// 对应重要性大于home的进程而言,重要性越高,内存回收等级越低// 对于重要性小于home的进程,即越重要回收等级越高// 这么安排的理由有两个:// 1、此时越不重要的进程,其中运行的组件越少,能够回收的内存不多,不需要高回收等级// 2、越不重要的进程越有可能被LMK kill掉,没必要以高等级回收内存//注意:这里仅仅是通过应用的回调回收内存,但是实际上应用可能根本没有实现这个接口回收内存for (int i=N-1; i>=0; i--) {//一般i=N-1 就是当前在显示的页面。ProcessRecord app = mLruProcesses.get(i);if (allChanged || app.procStateChanged) {setProcessTrackerStateLocked(app, trackerMemFactor, now);app.procStateChanged = false;}//处理重要性小于home的进程if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME&& !app.killedByAm) {//遍历时前若干个进程属于同一个槽使用同一等级进行回收,之后再使用下一个等级进行回收。//但是这个这里可以保证前面若干的进程都是走这个分支吗?if (app.trimMemoryLevel < curLevel && app.thread != null) {try {//回调ApplicationThread的scheduleTrimMemory函数,回收内存app.thread.scheduleTrimMemory(curLevel);} catch (RemoteException e) {}}app.trimMemoryLevel = curLevel;step++;//一个slot分配满,开始下一个slot//可以看到每处理完一个slot。处理下一个slot的时候curLevel 就降低了,//因为前面的进程很可能释放了内存if (step >= factor) {step = 0;switch (curLevel) {case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE;break;case ComponentCallbacks2.TRIM_MEMORY_MODERATE:curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;break;}}} else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {//处理Heavy weight进程,app初始化的时候trimMemoryLevel 是什么级别?//这个一般是系统进程。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 {if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND|| app.systemNoUi) && app.pendingUiClean) {// 后台进程且没有ui,一般就是一个单独的Service 进程。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;}if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {try {app.thread.scheduleTrimMemory(fgTrimLevel);} catch (RemoteException e) {}}app.trimMemoryLevel = fgTrimLevel;}}} else {//处理内存回收等级为normal的情况if (mLowRamStartTime != 0) {mLowRamTimeSinceLastIdle += now - mLowRamStartTime;mLowRamStartTime = 0;}//根据cachedProcess和emptyProcess数量计算出的系统内存等级为正常时候的处理。// 简单来说,此时只需要让重要性低于PROCESS_STATE_IMPORTANT_BACKGROUND// 以TRIM_MEMORY_UI_HIDDEN进行回收即可。for (int i=N-1; i>=0; i--) {ProcessRecord app = mLruProcesses.get(i);if (allChanged || app.procStateChanged) {setProcessTrackerStateLocked(app, trackerMemFactor, now);app.procStateChanged = false;}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;}//重置levelapp.trimMemoryLevel = 0;}}//是否finish activityif (mAlwaysFinishActivities) {// Need to do this on its own message because the stack may not// be in a consistent state at this point.mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish");}if (allChanged) {requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());}if (mProcessStats.shouldWriteNowLocked(now)) {mHandler.post(new Runnable() {@Override public void run() {synchronized (ActivityManagerService.this) {mProcessStats.writeStateAsyncLocked();}}});}}

这一部分主要与内存回收有关系。

首先根据当前的cachedProcess和emptyProcess进程的数量来综合判定当前进程的等级,这两类进程的数量越少, 表示系统内存越紧张,内存等级越高。由低至高(对应的数值也是由低至高) 分别为ADJ_MEM_FACTOR_NORMAL、ADJ_MEM_FACTOR_MODERATE、ADJ_MEM_FACTOR_LOW、ADJ_MEM_FACTOR_CRITICAL这四个等级。 为什么能根据后台进程和空进程数量来判断出系统的内存等级呢?因为 Android系统在后台进程和空进程不超过数量上限时总是尽可能多的保留后台进程和空进程, 这样用户便可再再次启动这些进程时减少启动时间从而提高了用户体验; 而lowmemeorykiller的机制又会在系统可用内存不足时杀死这些进程, 所以在后台进程和空进程数量少于一定数量时,便表示了系统以及触发了lowmemrorykiller的机制, 而剩余的后台进程和空进程的数量则正好体现了Lowmemroykiller杀进程的程度,即表示当前系统内存的紧张程度。

在ADJ_MEM_FACTOR_NORMAL也就是后台进程较多的时候,此时会先从mLruProcesses列表顶端开始回收内存,也即是从当前显示的App开始调用onTrimMemory方法回收内存。其余的时候回收重要程度小于PROCESS_STATE_IMPORTANT_BACKGROUND的进程的内存。

总结

 关于如果更新adj 也就是computeOomAdjLocked的实放到下一章介绍。

这篇关于android 内存管理之adj 《二》的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/700335

相关文章

Java内存区域与内存溢出异常的详细探讨

《Java内存区域与内存溢出异常的详细探讨》:本文主要介绍Java内存区域与内存溢出异常的相关资料,分析异常原因并提供解决策略,如参数调整、代码优化等,帮助开发者排查内存问题,需要的朋友可以参考下... 目录一、引言二、Java 运行时数据区域(一)程序计数器(二)Java 虚拟机栈(三)本地方法栈(四)J

linux服务之NIS账户管理服务方式

《linux服务之NIS账户管理服务方式》:本文主要介绍linux服务之NIS账户管理服务方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、所需要的软件二、服务器配置1、安装 NIS 服务2、设定 NIS 的域名 (NIS domain name)3、修改主

java变量内存中存储的使用方式

《java变量内存中存储的使用方式》:本文主要介绍java变量内存中存储的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、介绍2、变量的定义3、 变量的类型4、 变量的作用域5、 内存中的存储方式总结1、介绍在 Java 中,变量是用于存储程序中数据

Android NDK版本迭代与FFmpeg交叉编译完全指南

《AndroidNDK版本迭代与FFmpeg交叉编译完全指南》在Android开发中,使用NDK进行原生代码开发是一项常见需求,特别是当我们需要集成FFmpeg这样的多媒体处理库时,本文将深入分析A... 目录一、android NDK版本迭代分界线二、FFmpeg交叉编译关键注意事项三、完整编译脚本示例四

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

Python+PyQt5开发一个Windows电脑启动项管理神器

《Python+PyQt5开发一个Windows电脑启动项管理神器》:本文主要介绍如何使用PyQt5开发一款颜值与功能并存的Windows启动项管理工具,不仅能查看/删除现有启动项,还能智能添加新... 目录开篇:为什么我们需要启动项管理工具功能全景图核心技术解析1. Windows注册表操作2. 启动文件

Android 实现一个隐私弹窗功能

《Android实现一个隐私弹窗功能》:本文主要介绍Android实现一个隐私弹窗功能,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 效果图如下:1. 设置同意、退出、点击用户协议、点击隐私协议的函数参数2. 《用户协议》、《隐私政策》设置成可点击的,且颜色要区分出来res/l

Android实现一键录屏功能(附源码)

《Android实现一键录屏功能(附源码)》在Android5.0及以上版本,系统提供了MediaProjectionAPI,允许应用在用户授权下录制屏幕内容并输出到视频文件,所以本文将基于此实现一个... 目录一、项目介绍二、相关技术与原理三、系统权限与用户授权四、项目架构与流程五、环境配置与依赖六、完整

Android 12解决push framework.jar无法开机的方法小结

《Android12解决pushframework.jar无法开机的方法小结》:本文主要介绍在Android12中解决pushframework.jar无法开机的方法,包括编译指令、框架层和s... 目录1. android 编译指令1.1 framework层的编译指令1.2 替换framework.ja

Android开发环境配置避坑指南

《Android开发环境配置避坑指南》本文主要介绍了Android开发环境配置过程中遇到的问题及解决方案,包括VPN注意事项、工具版本统一、Gerrit邮箱配置、Git拉取和提交代码、MergevsR... 目录网络环境:VPN 注意事项工具版本统一:android Studio & JDKGerrit的邮