Android开发

Android开发

Android 的四大组件

​ 活动(Activity)

​ 服务(Service)

​ 广播接收器(BroadcastReceiver)

​ 内容提供商(Content Provider)

Activity

​ 一个Activity代表一个单独的拥有用户界面的屏幕。比如,一个购物应用程序可能拥有一个首页显示商品的Activity,一个购物车显示的Activity,一个查看购物记录的Activity。虽然这些Activity一起工作,但是他们是相互独立的。因此一个不同的应用程序也可以启动这些Activity中的任何一个(如果这个应用程序允许的话)。比如购物应用程序中的商品评价页面为了能让用户分享一张图片它能启动系统相册应用的Activity。

一个Activity是由Activity类的子类来实现的。

Service

​ 一个Service组件是运行在后台的一个进程,这个进程处理一个长时间的操作或者是一个远程调用。一个Service不需要提供一个用户界面。比如,一个Service可以在用户运行一个不同程序的同时在后台播放音乐,或者在不阻塞一个Activity的用户界面的同时获取网络数据。其他的组件,比如一个Activity,可以启动一个Service并且与之交互。

一个Service是由Service的子类来实现的。

Content providers

​ 一个Content provider管理一组共享的应用程序数据。你可以把这些数据存储到文件系统、SQLite数据库、互联网或者任何你的应用程序可以访问的持久化存储设备中。通过Content provider其他的应用程序能够查询甚至修改(如果Content provider允许修改操作)这些数据。例如Android系统提供了管理联系人信息的Content provider。因此,任何一个应用程序在拥有相应权限的情况下都可以访问这个Content provider管理的数据(比如ContactsContract.Data)来读取或者修改某个联系人的信息。

一个Content Provider是由ContentProvider的子类来实现的,并且必须实现一组使其他应用程序能与之通信的API。

Broadcast receivers

​ 一个Broadcast receiver组件用于响应整个系统的广播通知。大量的广播都是由系统产生的。例如屏幕关闭的广播,电量不足的广播,拍了一张相片的广播。应用程序也可以发出广播,例如让其他应用程序知道一些数据已从网上下载了,并且它们可以使用这些数据。虽然broadcast receiver不显示用户界面,但是当广播事件发生的时候他们可以创建一个状态栏通知来提示用户。更加普遍的情况是,一个Broadcast receiver只是一扇通向其他组件的“大门”,它只做非常少量的工作。例如它可以基于一个广播事件启动一个Service来完成一些工作。

一个Broadcast receiver是由BroadcastReceiver的子类来实现的,并且每个广播都是当作一个Intent对象来传递的。

Activity生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wjpBqbEa-1615111390518)(C:\Users\jfcaof\Desktop\activity的生命周期.png)]

Activity生命周期中各个方法的含义和作用

(1)onCreate:

​ create表示创建,这是Activity生命周期的第一个方法,也是我们在android开发中接触的最多的生命周期方法。它本身的作用是进行Activity的一些初始化工作,比如使用setContentView加载布局,对一些控件和变量进行初始化等。但也有很多人将很多与初始化无关的代码放在这,其实这是不规范的。此时Activity还在后台,不可见。所以动画不应该在这里初始化,因为看不到……

(2)onStart:

​ start表示启动,这是Activity生命周期的第二个方法。此时Activity已经可见了,但是还没出现在前台,我们还看不到,无法与Activity交互。其实将Activity的初始化工作放在这也没有什么问题,放在onCreate中是由于官方推荐的以及我们开发的习惯。

(3)onResume:

​ resume表示继续、重新开始,这名字和它的职责也相同。此时Activity经过前两个阶段的初始化已经蓄势待发。Activity在这个阶段已经出现在前台并且可见了。这个阶段可以打开独占设备

(4)onPause:

​ pause表示暂停,当Activity要跳到另一个Activity或应用正常退出时都会执行这个方法。此时Activity在前台并可见,我们可以进行一些轻量级的存储数据和去初始化的工作,不能太耗时,因为在跳转Activity时只有当一个Activity执行完了onPause方法后另一个Activity才会启动,而且android中指定如果onPause在500ms即0.5秒内没有执行完毕的话就会强制关闭Activity。从生命周期图中发现可以在这快速重启,但这种情况其实很罕见,比如用户切到下一个Activity的途中按back键快速得切回来。

(5)onStop:

​ stop表示停止,此时Activity已经不可见了,但是Activity对象还在内存中,没有被销毁。这个阶段的主要工作也是做一些资源的回收工作。

