Android Framework-Android进程/线程和程序内存优化

2023-05-16

Android进程和线程

进程(Process)是程序的一个运行实例,以区别于“程序”这一静态的概念;而线程(Thread)则是CPU调度的基本单位。
Android中的程序和进程具体是什么概念呢?
一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。而main()函数的工作也是千篇一律的。总结如下:
初始化
比如Windows环境下通常要创建窗口、向系统申请资源等。
进入死循环
并在循环中处理各种事件,直到进程退出。
这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。
以AndroidManifest.xml为例:

<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com. 
android.launchperf">
<application android:label="Launch Performance">
 <activity android:name="SimpleActivity" android:label="Simple
Activity">
 <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.DEFAULT" />
 </intent-filter>
 </activity>

可以看到,Activity的外围有一个名为< application>的标签。换句话说,四大组件只是“application”的“零件”。
自动创建一个activity,然后在onCreate方法打个断点,
在这里插入图片描述
那么当这个Activity启动后,将会生成几个Thread呢(是不是只会有一个主线程)?如图所示。
在这里插入图片描述
一个由向导诞生的、没有任何实际功能的Activity程序,有三个线程,除了有我们最熟悉的main thread(即图中的Thread[<1> main])外,还有两个Binder Thread。主线程由ZygoteInit启动,经由一系列调用后最终才执行Activity本身的onCreate()函数。Zygote为Activity创建的主线程是ActivityThread:

 SamplingProfilerIntegration.start();
 CloseGuard.setEnabled(false);
 Process.setArgV0("<pre-initialized>");
 Looper.prepareMainLooper(); /*只有主线程才能调用这个函数,普通线程应该使用prepare(),
 */
 if (sMainThreadHandler == null) {
 sMainThreadHandler = new Handler(); /*主线程对应的Handler*/
 }
 ActivityThread thread = new ActivityThread(); /*这个main()是static的,因此在这里需要创建一个实例*/
 thread.attach(false); /*Activity是有界面显示的,这个函数将与WindowManagerService
 建立联系。*/Looper.loop(); /*主循环开始*/
 throw new RuntimeException("Main thread loop
unexpectedly exited");/*如果程序运 行到这里,说明退出了上面的Looper循环*/
 }

Service也是寄存于ActivityThread之中的,并且启动流程和Activity基本一致。
启动Service时,也同样需要两个Binder线程的支持。

