ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍一文从Android Framework角度杂谈了一波应用启动时系统各种时间计算的整个过程。想要获取我们开发的应用启动的详细时间,直接使用Android系统提供的“adb shell am start -W -S”命令,如下所示,这个命名会很优雅的输出你的应用启动过程的整个时间

adb shell am start -W -S com.android.settings/.Settings
Stopping: com.android.settings
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.settings/.Settings }
Status: ok
Activity: com.android.settings/.Settings
ThisTime: 780
TotalTime: 780
WaitTime: 875
Complete

一般我们更多的关注ThisTime,当然TotalTime和WaitTime也很重要,复杂场景下的Activity启动需要用到。我们在ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍中的“系统是如何计算应用启动的时间的?”章节做过介绍。上一篇文章我们侧重点在于介绍Android系统计算应用启动时间的过程,并没有提及如何去优化我们的应用启动时间。本文将来点实际需要的,优雅的优化我们应用的启动时间。本文内容首先将总结性介绍一下上一篇文章的内容,然后并从各种优化工具入手来介绍优化我们应用启动时间,仅提供一些优化思路,并未深入详细的讲解细节上,

前文回顾

通过前文的介绍我们可以将应用的时间分为了三个时间段A,B和C,并且我们也介绍了了各个时间点所对应的开始Log,这里就不在过多的赘述。我们来看下上图,一般情况下ThisTime和TotalTime相等,上图显示的是含有NoDisplay的Activity启动时的情况。根据不同的行为,我们一般将Activity的启动分为三种情况Cold,Hot和Warm。Cold是指带有创建进程的启动,Hot是指当用户按了Home键退出到后台再次启动的情况,Warm是在Cold的基础上不需要创建进程,正常走Activity生命周期onCreate->onResume。正常情况我们都是以Cold启动方式来计算应用的启动时间。从上图我们可以看出,我们如果要优化应用的启动时间,我们应该更可能的减少TotalTime和ThisTime。而结合ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍一文的介绍,TotalTime和ThisTime是对应的启动三个时间段的B和C。

本文测试用例-说明

MainActivity在onCreate方法中启动Main2Activity并且将自己finish掉,分别在MyApplication,MainActivity,Main2Activity组件各个生命周期中使用了Thread.sleep(1000ms)来模拟耗时操作。同时我们为MainActivity配置了自己的进程".abm"。接下来开始我们的分析。

<applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:name=".MyApplication"android:roundIcon="@mipmap/ic_launcher_round"><activityandroid:name=".MainActivity"android:process=".abm"android:launchMode="standard"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter>
</activity>

优雅分析姿势一----Log入手

我们先从ActivityManagerService解读之Activity启动初探一文中拿来一段对应我们B和C时间段的启动堆栈:(具体信息请参阅前文介绍)

SystemServer进程
ActivityManagerService.java
activityPaused(token);ActivityStack.javaactivityPausedLocked(token, false);completePauseLocked(true /* resumeNext */, null /* resumingActivity */);ActivityStackSupervisor.javaresumeFocusedStackTopActivityLocked(topStack, prev, null);ActivityStack.javaresumeTopActivityUncheckedLocked(target, targetOptions);ActivityStackSupervisor.javastartSpecificActivityLocked(next, true, true);ActivityManagerService.javastartProcessLocked(r.processName, r.info.applicationInfo, true, 0,"activity", r.intent.getComponent(), false, false, true,(null != r.launchedFromPackage ? r.launchedFromPackage : "NA"));QQ进程
一些列调用到ActivityThread.java的main方法,我们称之为应用程序的入口
attach(false, startSeq);
attachApplication(mAppThread, startSeq);---Binder IPC--->attachApplicationLockedSystemServer进程
ActivityManagerService.java
attachApplicationLocked(thread, callingPid, callingUid, startSeq);//am_proc_boundbindApplication(processName, appInfo, providers, null, profilerInfo,null, null, null, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial, isAutofillCompatEnabled);//触发应用Application的创建操作ActivityStackSupervisor.javaattachApplicationLocked realStartActivityLocked(activity, app,top == activity /* andResume */, true /* checkConfig */)//am_restart_activityClientLifecycleManager.javascheduleTransaction(clientTransaction);ClientTransaction.javaschedule();---Binder IPC--->scheduleTransactionQQ进程
IApplicationThread.java
scheduleTransaction(this);ActivityThread.javascheduleTransaction(transaction);sendMessage(EXECUTE_TRANSACTION)->handleMessageTransactionExecutor.javaexecute(transaction);executeCallbacks(transaction);LaunchActivityItem.javaexecute(mTransactionHandler, token, mPendingActions);ActivityThread.javahandleLaunchActivity(r, pendingActions, null /* customIntent */);executeLifecycleState(transaction);ResumeActivityItem.javaexecute(mTransactionHandler, token, mPendingActions);ActivityThread.javahandleResumeActivity(token, true /* finalStateRequest */, mIsForward,"RESUME_ACTIVITY");postExecute(mTransactionHandler, token, mPendingActions);---Binder IPC--->activityResumedSystemServer进程
ActivityManagerService.java
activityResumed(token);
原文:https://blog.csdn.net/abm1993/article/details/82773652
版权声明:本文为博主原创文章,转载请附上博文链接!