(6)onDestroy:

​ destroy表示毁灭,这个阶段Activity被销毁,不可见,我们可以将还没释放的资源释放,以及进行一些回收工作。

(7)onRestart:

​ restart表示重新开始,Activity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。

Activity A切换到ActivityB时,A和B的生命周期变化。

1、A的onPause()方法被执行。
2、B的onCreate()方法,onStart(),onResume()方法被顺序执行。
3、如果B是不透明的,A将会完全被隐藏,此时A完全不可见,A的onStop()方法就会被执行。

问题一:为什么要先执行A的onPause方法,再执行B的生命周期方法? 
问题二:为什么不是执行完A的onStop方法之后再执行B的生命周期方法?
要先停止A中的操作,比如播放的音乐等等,不然切换到B音乐还在播放。
若是先执行完A的onStop方法之后再执行B的生命周期方法,会出现黑屏的现象。

     生命周期回调的确定的顺序允许管理两个Activity之间的切换信息。例如,当第一个Activity终止时你必须把数据写到数据库中以便下一个Activity能够读取它,那么你就应该在onPause()方法执行期间把数据写入数据库,而不是在onStop()方法执行期间。  

     我们注意到Activity B是在Activity A的onPause()方法执行完之后才执行自己的生命周期的,如果A的onPause()中有非常耗时的代码,那么将会影响到B的启动速度。因此,我们要在onPause()中做尽可能少的工作来提高页面的切换速度。 

Service的使用

定义一个Service

每一个服务都需要在AndroidManifest.xml文件中注册,注册形式如下

<service android:name=".MyService" >
</service>

启动和停止服务

构建Intent对象调用startService()和stopService()来执行和停止MyService,也可以任意位置调用stopSelf()方法让服务停止

Intent startIntent = new Intent(this, MyService.class);
startService(startIntent); // 启动服务

Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务

服务生命周期

  1. 启动类型的服务: onCreate()- >onStartCommand()->Service running–调用context.stopService() ->onDestroy()
  2. 绑定类型的服务: onCreate()->onBind()->Service running–调用>onUnbind() -> onDestroy()

Content providers的使用

基本概念:

1.ContentProvider提供为存储和获取数据提供了统一的接口

2.使用ContentProvider可以在不同的应用程序之间共享数据

3.Android为常见的一些数据提供了ContentProvider

在安卓系统中,各应用之间的数据信息是无法直接传递共享的,因此,系统提供了一个组件,用来把系统私有的数据库暴漏给其他应用使用,这就是内容提供者

Camera APP方向

Android App 主线程介绍

​ 一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。

主线程:也叫UI线程,或称ActivityThread,用于运行四大组件和处理他们用户的交互。 ActivityThread管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。ActivityThread既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。

子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。(安卓3.0以后要求耗访问网络必须在子线程种执行) 更新UI的工作必须交给主线程,子线程在安卓里是不允许更新UI的。ActivityThread在Android中它就代表了Android的主线程,但是并不是一个Thread类。严格来说,UI主线程不是ActivityThread。ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,Main Thread实例是存在的,只是创建它的代码我们不可见。ActivityThread的main函数就是在这个Main Thread里被执行的。

1.Java程序初始类中的main()方法,将作为该程序初始线程的起点,任何其他的线程都是由这个初始线程启动的。这个线程就是程序的主线程。

2.在Thread.java文件头部的说明中,有这样的介绍:Each application has at least one thread running when it is started, the main thread, in the main {@link ThreadGroup}.

ActivityThread功能

它管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),并根据AMS的要求(通过IApplicationThread接口,AMS为Client、ActivityThread.ApplicationThread为Server)负责调度和执行activities、broadcasts和其它操作。

在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的【主线程】负责执行。    在Android系统中,如果有特别指定(通过android:process),也可以让特定组件在不同的进程中运行。无论组件在哪一个进程中运行,默认情况下,他们都由此进程的【主线程】负责执行。

【主线程】既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。

【主线程】的主要责任:

• 快速处理UI事件。而且只有它才处理UI事件, 其它线程还不能存取UI画面上的对象(如TextView等),此时, 主线程就叫做UI线程。基本上,Android希望UI线程能根据用户的要求做出快速响应,如果UI线程花太多时间处理后台的工作,当UI事件发生时,让用户等待时间超过5秒而未处理,Android系统就会给用户显示ANR提示信息。         只有UI线程才能执行View派生类的onDraw()函数。