对于同一个AndroidManifest.xml中定义的四大组件,除非有特别声明(,否则它们都运行于同一个进程中(并且均由主线程来处理事件)。
结论:
1.四大组件并不是程序(进程)的全部,而只是它的“零件”。
2.应用程序启动后,将创建ActivityThread主线程。
3.同一个包中的组件将运行在相同的进程空间中。
4.不同包中的组件可以通过一定的方式运行在一个进程空间中。
5.一个Activity应用启动后至少会有3个线程:即一个主线程和两个Binder线程。

Handler, MessageQueue, Runnable与Looper

概念初探
在这里插入图片描述
Runnable,Message,MessageQueue,Looper和Handler的关系简图
Runnable和Message可以被压入某个MessageQueue中,形成一个集合
在这里插入图片描述
注意,一般情况下某种类型的MessageQueue只允许保存相同类型的Object。图中我们只是为了叙述方便才将它们混放在同一个MessageQueue中,实际源码中需要先对Runnalbe进行相应转换。
Looper循环地去做某件事
比如在这个例子中,它不断地从MessageQueue中取出一个item,然后传给Handler进行处理,如此循环往复。假如队列为空,那么它会进入休眠。
Handler是真正“处理事情”的地方
它利用自身的处理机制,对传入的各种Object进行相应的处理并产生最终结果。
用一句话来概括它们,就是:
Looper不断获取MessageQueue中的一个Message,然后由Handler来处理。
它们各司其职,很像一台计算机中CPU的工作方式::中央处理器(Looper)不断地从内存
(MessageQueue)中读取指令(Message),执行指令(Handler),最终产生结果。
Thread和Handler的关系
在这里插入图片描述
① 每个Thread只对应一个Looper;
② 每个Looper只对应一个MessageQueue;
③ 每个MessageQueue中有N个Message;
④ 每个Message中最多指定一个Handler来处理事件。
由此可以推断出,Thread和Handler是一对多的关系。
Handler的作用:
1.处理Message,这是它作为“处理者”的本职所在。
2.将某个Message压入MessageQueue中。
实现第一个功能的相应函数声明如下:

public void dispatchMessage(Message msg);//对Message进行分发
public void handleMessage(Message msg);//对Message进行处理

Looper从MessageQueue中取出一个Message后,首先会调用Handler.dispatchMessage进行消息派发;后者则根据具体的策略来将Message分发给相应的责任人
Handler的第二个功能,相应的功能函数声明如下:

1Post系列:
final boolean post(Runnable r);
final boolean postAtTime(Runnable r, long uptimeMillis);2Send系列:
final boolean sendEmptyMessage(int what);
final boolean sendMessageAtFrontOfQueue(Message msg);
boolean sendMessageAtTime(Message msg, long uptimeMillis);
final boolean sendMessageDelayed(Message msg, long
delayMillis);等等

Post和Send两个系列的共同点是它们都负责将某个消息压入MessageQueue中;区别在于后者处理的函数参数直接是Message,而Post则需要先把其他类型的“零散”信息转换成Message,再调用Send系列函数来执行下一步。
更贴近Android实现的“印象图”
在这里插入图片描述
Looper中包含了一个MessageQueue。下面是一个使用Looper的普通线程范例,名为LooperThread:

class LooperThread extends Thread {
 public Handler mHandler;
 public void run() {
 Looper.prepare();/*一句简单的prepare,究竟做了些什么工作?
*/
 mHandler = new Handler() {
 public void handleMessage(Message msg) {/*处理消息的地方。继承Handler的子类通常需要修改这个函数
*/
 }
 };
 Looper.loop();/*进入主循环*/
 }
 }

这段代码在Android线程中很有典型意义。概括起来只有3个步骤
(1)Looper的准备工作(prepare);
(2)创建处理消息的handler;
(3)Looper开始运作(loop)。
分析一下上边的代码:
首先是:

Looper.prepare();

既然要使用Looper类的函数,那么LooperThread中肯定就得执行如下操作:

import android.os.Looper;

仔细观察,Looper里有个非常重要的成员变量:

static final ThreadLocal<Looper> sThreadLocal = new
ThreadLocal<Looper>();

这是一个静态类型的变量,意味着一旦import了Looper后,sThreadLocal就已经存在并构建完毕。ThreadLocal对象是一种特殊的全局变量,因为它的“全局”性只限于自己所在的线程,而外界所有线程(即便是同一进程)一概无法访问到它。这从侧面告诉我们,每个线程的Looper都是独立的。

prepare:

private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {/*sThreadLocal.get返回的
是模板类,这个场景中是Looper。
 这个判断保证一个Thread只会有
一个Looper实例存在*/
 throw new RuntimeException("Only one Looper may be
created per thread");
 }
 sThreadLocal.set(new
Looper(quitAllowed));
 }

sThreadLocal的确保存了一个新创建的Looper对象。

接下来创建一个Handler对象,我们单独将它提取出来以方便阅读。

public Handler mHandler;
…
mHandler = new Handler() {
 public void handleMessage(Message msg) {}
};

可见,mHandler是LooperThread的成员变量,并通过new操作创建了一个Handler实例。
Handler有多个构造函数,比如:

public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Looper looper, Callback callback);

之所以有这么多构造函数,是因为Handler有如下内部变量需要初始化:

final MessageQueue mQueue;
 final Looper mLooper;
 final Callback mCallback;

我们就以LooperThread例子中采用的第一个函数来讲解下它的构造函数:

public Handler() {
 /*…省略部分代码*/
 mLooper =Looper.myLooper();/*还是通过sThreadLocal.get来获取当
前线程中的Looper实例*/
 …
 mQueue= mLooper.mQueue; /*mQueue是Looper与
Handler之间沟通的桥梁*/
 mCallback = null;
}

这样Handler和Looper,MessageQueue就联系起来了

UI主线程——ActivityThread

public static void main(String[] args) {Looper.prepareMainLooper();//和前面的LooperThread不同
 ActivityThread thread = new ActivityThread();//新建一个
ActivityThread对象
 thread.attach(false);
 if (sMainThreadHandler == null) {
 sMainThreadHandler = thread.getHandler();//主Handler
 }
 AsyncTask.init();
 Looper.loop();
 throw new RuntimeException("Main thread loop
unexpectedly exited");
 }

prepareMainLooper和prepare
普通线程只要prepare就可以了,而主线程使用的是prepareMainLooper。
普通线程生成一个与Looper绑定的Handler对象就行,而主线程是从当前线程中获取的Handler(thread.getHandler)
(1)prepareMainLooper

public static void prepareMainLooper() {
 prepare(false);//先调用prepare
 synchronized (Looper.class) {
 if (sMainLooper != null) {
 throw new IllegalStateException("The main Looper
has already been prepared.");
 }
 sMainLooper = myLooper();
 }
 }

可以看到,prepareMainLooper也需要用到prepare。参数false表示该线程不允许退出,这和前面的LooperThread不一样。经过prepare后,myLooper就可以得到一个本地线程的Looper对象,然后将其赋给sMainLooper。从这个角度来讲,主线程的sMainLooper其实和其他线程的Looper对象并没有本质的区别
在这里插入图片描述
这个图描述的是一个进程和它内部两个线程的Looper情况,其中线程1是主线程,线程2是普通线程。方框表示它们能访问的范围,如线程1就不能直接访问到线程2中的Looper对象,但二者都可以接触到进程中的各元素。
线程1:因为是Main Thread,它使用的是prepareMainLooper(),这个函数将通过prepare()为线程1生成一个ThreadLocal的Looper对象,并让sMainLooper指向它。这样做的目的就是其他线程如果要获取主线程的Looper,只需调用getMainLooper()即可。
线程2:作为普通线程,它调用的是prepare();同时也生成了一个ThreadLocal的Looper对象,只不过这个对象只能在线程内通过myLooper()访问。当然,主线程内部也可以通过这个函数访问它的Looper对象。
(2)sMainThreadHandler。
当ActivityThread对象创建时,会在内部同时生成一个继承自Handler的H对象:

final H mH = new H();

ActivityThread.main中调用的thread.getHandler()返回的就是mH。
也就是说,ActivityThread提供了一个“事件管家”,以处理主线程中的各种消息。
Looper.loop

public static void loop() {
 final Looper me = myLooper();/*loop函数也是静态的,所以它只能访问静态数据。函数myLooper 则调用sThreadLocal.get()来获取与之匹配的Looper实例(其实 就是取出之前prepare中创建的那个Looper对象)*/final MessageQueue queue = me.mQueue;/*正如我们之前所说,Looper中自带一个MessageQueue*/for (;;) {//消息循环开始
 Message msg = queue.next();/*从MessageQueue中取出一个消息,可能会阻塞*/
 if (msg == null) {/*如果当前消息队列中没有msg,说明线程要退出了。类比于上面Windows 伪代码例子中的while判断条件为0,这样就会结束循环*/
 return; /*直接返回,结束程序*/
 }
 …
 msg.target.dispatchMessage(msg); /*终于开始分派消息了,重心就在这里。变量target其实是一个Handler,所以dispatchMessage最终调用的是Handler中的处理函数。*/
 …
 msg.recycle();/*消息处理完毕,进行回收*/
 }
 }

loop()函数的主要工作就是不断地从消息队列中取出需要处理的事件,然后分发给相应的责任人。如果消息队列为空,它很可能会进入睡眠以让出CPU资源。而在具体事件的处理过程中,程序会post新的事件到队列中。另外,其他进程也可能投递新的事件到这个队列中。APK应用程序就是不停地执行“处理队列事件”的工作,直到它退出运行,如图所示。
在这里插入图片描述
在这个模型图中,ActivityThread这个主线程从消息队列中取出Message后,调用它对应的Runnable.run进行具体的事件处理。在处理的过程中,很可能还会涉及一系列其他类的调用(在图中用Object1,
Object2表示)。而且它们可能还会向主循环投递新的事件来安排后续操作。另外,其他进程也同样可以通过IPC机制向这一进程的主循环发送新事件,如触摸事件、按键事件等。这就是APK应用程序能“动起
来”的根本原因。
MessageQueue

/*以下代码还是Looper.java中的,不过只提取出MessageQueue相关的部分
*/
 final MessageQueue mQueue; /*注意它不是static的*/
 private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed); /*new了一个MessageQueue,就是它了。也就是说,当Looper创建时,消息队列也同时会被创建出来*/
 mRun = true;
 mThread = Thread.currentThread();//Looper与当前线程建立对应关系
 }