我们再来有一段启动测试Log(建议使用命令:adb logcat -b all | grep "am_\|ActivityManager:"):

/*startTime*/
01-01 20:22:44.171 28907  3552 I ActivityManager: START u0 {flg=0x10000000 cmp=com.example.hoperun.myapplication/.MainActivity} from uid 0
01-01 20:22:44.189 28907  3552 I am_focused_stack: [0,1,0,reuseOrNewTask]
01-01 20:22:44.192 28907  3552 I am_create_task: [0,145]
01-01 20:22:44.192 28907  3552 I am_create_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,NULL,NULL,NULL,268435456]
01-01 20:22:44.202 28907  3552 I am_pause_activity: [0,160659114,com.android.launcher3/.Launcher]
01-01 20:22:44.204  2757  2757 I am_on_paused_called: [0,com.android.launcher3.Launcher,handlePauseActivity]//如果前一个ActivityonPause耗时怎么办?没关系Android系统设置了500ms的timeout时间,超过timeout系统继续启动操作。
/*mLaunchStartTime*/
01-01 20:22:44.251 28907 29100 I am_proc_start: [0,3593,10066,.abm,activity,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:44.252 28907 29100 I ActivityManager: Start proc 3593:.abm/u0a66 for activity com.example.hoperun.myapplication/.MainActivity
01-01 20:22:44.381 28907  3552 I am_proc_bound: [0,3593,.abm]//如果我们应用采用了多进程,这也会成为一个耗时点
01-01 20:22:44.408 28907  3552 I am_restart_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:44.418 28907  3552 I am_set_resumed_activity: [0,com.example.hoperun.myapplication/.MainActivity,minimalResumeActivityLocked]
01-01 20:22:44.421 28907  3552 I ActivityManager: screenStatusRequestTag =0
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: onCreate//如果我们应用复写了自己的Application,Application的创建也是一个耗时点,如果应用使用了多进程,我们的复写的Application还是重复创建多次,这也是一个耗时点
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: sleep....
01-01 20:22:45.503  3593  3593 D ABM#MyApplication: wake.
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: onCreate//MainActivity的onCreate耗时点
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: sleep....
01-01 20:22:46.549  3593  3593 D ABM#MainActivity: wake.
01-01 20:22:46.556 28907  3552 I ActivityManager: START u0 {cmp=com.example.hoperun.myapplication/.Main2Activity} from uid 10066
01-01 20:22:46.562 28907  3552 I am_create_activity: [0,24308867,145,com.example.hoperun.myapplication/.Main2Activity,NULL,NULL,NULL,0]
01-01 20:22:46.566 28907  3552 I am_pause_activity: [0,89434600,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:46.583 28907  3552 I am_finish_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,app-request]
01-01 20:22:46.796 28907  3552 I am_proc_start: [0,3610,10066,com.example.hoperun.myapplication,activity,com.example.hoperun.myapplication/.Main2Activity]
01-01 20:22:46.797 28907  3552 I ActivityManager: Start proc 3610:com.example.hoperun.myapplication/u0a66 for activity com.example.hoperun.myapplication/.Main2Activity
01-01 20:22:46.922 28907  3552 I am_proc_bound: [0,3610,com.example.hoperun.myapplication]
01-01 20:22:46.949 28907  3552 I am_restart_activity: [0,24308867,145,com.example.hoperun.myapplication/.Main2Activity]
/*displayStartTime*/
01-01 20:22:46.954 28907  3552 I am_set_resumed_activity: [0,com.example.hoperun.myapplication/.Main2Activity,minimalResumeActivityLocked]
01-01 20:22:46.955 28907  3552 I ActivityManager: screenStatusRequestTag =0
01-01 20:22:47.027  3610  3610 D ABM#MyApplication: onCreate//对于多进程的应用此时复写的MyApplication由重新被创建一次,耗时点
01-01 20:22:47.027  3610  3610 D ABM#MyApplication: sleep....
01-01 20:22:48.028  3610  3610 D ABM#MyApplication: wake.
01-01 20:22:48.068  3610  3610 D ABM#Main2Activity: onCreate//Main2Activity的onCreate耗时点
01-01 20:22:48.294  3610  3610 D ABM#Main2Activity: onCreate
01-01 20:22:48.294  3610  3610 D ABM#Main2Activity: sleep....
01-01 20:22:49.294  3610  3610 D ABM#Main2Activity: wake.
01-01 20:22:49.297  3610  3610 D ABM#Main2Activity: onStart//Main2Activity的onStart耗时点
01-01 20:22:49.298  3610  3610 D ABM#Main2Activity: sleep....
01-01 20:22:50.298  3610  3610 D ABM#Main2Activity: wake.
01-01 20:22:50.317  3610  3610 D ABM#Main2Activity: onResume//Main2Activity的onResume耗时点,onResume耗时主要用于View绘制,这里需要注意自身应用的布局是否可优化
01-01 20:22:50.318  3610  3610 D ABM#Main2Activity: sleep....
01-01 20:22:51.318  3610  3610 D ABM#Main2Activity: wake.
01-01 20:22:51.319  3610  3610 D ABM#Main2Activity: onPostResume
01-01 20:22:51.320  3610  3610 I am_on_resume_called: [0,com.example.hoperun.myapplication.Main2Activity,LAUNCH_ACTIVITY]
/*endTime*/
01-01 20:22:51.717 28907 28929 I am_activity_launch_time: [0,24308867,com.example.hoperun.myapplication/.Main2Activity,4929,7476]
01-01 20:22:51.717 28907 28929 I ActivityManager: Displayed com.example.hoperun.myapplication/.Main2Activity: +4s929ms (total +7s476ms)
01-01 20:22:51.807 28907 28922 I am_destroy_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,finish-imm]
01-01 20:22:51.814 28907 28922 I am_stop_activity: [0,160659114,com.android.launcher3/.Launcher]
01-01 20:22:51.822  2757  2757 I am_on_stop_called: [0,com.android.launcher3.Launcher,handleStopActivity]
01-01 20:22:51.825  3593  3593 D ABM#MainActivity: onDestroy