• 快速处理Broadcast消息。【主线程】除了处理UI事件之外,还要处理Broadcast消息。所以在BroadcastReceiver的onReceive()函数中,不宜占用太长的时间,否则导致【主线程】无法处理其它的Broadcast消息或UI事件。如果占用时间超过10秒, Android系统就会给用户显示ANR提示信息。

注意事项:

• 尽量避免让【主线程】执行耗时的操作,让它能快速处理UI事件和Broadcast消息。

• BroadcastReceiver的子类都是无状态的,即每次启动时,才会创建其对象,然后调用它的onReceive()函数,当执行完onReceive()函数时,就立即删除此对象。由于每次调用其函数时,会重新创建一个新的对象,所以对象里的属性值,是无法让各函数所共享。

Android handler 消息机制介绍

​ 为了避免ANR,我们会通常把 耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新的UI的时候就需要借助到安卓的消息机制,也就是Handler机制了。

​ 注意:在安卓的世界里面,当 子线程 在执行耗时操作的时候,不是说你的主线程就阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是Thread.sleep()。安卓采取的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。

基本概念

​ 什么是消息机制? —— 不同线程之间的通信。

什么是安卓的消息机制,就是 Handler 运行机制。

安卓的消息机制有什么用? —— 避免ANR(Application Not Responding) ,一旦发生ANR,程序就挂了,奔溃了。

什么时候会触发ANR?(消息机制在什么时候用?) —— 以下两个条件任意一个触发的的时候就会发生ANR在activity中超过5秒的时间未能响应下一个事件BroadcastReceive超过10未响应造成以上两点的原因有很多,比如网络请求, 大文件的读取, 耗时的计算等都会引发ANR

如何避免ANR

首先明白两点:

主线程不能执行耗时操作(避免ANR)

子线程不能直接更新UI界面

结合起来这两点的解决办法是:把耗时操作放到子线程去执行,然后使用Handler去更新UI

为什么系统不允许子线程更新UI

因为的UI控件不是线程安全的。

如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那为什么不对UI控件的访问加上 上锁机制 呢?

因为有这么两个缺点:上锁会让UI控件变得复杂和低效上锁后会阻塞某些进程的执行对于手机系统来说,这两个缺点是不可接受的,所以最简单高效的方法就是 —— 采用单线程模型来处理UI操作。对开发者而言也不是很麻烦,只是通过Handler切换一下访问的线程的就好

Handler的简单使用

既然子线程不能更改界面,那么我们现在就借助Handler让我们更改一下界面:

主要步骤是这样子的:

1、new出来一个Handler对象,复写handleMessage方法

2、在需要执行更新UI的地方 sendEmptyMessage 或者 sendMessage

3、在handleMessage里面的switch里面case不同的常量执行相关操作

Handler消息机制的分析理解

安卓的异步消息处理机制就是handler机制主线程,ActivityThread被创建的时候就会创建Looper

Looper被创建的时候创建MessageQueue。 也就是说主线程会直接或者间接创建出来Looper和MessageQueue。

Handler的工作机制简单来说是这样的

1、Handler发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue

2、Looper不断轮询调用MeaasgaQueue的next方法

3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用 handlerMessage()

Android App 多线程实现方式介绍

Android提供了四种常用的操作多线程的方式,分别是:

  1. Handler+Thread
  2. AsyncTask
  3. ThreadPoolExecutor
  4. IntentService

Handler+Thread

​ Android主线程包含一个消息队列(MessageQueue),该消息队列里面可以存入一系列的Message或Runnable对象。通过一个Handler你可以往这个消息队列发送Message或者Runnable对象,并且处理这些对象。每次你新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列,从这时起,这个handler就会开始把Message或Runnable对象传递到消息队列中,并在它们出队列的时候执行它们。

优缺点

  1. Handler用法简单明了,可以将多个异步任务更新UI的代码放在一起,清晰明了
  2. 处理单个异步任务代码略显多

AsyncTask

​ AsyncTask通过一个阻塞队列BlockingQuery存储待执行的任务,利用静态线程池THREAD_POOL_EXECUTOR提供一定数量的线程,默认128个。在Android 3.0以前,默认采取的是并行任务执行器,3.0以后改成了默认采用串行任务执行器,通过静态串行任务执行器SERIAL_EXECUTOR控制任务串行执行,循环取出任务交给THREAD_POOL_EXECUTOR中的线程执行,执行完一个,再执行下一个。

​ 优缺点1.方便实现异步通信不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合节省资源

​ 2.采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销

​ 3.处理多个异步任务代码显得较多