事实证明Looper内部的确管理了一个MessageQueue,它将作为线程的消息存储仓库,配合Handler、Looper一起完成一系列操作。

Thread类

Thread类的内部原理
 public class Thread implements Runnable {

可以看到,Thread实现了Runnable,也就是说线程是“可执行的代码”:

public interface Runnable {
 public void run();
}

Runnable是一个抽象接口类,唯一提供的方法就是run()。一般情况下,我们是这样使用Thread的:
方法1,继承自Thread
定义一个MyThread继承自Thread,重写它的run方法,然后调用:

MyThread thr = new MyThread();
thr.start();

方法2,直接实现Runnable
Thread的关键就是Runnable,因而下面是另一个常见用法:

new Thread(Runnable target).start();

这两种方法最终都通过start启动,它会间接调用上面的run实现。

 checkNotStarted();
 hasBeenStarted = true;
 VMThread.create(this, stackSize);/*这里是真正创
建一个CPU线程的地方*/
}

在此之前,我们一直都运行在“老线程”中,直到VMThread.create——而实际上真正在新线程中运行的只有Run方法,这解释了上面第二种方法通过传入一个Runnable也可以奏效的原因。从这个角度来理解,Thread类只能算是一个中介,任务就是启动一个线程来运行用户指定的Runnable,而不管这个Runnable是否属于自身,如图5-13所示。
在这里插入图片描述
线程有如下几种状态:
public enum State {
NEW, //线程已经创建,但还没有start
RUNNABLE, //处于可运行状态,一切就绪
BLOCKED, //处于阻塞状态,比如等待某个锁的释放
WAITING, //处于等待状态
TIMED_WAITING, //等待特定的时间
TERMINATED //终止运行
}

Thread休眠和唤醒

1.wait和notify/notifyAll
这3个函数是由Object类定义的——也就意味着它们是任何类的共有“属性”
在这里插入图片描述
当某个线程(比如SystemServer所在线程)调用一个Object(比如BlockingRunnable)的wait方法时,系统就要在这个Object中记录这个请求。因为调用者很可能不止一个,所以可使用列表(见图5-15的
waiting list)的形式来逐一添加它们。当后期唤醒条件(也就是BlockingRunnable执行了run后)满足时,Object既可以使用notify来唤醒列表中的一个等待线程,也可以通过notifyAll来唤醒列表中的所
有线程,如图5-15所示
在这里插入图片描述
2.interrupt
调用一个线程的interrupt的目的和这个单词的字面意思一样,就是“中断”它的执行过程。此时有以下3种可能性。
1.如果Thread正被blocked在某个object的wait上,或者join(),sleep()方法中,那么会被唤醒,中断状态会被清除并接收到InterruptedException。
2.如果Thread被blocked在InterruptibleChannel的I/O操作中,那么中断状态会被置位,并接收到ClosedByInterruptException,此时channel会被关闭。
3.如果Thread被blocked在Selector中,那么中断状态会被置位并且马上返回,不会收到任何exception。
3.join
join方法有如下几个原型:

public final void join ();
public final void join (long millis, int nanos);
public final void join (long millis);

比如:

Thread t1 = new Thread(new ThreadA()); 
Thread t2 = new Thread(new ThreadB()); 
t1.start(); 
t1.join(); 
t2.start();

它希望达到的目的就是只有当t1线程执行完成时,我们才接着执行后面的t2.start()。这样就保证了两个线程的顺序执行。而带有时间参数的join()则多了一个限制,即假如在规定时间内t1没有执行完成,那么我们也会继续执行后面的语句,以防止“无限等待”拖垮整个程序。
4.sleep
wait是等待某个object,而sleep则是等待时间,一旦设置的时间到了就会被唤醒。

Thread状态迁移
在这里插入图片描述
Thread+Handler+Looper的组合实例
BusinessThread

private Thread mBusinessThread = null;
private boolean mBusinessThreadStarted = false;
private BusinessThreadHandler mBusinessThreadHandler = null; 
private void startBusinessThread()
{
 if (true == mBusinessThreadStarted)
 return;
 else
 mBusinessThreadStarted = true;
 mBusinessThread = new Thread(new Runnable()
 {
 @Override
 public void run()
 {
 Looper.prepare();
 mBusinessThreadHandler = new
BusinessThreadHandler();
 Looper.loop();
 }
 });
 mBusinessThread.start();
}

BusinessThread重写了run方法,并使用Looper.prepare和Looper.loop来不断处理调整请求。这些请求是通过mBusinessThreadHandler发送到BusinessThread的消息队列中的。

public class BusinessThreadHandler extendsHandler
{
 public boolean sendMessage(int what, int arg1, int arg2)//重写sendMessage
 {
 removeMessages(what); //清理消息队列中未处理的请求
 return super.sendMessage(obtainMessage(what, arg1,arg2));//发送消息到队列
 }
 public void handleMessage(Message msg)
 {
 switch(msg.what)
 {
 case MSG_CODE:
 //在这里执行耗时操作
 break;
 default:
 break;
 }
 }
};

Android应用程序如何利用CPU的多核处理能力

开发人员如何主动去利用CPU的多核能力,从而有效提高自身应用程序的性能呢?
答案就是针对Java-Based的并行编程技术。
第一种方式就是Java线程,它在Android系统中同样适用。使用上也和Java没有太多区别,我们只要继承Thread类或者实现Runnable接口就可以了。不过采用这类方法有一点比较麻烦的地方,就是和主线程的通信需要通过Message Queue——因为只有主线程才能处理UI相关的事务,包括UI界面更新
另一种可选的并行编程方法是AsyncTask,它是Android开发的专门用于简化多线程实现的Helper类型的类。优点很明显,就是可以不需要通过繁琐的Looper、Handler等机制来与UI线程通信。
AsyncTask在设计时的目标是针对比较短时间的后台操作,换句话说,如果你需要在后台长时间执行某些事务的话,我们建议你还是使用java.util.concurrent包所提供的Executor、ThreadPoolExecutor和FutureTask等其它API接口。
第三种比较常用的“工作线程”实现模型是IntentService。

Android应用程序的典型启动流程

APK类型的应用程,它们通常由两种方式在系统中被启动
1.在Launcher中点击相应的应用程序图标启动
这种启动方式大多是由用户发起的。默认情况下APK应用程序在Launcher主界面上会有一个图标,通过点击它可以启动这个应用程序指定的一个Activity。
2.通过startActivity启动
这种启动方式通常存在于源码内部。比如在Activity1中通过startActivity来启动Activity2。
这两种启动方式的流程基本上是一致的,最终都会调用ActivityManagerService的startActivity来完成。