在测试应用中,我直接暴力使用了Thread的sleep方法来模拟我们应用存在的耗时点,主要为了让大家更加清晰的观察到几个我们平时优化启动的一些耗时点。大家可以自行参考去优化自己的应用。

优雅分析姿势二----TraceView分析具体方法耗时

打开Android的Device Monitor工具选择你的应用进程然后点击Starter Method Profile,最后执行去启动你的应用,然后点Stop,Device Monitor会自动生成一份以.trace结尾的文件如下图所示:

TraceView中的列名介绍如下:

列名 描述
Name 该线程运行过程中所调用的函数名
Incl Cpu Time 某函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time 某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间
Incl Real Time 某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time 某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间
Call+Recur Calls/Total 某函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call 某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间
Real Time/Call 同CPU Time/Call类似,只不过统计单位换成了真实时间

生成.trace文件还有一种方法就是使用"adb shell am start -W -S -P -n " -P 后面需要加上你的文件路径地址比如:

adb shell am start -n com.example.hoperun.myapplication/.MainActivity  -W -S -P data/local/tmp/test.trace
Stopping: com.example.hoperun.myapplication
Starting: Intent { cmp=com.example.hoperun.myapplication/.MainActivity }
Status: ok
Activity: com.example.hoperun.myapplication/.Main2Activity
ThisTime: 5019
TotalTime: 8756
WaitTime: 9249
Complete