注意:

1.AsyncTask不与任何组件绑定生命周期,在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的onDestory()调用 cancel(boolean);

2.若AsyncTask被声明为Activity的非静态内部类,当Activity需销毁时,会因AsyncTask保留对Activity的引用 而导致Activity无法被回收,最终引起内存泄露;

3.当Activity重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的AsyncTask(非静态的内部类)持有的之前Activity引用已无效,故复写的onPostExecute()将不生效,即无法更新UI操作,在Activity恢复时的对应方法 重启 任务线程。

ThreadPoolExecutor

ThreadPoolExecutor提供了一组线程池,可以管理多个线程并行执行。这样一方面减少了每个并行任务独自建立线程的开销,另一方面可以管理多个并发线程的公共资源,从而提高了多线程的效率。所以ThreadPoolExecutor比较适合一组任务的执行。Executors利用工厂模式对ThreadPoolExecutor进行了封装,使用起来更加方便。

特点:1.避免线程频繁创建消毁2.避免系统资源紧张3.更好地管理线程

IntentService

IntentService继承自Service,是一个经过包装的轻量级的Service,用来接收并处理通过Intent传递的异步请求。客户端通过调用startService(Intent)启动一个IntentService,利用一个work线程依次处理顺序过来的请求,处理完成后自动结束Service。特点:1.IntentService 是继承自 Service 并处理异步请求的一个类,在 IntentService 内有一个工作线程来处理耗时操作。2.当任务执行完后,IntentService 会自动停止,不需要我们去手动结束。3.如果启动 IntentService 多次,那么每一个耗时操作会以工作队列的方式在 IntentService 的 onHandleIntent 回调方法中执行,依次去执行,使用串行的方式,执行完自动结束。

进程与线程

进程是资源分配的基本单位(划分地盘),线程是处理器调度的基本单位(调度CPU等)。线程可以启动、撤销另一个线程,一个进程可以有多个线程抢占式式并发执行,每一个线程在执行中都可能被挂起,腾出处理器来执行另一个线程。线程不能独立执行,线程依赖于进程,一个线程必然有一个父进程,一个程序至少有一个进程,一个进程至少有一个主线程,同一个父进程的线程共享该进程资源(地盘)。线程之间是独立的,一个线程不知道父进程中是否有其他的线程存在。调动使用CPU的是线程,在处理器运行的是线程。同一进程的多个线程共享全局变量、静态变量,堆存储,每一个线程也有自己栈段,用来存储局部或临时变量。

线程的生命周期

包括:新建、就绪、运行、阻塞、死亡这几个状态。

1.线程只能调用start方法,不能调用run方法,调用run方法只是将thread对象当作一个普通的对象来运行。

2.除了新建状态外,其他状态不能再调用start方法,否则会抛出IllegalThreadStateException异常。3.Thread.sleep()方法会使当前正在被执行的线程睡眠,让CPU可以去启动另一个处于就绪的线程,但是该方法在睡眠期间被唤醒会抛出InterruptedException异常。(上文停止线程也用到该原理)

4.当sleep时间结束后、调用的IO方法等待返回结果时、获取同步锁对象时、挂起的线程调用了resume方法时该线程就出于就绪状态,等待CPU调度。主线程执行完之后,不影响其他线程的运行,每一个线程运行起来后,级别相同。

5.线程分守护线程和普通线程,守护线程是JVM自己使用的线程,如垃圾回收线程,用户创建的线程基本上为普通线程,也可以自动设置Thread对象的setDaemon(true)将线程改为守护线程(在start之前调用)。Thread对象的isDaemon方法可以查询是否时守护线程。

6.用户创建的线程设置为守护线程后,不管是否执行完,当进程中所有普通线程执行完,整个进程也就结束。7.Thread对象的setPriority(int newPriority)可以设置线程优先级,范围从0~10,值越大,级别越高,级别越高,执行的机会就越多,不设置的话,默认级别和其父进程级别相同。

线程sleep和yield方法区别

1.sleep方法会让线程进入阻塞状态,在其睡眠时间内,该线程不会被执行。

2.yield方法只是将线程处于就绪状态,可能调度器又立马调度了该线程。

3.yield方法暂停后,和该线程优先级相同或者高的线程优先执行。

4.sleep之后,其他线程执行的机会有JVM决定,不一定就是高优先级的。

5.yield不会抛出异常,sleep会抛出InterruptedException异常

sleep和wait的区别

1.sleep是Thread的静态方法、wait是Object的方法;