在这里插入图片描述
无论以什么方式发起一个Activity的启动流程,最终都会调用到AMS的startActivity函数。

如果一切顺利,AMS才会最终尝试启动指定的Activity。如果读者写过APK应用程序,应该清楚Activity的生命周期中除了onCreate,onResume外,还有onPause,onStop等。其中的onPause就是在此时被
调用的——因为Android系统规定,在新的Activity启动前,原先处于resumed状态的Activity会被pause。这种管理方式相比于Windows的多窗口系统简单很多,同时也完全可以满足移动设备的一般需求。将一
个Activity置为pause主要是通过此Activity所属进程的ApplicationThread.schedulePauseActivity方法来完成的。ApplicationThread是应用程序进程提供给AMS的一个Binder通道。

当收到pause请求后,此进程的ActivityThread主线程将会做进一步处理。除了我们熟悉的调用Activity.onPause()等方法外,它还需要通知WindowManagerService这一变化——因为用户界面也需要发生改变。做完这些以后,进程通知AMS它的pause请求已经执行完成,从而使得AMS可以接着完成之前的startActivity操作。

假如即将启动的Activity所属的进程并不存在,那么AMS还需要先把它启动起来。这一步由Process.start实现,它的第一个入参是“android.app.ActivityThread”,也就是应用程序的主线程,然后调用它的main函数。