我们取出结果test.trace文件直接放在AndroidStudio中,便会自动解析生成如下所示的图片:

我们会发现加上生成trace文件时的启动时间要慢于正常启动我们的应用,因此我们认为这里面是有一定的误差的。所以我们在查看TraceView的时候更多的关注Incl Cpu Time和Incl Real Time的百分比时间,找出占比比较大的方法,然后具体分析代码逻辑是否存在耗时。有一点需要指出的是本文所列的例子从一方面很好的解释了Incl Cpu Time和Incl Real Time的区别,由于使用了线程sleep方法,因此sleep的时间是不算在Incl Cpu Time内的。总的来说我们理解了各个时间的含义,找出耗时的操作应该不难。

优雅分析姿势三-TraceView有趣的变形dmtracedump

这里提及dmtracedump只是为了一乐,目的在于让大家了解一下原来Google还给了我们这么一个有趣又好玩的工具(个人感觉有点鸡肋,不如TraceView和systrace,更不如下面提到的Android Porfiler),先来张图介绍一下这个工具的作用:

可以使用dmtracedump  -g命令将.trace文件转化成png图片查看一些函数调用堆栈,我们还可以使用“dmtracedump  -h ddms3567038905260884485.trace > out1.html”命令将trace文件转化成html,我这边截取一点信息分享一下:

至于我们通过dmtracedump能获取什么有用的内容,就看各位自己了(至少我现在好像没有获取到一些实质性的有用信息,如果您有什么发现,不吝赐教,这是我的邮箱1056908181@qq.com)。

优雅分析姿势四-使用systrace分析

我为本文中的例子也抓取一段10s的systrace,如下图所示:

Android提供的systrace工具会收集给定时间段内的系统进程的总体情况,但是不会收集有关应用程序进程中代码执行的信息。如果我们想要获取我们自己应用具体代码的执行信息,我们可以自己添加Trace Tag让systrace去抓取:

Trace.beginSection("我们需要设置的TAG");
try {//我们需要测试的执行代码
} finally {Trace.endSection();
}

我们可以从systrace中获取很多信息以本文例子来说:

01-01 20:22:44.381 28907  3552 I am_proc_bound: [0,3593,.abm]
01-01 20:22:44.408 28907  3552 I am_restart_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:44.418 28907  3552 I am_set_resumed_activity: [0,com.example.hoperun.myapplication/.MainActivity,minimalResumeActivityLocked]
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: onCreate
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: sleep....
01-01 20:22:45.503  3593  3593 D ABM#MyApplication: wake.
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: onCreate
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: sleep....
01-01 20:22:46.549  3593  3593 D ABM#MainActivity: wake.
01-01 20:22:46.556 28907  3552 I ActivityManager: START u0 {cmp=com.example.hoperun.myapplication/.Main2Activity} from uid 10066
01-01 20:22:46.562 28907  3552 I am_create_activity: [0,24308867,145,com.example.hoperun.myapplication/.Main2Activity,NULL,NULL,NULL,0]
01-01 20:22:46.566 28907  3552 I am_pause_activity: [0,89434600,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:46.583 28907  3552 I am_finish_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,app-request]

其实我们可以通过对比Log的时间戳,大致上就能够定位处组件某个生命周期方法是否存在耗时操作,但是如果使用systrace,我们会更加的优雅比如:

截取了对应Log部分的systrace,我们可以很清晰的看见在创建Application和Activity的时候,我们的主进程sleep的1000ms,因此只要我们耐心的去查看,我们一定能从systrace中定位出耗时操作所在。