2.sleep不释放锁对象,wait放弃锁对象;

3.sleep暂停线程,但监控状态仍然保持,结束后自动恢复;

4.wait、notify和notifyAll只能在同步控制方法控制块里面使用,而sleep可以在任意地方使用;

5.wait方法导致线程放弃对象锁,只有针对此对象发出notify(或notifyAll)后才进入对象锁定池准备重新获得对象锁,然后进入就绪状态,准备运行。

synchronized

同步的时候,是以降低运行效率的方式来保证线程安全的,为此,不要对线程使用类中没必要的方法、对象进行同步标识,只对有竞争的资源或者代码进行同步标识。同步标识后,有以下几点可以释放该锁:代码块、方法执行完毕(正常完毕、return或break、抛出异常)调用了wait方法,使得当前线程暂停。当线程执行到同步代码块时,sleep、yield方法不会释放该同步锁,挂起方法suspend也不会(线程操作过程中尽量避免使用suspend、resume来操作线程状态,容易导致死锁。)

同步锁Lock

synchronized是java中的一个关键词,也提到了在sleep的时候、进行IO操作的时候该线程不会释放线程锁,其他线程就需要一直等待,这样有时会降低执行的效率,所以就需要一个可以在线程阻塞时可以释放线程锁的替代方案,Lock就是为了解决这个问题出现的。

Lock接口的一个实现子类为ReentrantLock,在java.util.concurrent.locks包下

Lock的使用方法

1.lock() 用来获取锁,如果该锁被其他线程占用,则进入等待。

​ Lock lock = new ReentrantLock(); public void test(){ lock.lock(); lock.unlock(); }

  1. tryLock()尝试获取锁,如果获取成功返回true,如果失败,则返回false,不会进入等待状态。

  2. tryLock(long time, TimeUnit unit)可以设置拿不到锁的时候等待一段时间。//第一个参数时常长,第二个参数时间单位

  3. lockInterruptibly()通过该方法获取锁时,如果该锁正在被其他线程持有,则进入等待状态,但是这个等待过程是可以被中断的,通过调用Thread对象的interrupt方法就可中断等待,中断时抛出异常InterruptedException,需要捕获或者声明抛出。

synchronized和Lock对比

两者都是可重入锁

可重入锁是指当一个线程获得对象锁之后,该线程可以再次获取该对象的锁而不被阻塞。比如同一个类中有多个方法(或一个方法递归调用)被synchronized修饰或者被Lock加持后,同一个线程在调用这两个方法时都可以获取该对象的锁而不被阻塞。synchronized实现上也基本是采用记数器的方法来实现可重入的。

Lock是可中断锁,synchronized不可中断。当一个线程B执行被锁的对象的代码时,发现线程A已经持有该锁,那么线程B就会进入等待,但是synchronized就无法中断该等待过程,而Lock就可以通过lockInterruptibly方法抛出异常从而中断等待,去处理别的事情。

Lock可创建公平锁,synchronized是非公平锁。公平锁的意思是按照请求的顺序来获取锁,不平公锁就无法保证线程获取锁的先后次序。

Lock可以知道是否获取到锁,synchronized不可以。

synchronized在发生异常或者运行完毕,会自动释放线程占有的锁。而Lock需要主动释放锁,否则会锁死;synchronized在阻塞时,别的线程无法获取锁,Lock可以(这也是lock设计的一个目的)。总结如果需要效率提升,则建议使用Lock,如果效率要求不高,则synchronized满足使用条件,业务逻辑写起来也简单,不需要手动释放锁。

Thread/Runnable

在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口;

Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限。

Thread和Runnable的不同

l Thread是类,类只能单继承

l Runnable是接口,接口可以多实现

l Runnable可以进行数据共享。

实现 Runnable 接口相对于继承 Thread 类来说,有如下显著的优势:

  1. 适合多个相同程序代码的线程去处理同一资源的情况,把虚拟 CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。

  2. 可以避免由于 Java 的单继承特性带来的局限。开发中经常碰到这样一种情况,即:当要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承 Thread 类的方式,那么就只能采用实现 Runnable 接口的方式了。

  3. 增强了程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。

  4. 避免点继承的局限,一个类可以继承多个接口。

  5. 适合于资源的共享,能多个线程同时处理一个资源。

当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程可以操作相同的数据,与它们的代码无关。当共享访问相同的对象时,即共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现了

Runnable 接口的类的实例。

​ 事实上,几乎所有多线程应用都可用第二种方式,即实现 Runnable 接口。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值