ActivityThread启动并做好相应初始化工作后,需要调用attachApplication来通知AMS,后者才能继续执行未完成的startActivity流程。具体而言,AMS通过ApplicationThread.scheduleLaunchActivity请求应用程序来启动一个指定的Activity。之后的一系列工作就要依靠应用进程自己来完成,如Activity创建
Window,ViewRootImpl,遍历View Tree等。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android Framework-Android进程/线程和程序内存优化 的相关文章

  • 由于mariadb不支持json操作符->>,只得卸载mariadb,并安装mysql

    1 玛莉亚 xff08 maria xff09 不喜欢 gt gt 今天我的一个基于mysql的程序 xff0c 在mariadb上跑 xff0c 结果报错了 xff0c 查了一下 xff0c 原来是不支持如下sql xff1a SELEC
  • Kotlin--›Kotlin时代的Adapter(Android 一个话时代的DslAdapter(多类型,情感图,加载更多,多选,群组等))

    DslAdapter Kotlin时代的Adapter Dsl 的形式使用 RecyclerView Adapter 支持情感图状态切换 加载更多 多类型Item等 有何用 只有一个目的高效开发 一切都是为了效率 可能以前写界面 还在为了继
  • PageRank算法 到 textRank

    1 PageRank 算法概述 PageRank 即网页排名 xff0c 又称网页级别 Google 左侧排名或佩奇排名 是Google创始人拉里 佩奇和谢尔盖 布林于1997年构建早期的搜索系统原型时提出的链接分析算法 xff0c 自从G
  • 2015年阿里实习生面试总结

    2015年阿里实习生招聘总结 就在这学期作为大三的我 xff0c 本想在这个学期安安静静的学习 xff0c 9月份再参加招聘 xff0c 偶然在朋友那里听到了阿里实习生招聘的消息 xff0c 我还是挺喜欢阿里这个公司的 xff0c 氛围好
  • 揭秘Socket与底层数据传输实现

    揭秘socket 什么是socket xff1f socket字面意思其实就是一个插口或者套接字 xff0c 包含了源ip地址 源端口 目的ip地址和源端口 但是socket在那个位置呢 xff0c 在TCP IP网络的四层体系和OSI七层
  • tomcat的启动过程(Tomcat源码解析(三))

    Tomcat组件生命周期管理 在 Tomcat总体结构 xff08 Tomcat源代码解析之二 xff09 中 xff0c 我们列出了Tomcat中Server Service Connector Engine Host Context的继
  • Tomcat请求处理过程(Tomcat源码解析五)

    前面已经分析完了Tomcat的启动和关闭过程 xff0c 本篇就来接着分析一下Tomcat中请求的处理过程 在开始本文之前 xff0c 咋们首先来看看一个Http请求处理的过程 xff0c 一般情况下是浏览器发送http请求 gt 建立So
  • Tomcat 设计模式总结(Tomcat源代码阅读系列之八)

    本篇我们将来分析一下Tomcat中所涉及到设计模式 xff0c 本文我们将主要来分析 外观模式 xff0c 观察者模式 xff0c 责任链模式 xff0c 模板方法模式 命令模式 在开始本文之前 xff0c 笔者先说明一下对于设计模式的一点
  • TCP三次握手及关闭时的2MSL分析

    TCP IP三次握手四次挥手 xff0c 是非常重要的 xff0c 这个链接与关闭过程也是很简单的 xff0c 但为什么是三次握手 xff1f 以及为什么要等待2MSL的状态 xff1f 大部分人也许听到这个问题就蒙了 xff0c 这篇博客
  • HashTable源码剖析

    lt span style 61 34 font size 14px font weight normal 34 gt public class Hashtable lt K V gt extends Dictionary lt K V g
  • HashMap源码剖析

    大部分思路都是一样的 xff0c 只是一些细节不一样 xff0c 源码中都标了出来 jdk容器源码还是挺简单的 public class HashMap lt K V gt extends AbstractMap lt K V gt imp
  • Android设置/配置页面,androidx.preference的使用

    一场与Preference的战争 一 介绍二 简单使用以下将会通过简单的Demo实现Preference的样例引入build gradleMainActivity javaactivity main xmlSettingFragment j
  • Android--›迁移到AndroidX指南(含包依赖关系)

    AndroidX发布已经有段时间了 相应的包也都出了1 0 0正式版本 顺势而为 才能得以生存 是时候迁移到AndroidX了 迁移操作本身是很简单的 有菜单命令一键搞定 如下 你以为这样就完事了 还真是 项目妥妥的跑起来了 还不算完事吗
  • 【TensorFlow 入门】2、优化器函数 Optimizer

    文章目录 一 常用的optimizer类二 注意事项 xff1a 在使用它们之前处理梯度三 选通梯度 因为大多数机器学习任务就是最小化损失 xff0c 在损失定义的情况下 xff0c 后面的工作就交给了优化器 因为深度学习常见的是对于梯度的
  • 地面坐标系与机体坐标系的转换和欧拉角

    大家在入门四旋翼飞行器数学模型时第一个遇到的就是坐标系的转换 这篇文章用尽量浅显的语言为大家讲解坐标系的转换的欧拉角 机体坐标系 原点O取在飞机质心处 Xb轴指向机头 Yb轴指向机身右方 Zb指向机身下方 地面坐标系 在地面上选一点Og x
  • 九、设置元素等待

    转载于 xff1a http www testclass net selenium python element wait WebDriver提供了两种类型的等待 xff1a 显式等待和隐式等待 显式等待 显式等待使WebdDriver等待
  • find big file

    bin bash command usage description function usage echo e 34 Usage nt 0 DIR NAME 34 exit Check if user is root if id u 61
  • apache启动报错(98)Address already in use: make_sock: could not bind to address [::]:80 (98)Address alrea

    etc init d httpd restart Starting httpd 98 Address already in use make sock could not bind to address 80 98 Address alre
  • linux 查看目录大小和查看磁盘使用情况

    作者 xff1a 北南南北 来自 xff1a LinuxSir Org 提要 xff1a Linux 磁盘分区表 文件系统的查看 统计的工具很多 xff0c 有些工具是多功能的 xff0c 不仅仅是查看磁盘的分区表 xff0c 而且也能进行
  • Sublime Text 2 for Linux 的安装、配置汇总

    Sublime Text 2是一款跨平台文本编辑器 xff0c 支持Windows xff0c Linux xff0c Mac os 特色功能 xff1a 良好的扩展功能 Package 右边没有滚动条 xff0c 取而代之的是代码缩略图

随机推荐