优雅分析姿势五-Android Profiler

Android Profiler是Google提供给Android开发者的一个非常强大的专门用来分析我们应用性能的工具。它不仅仅可以帮助我们分析应用启动时间,还可以用来分析应用的内存分配情况,电量使用等一些其他的关于性能的分析场景。这里就直接介绍如何使用Android Profiler来分析我们应用的启动了,至于工具如何使用就不详细介绍了,这里暂且认为大家都会了。直接切入本文正题:

我们可以看下Android Profiler的界面更像是systrace和TraceView的结合体,因此Android Profiler的界面里的一些信息都是和systrace,TraceView一样的。不过厉害的是Android Profiler使用更简单,更方便程序员跟踪问题信息。但是不足的是它只能抓到最后一个界面的信息,比如本例中第一个MainActivity就不能被Android Profiler给获取(也许是我对工具的理解不到位,如果有大神,麻烦告知小弟,万分感谢!)。我们简单介绍以下这个图片里所涵盖的一些信息:Event Timeline和Activity Lifecycle Timeline:这个时间线会记录Activity的生命周期的不同状态,以及与用户交互的信息,比如接收了什么按键事件,屏幕旋转等等。CPU Timeline:显示了应用进程运行过程中的CPU了使用情况。All Thread State Timeline:显示的则是应用进程中的所有线程在应用运行过程中的状况,并且这个时间线还用了不同颜色用以区分线程的不同状态,绿色表示Running,黄色表示waiting I/O,棕色表示sleeping。Method Profile界面则是在你点击start method Profile再次点击stop之后才会显示的界面。Method Profile界面由四种不同的选项,Call Chart(正常的调用图),Flame Chart(火焰图),Top Down(Top-Down顺序的调用树图),和Bottom Up(bottom-up顺序的调用树图)我们可以根据不同需要去选择不同的选项。

写在最后

应用启动时间优化是一个漫长的旅程,对于我们自己开发的应用,优化自己的代码,算不上是一种煎熬。如果换作debug别人的应用或者让我们优化一些复杂的大型的商业应用,那便是一种折磨。铁打的我们,多变的代码啊。煎熬也好,折磨也罢,我们都得去分析解决,关键是姿势要重要,得优雅。至于本文所提的五种分析思路,基本都是点到为止,想要详细了解,度娘上有很多大神的博文有做详细介绍。文章最后给大家一些平时用到的应用时间优化的建议:布局文件在达到相同界面效果的时候尽可能的层次少一点,界面中不需要加载的View请使用ViewStub代替,第一次初始话过程中请使用lazy加载方式加载不需要用到的静态变量,耗时的操作请放到后台线程不要阻塞UI线程,尽可能的少使用堵塞式的API比如SharePerference的apply和commit,使用对进程的应用请区别初始话Application,有一些初始化的操作不需要在显示之前的请仿照系统调用方式使用“Looper.myQueue().addIdleHandler”(如果您还不了解IdleHandler,建议您看下Android消息机制-Looper MessageQueue Handler).......这里一下不能考虑全优化策略,后续持续补充,如果您还有什么建议,请在评论区留下您宝贵的建议,感谢!本文皆为自己平时的一些经验总结,难免有一些理解不到位的地方,望谅解,望指正,谢谢!

ActivityManagerService解读之Activity启动时间闲聊--优雅的优化我们应用的启动时间相关推荐

  1. ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍

    从ActivityManagerService解读之Activity启动初探,到ActivityManagerService解读之Activity启动再探,到ActivityManagerServic ...

  2. ActivityManagerService解读之Activity启动初探

    Activity是Android四大组建之一,负责用户交互界面展示,其重要性不可言喻.Android系统由ActivityManagerService负责管理Activity.熟悉Activity的启 ...

  3. struts启动时加载_iOS优化篇之App启动时间优化

    原文:橘子不酸丶http://www.zyiner.com/article/5 前言 最近由于体验感觉我们的app启动时间过长,因此做了APP的启动优化.本次优化主要从三个方面来做了启动时间的优化,m ...

  4. 优化嵌入式Linux的启动时间的秘密

    01 工具链/应用程序优化 导读:嵌入式Linux在应用中往往希望系统能在尽量短的时间内启动,以提高用户体验.而且在有的应用场合,对启动时间具有严格的时间要求,尤其在工业或者医疗器械应用领域.此时如何 ...

  5. 基于APK加速启动时间的Android系统资源优化

    为了尽可能减⼩应⽤的⼤⼩,我们应该在发布版本中移除不使⽤的代码和资源. 另外还存在两个 优化⽅向可以⽤来缩减应⽤程序的占⽤空间,⼀项是使⽤混淆处理功能,该功能会缩短应⽤的类 和成员的名称:另⼀项是使⽤ ...

  6. Android 7.0 ActivityManagerService(2) 启动Activity的过程:一

    从这一篇博客开始,我们将阅读AMS启动一个Activity的代码流程. 自己对Activity的启动过程也不是很了解,这里就初步做一个代码阅读笔记,为以后的迭代打下一个基础. 一.基础知识 在分析Ac ...

  7. App启动时间的测量和优化

    启动时间的测量 准备知识 简单了解一下App 的启动过程: 解析Info.plist 加载相关信息,例如如闪屏 沙箱建立.权限检查 Mach-O加载 如果是胖二进制文件,寻找合适当前CPU类别的部分 ...

  8. java -jar 启动优化_Android 8.1 启动时间优化--耗时分析

    之前分析各个部分耗时,都是通过分析log,在SecureCRT中设置时间戳,打印出如下log [   22.266201] c1 [saudio] saudio_wait_monitor_cmd er ...

  9. java else if和switch_如何优雅地优化代码中的的if else和switch

    引言 一般来说,随着我们项目的迭代以及业务的越来越复杂,项目中的分支判断会原来越多.当项目中涉及到复杂的业务判断或者分支逻辑时,我们就需要考虑是否需要对项目进行重构了,或者if else和switch ...

最新文章

  1. torch 归一化,momentum用法详解
  2. Jedis使用教程完整版
  3. 电阻应用电路之指示灯电路的设计
  4. 网易技术干货 | 云信跨平台C++ SDK开发实战
  5. GCD LCM 欧几里得算法 扩展欧几里得算法
  6. 如何打开手机端口_微信接收图纸dwg怎么打开?如何手机查看CAD图纸,三步免费教你...
  7. 云存储技术-JDK的安装
  8. PAT乙级 1004 成绩排名
  9. flink 1.9 编译: flink-shaded-asm-6 找不到
  10. 关于学习Godot时遇到的问题(未解决)
  11. android 描点抠图源码,一款功能强大的AI驱动一键安卓抠图软件,人物商品图章签名logo...
  12. 计算机图形学设计线宽代码,计算机图形学画圆并改变线宽.doc
  13. win 7更改计算机用户名和密码错误,解决win7一开机就显示用户名和密码错误故障...
  14. 计算机游戏是什么意思,端游是什么意思啊,吃鸡端游是什么意思啊
  15. 2022数据库系统工程师 下午 试题三 真题答案
  16. 2021国家网络安全等级保护工作协调小组办公室推荐测评机构名单(未删减版)
  17. 国企银行秋招不完全指南
  18. CDH6 安装 Apache atlas
  19. 用flatpak安装程序(比如GIMP)的方法
  20. Windows下PHP版本切换

热门文章

  1. mac 打开记事本快捷键_如何在所有文件的Windows快捷菜单中添加“使用记事本打开”...
  2. KNN 算法复习总结
  3. 基于树莓派的宿舍智能空调控制系统
  4. 全球造船业发展现状分析,正处于行业上升时期「图」
  5. 计算机丢失x3daudio1_7.dll,x3daudio1_7.dll
  6. 有道云笔记Android app离线缓存,离线也能使用 有道云笔记体验阅读分享
  7. MyBatis-Plus 笔记
  8. 如何下载c语言游戏,如何用C语言编写游戏.doc
  9. Python choices()函数详解、random模块下的常用函数
  10. html页面如何引入表情插件,jquery动画表情插件