多线程一直是个老大难问题。首先,很难理解。其次,我们在实际工作中需要面对的线程安全问题并不常见。今天我们就来总结一下多线程的几种实现方式。有兴趣的可以看看。
目录
前言
最基本的方法
继承Thread类并实现run()方法
匿名内部类实现了Runnable接口callable FutureTask。
线程池
手动创建线程池
使用执行器创建线程池
Android中多线程的独特实现
使用句柄线程
使用IntentService
JobIntentService/JobScheduler
工作管理器
工作管理器使用协作。
异步任务
标签
序
多线程一直是个老大难问题。首先,很难理解。其次,我们在实际工作中需要面对的线程安全问题并不常见。今天我们就总结几种实现多线程的方法,可能不全面。请读者补充更多。
最基本的方法
继承Thread类并实现run()方法
类MyThread扩展线程{
@覆盖
公共无效运行(){
System.out.println('我是子线程');
}
}
新的MyThread()。start();
匿名内部类
公共类多线程{
公共静态void main(String[] args) {
新线程(){
公共无效运行(){
System.out.println('我是子线程');
};
}.start();
}
}
实现Runnable接口
MyRunnable类实现Runnable{
@覆盖
公共无效运行(){
System.out.println('我是子线程');
}
}
Thread Thread=new Thread(new my runnable());
thread . start();
太简单了!别担心,只是热身而已~
可赎回的未来任务
Callable接口类似于Runnable,只是你可以通过使用Callable得到结果。
FutureTask实现RunnableFuture,
公共类FutureTaskV实现RunnableFutureV {
.
}
RunnableFuture又实现了Runnable和Future。
公共接口RunnableFutureV扩展Runnable,FutureV {
/**
*将此未来设置为其计算结果
*除非已经取消。
*/
空运行();
}
因此,FutureTask既可以用作Runnable,也可以用作Future来获得结果。
Future的API:get():任务完成后,返回一个结果。如果任务在被调用时没有完成,它将阻塞线程,直到任务完成。您可以设置等待时间。如果超过等待时间或者返回的结果为null,将引发异常cancel()。
//自定义CallAble并实现call()方法
MyCallable类实现CallableInteger{
@覆盖
公共整数调用()引发异常{
int a=1;
for(int I=0;我100000;i ) {
a;
}
线程.睡眠(2000年);
返回a;
}
}
公共静态void main(String[] args) {
//创建新的可调用
my callable callable=new my callable();
//创建新的未来任务
FutureTaskInteger futureTask=new FutureTaskInteger(callable);
//创建一个新线程并将其与FutureTask关联
Thread Thread=new Thread(future task,' A ');
thread . start();
int sum
尝试{
//获取callable的结果
sum=future task . get();
system . out . println(sum);
} catch (InterruptedException e) {
e . printstacktrace();
} catch (ExecutionException e) {
e . printstacktrace();
}
}
线程池
有两种方法可以创建线程池,
手动创建线程池
公共ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
长keepAliveTime,
时间单位,
BlockingQueueRunnable工作队列,
线程工厂线程工厂,
RejectedExecutionHandler处理程序)
注意线程池的几个参数:
CorePoolSize:核心线程的数量
MaximumPoolSize:允许创建的最大线程数。
KeepAliveTime:这是空闲线程可以保持活动的最长时间。
单位:保活时间的时间单位。
工作队列:任务队列,它保存等待执行的阻塞任务队列。
ThreadFactory:用于设置创建线程的工厂。您可以通过线程工厂为每个创建的线程设置一个更有意义的名称。
Handler:饱和策略。当队列和线程池满了,就意味着线程处于饱和状态,必须采用一种策略来处理提交的新任务。默认值为AbortPolicy,这意味着当无法处理新任务时会引发异常。
提交任务execute()和Submit()到线程池:
ThreadPoolExecutor ThreadPoolExecutor=new ThreadPoolExecutor(2,2,1,TimeUnit。秒,new LinkedBlockingDequeRunnable());
//使用execute提交任务
threadPoolExecutor.execute(新的Runnable() {
@覆盖
公共无效运行(){
System.out.println('我是线程池');
}
});
//前面提到的callable
my callable callable=new my callable();
//使用submit提交任务返回一个未来。
future integer future=threadpoolexecutor . submit(可调用);
尝试{
//通过future获取执行callable的结果
int x=future . get();
system . out . println(x);
} catch (InterruptedException e) {
//中断异常
e . printstacktrace();
} catch (ExecutionException e) {
//无法执行任务异常
e . printstacktrace();
}最后{
//关闭线程池
threadpoolexecutor . shut down();
}
使用自定义线程池合理配置各个参数。
根据任务的性质:cpu密集型还是IO密集型,设置核心线程的数量。CPU密集型应该配置最小的线程,可以配置CPU数量为1的线程。由于IO密集型任务线程并不总是执行任务,所以应该配置尽可能多的线程,可以配置为2*cpu数量。
具有不同优先级的任务可以通过使用优先级队列PriorityBlockingQueue来处理,这允许具有高优先级的任务首先被执行。
建议使用有界队列,这样可以增加系统的稳定性和预警能力。
使用执行器创建线程池
这种创建方法本质上就是对ThreadPoolExecutor的参数进行不同的配置,每次创建后,源代码中不同的配置都放在后面,这样可以更清晰的显示出来。
//将根据需要创建新线程的线程池
ExecutorService newCachedThreadPool=executors . newCachedThreadPool();
//核心线程数设置为0,最大线程数是无界的,这意味着在某些情况下,会创建无限个线程,从而导致一些问题。空闲线程的最大等待时间是60秒。
public static ExecutorService newCachedThreadPool(){
返回新的ThreadPoolExecutor(0,整数。最大值,
60L,时间单位。秒,
new SynchronousQueueRunnable());
}
//使用单个工作线程
ExecutorService newSingleThreadExecutor=executors . newSingleThreadExecutor();
//核心线程数和最大线程数都设置为1,使用无界队列作为线程池的工作队列,具有无限的容量,并且在某些情况下,队列中的任务太多会出错。
public static ExecutorService newSingleThreadExecutor(){
返回新的FinalizableDelegatedExecutorService
(新的ThreadPoolExecutor(1,1,
0L,时间单位。毫秒,
new LinkedBlockingQueueRunnable()));
}
//具有固定数量线程的可重用线程池
ExecutorService newFixedThreadPool=executors . newFixedThreadPool(10);
//核心线程数量和最大线程数量都设置为创建时传入的数量。空闲等待时间为0,这意味着空闲线程将被立即终止,任务队列的容量是无限的,这可能会导致同样的问题。
public static ExecutorService newFixedThreadPool(int nThreads){
返回新的ThreadPoolExecutor(nThreads,nThreads,
0L,时间单位。毫秒,
new LinkedBlockingQueueRunnable());
}
//可用于在给定延迟后执行异步任务或周期性任务。
ScheduledExecutorService newScheduledThreadPool=executors . newScheduledThreadPool(2);
//核心线程数是指定的数,允许的最大线程数是无限制的。
public ScheduledThreadPoolExecutor(int corePoolSize,
线程工厂线程工厂,
RejectedExecutionHandler处理程序){
超级(corePoolSize,整数MAX_VALUE,0,纳秒,
new DelayedWorkQueue()、threadFactory、handler);
}
机器人中特有的实现多线程
使用手柄线程
手柄螺纹本质上就是一个线程,其继承了线程。它的内部有自己的尺蠖对象,并在奔跑方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()开启消息循环,这样在使用中就允许在手柄线程中创建处理者了
手柄线程和普通的线是有显著的区别的,普通的线主要在奔跑方法中执行一个耗时任务,而手柄线程在内部创建了消息队列,外界需要通过处理者的消息方式来通知手柄线程执行一个具体的任务,由于手柄线程的奔跑方法是一个无限循环,因此当明确不需要再使用手柄线程时,可以通过其放弃或者安全地离开方法来终止线程的执行。
公共类处理程序线程扩展线程{
活套
private @Nullable处理程序mHandler
@覆盖
公共无效运行(){
mTid=进程。mytid();
looper。prepare();
同步(这){
m looper=looper。我的looper();
通知所有();
}
过程。setthreadpriority(m优先级);
onLooperPrepared();
looper。loop();
mTid=-1;
}
.
}
具体使用:
//创建手柄线程,标记一个名字
HandlerThread HandlerThread=new HandlerThread(' MyHandlerThread ');
//启动线程
处理程序线程。start();
//创建工作线程的处理者,关联手柄线程的尺蠖对象。
//复写处理消息处理消息
Handler my Handler=new Handler(处理程序线程。get looper()){
@覆盖
公共void句柄消息(@ NonNull Message msg){
日志。我(味精。什么味精。obj。tostring());
超级棒。处理消息(msg);
}
};
//定义消息
Message msg=new Message();
味精。what=1;
msg.obj=' A
//发送消息到其绑定的消息队列中
我的负责人。发送消息(msg);
//结束线程,停止线程的消息循环
处理程序线程。quit();
使用IntentService
本质上是一个服务,可以看做是服务和手柄线程的结合体,它继承了服务并且是一个抽象类,因此需要创建它的子类才能使用内部服务。可以用于执行后台耗时任务,由于是服务,所以它的优先级比单纯的线程要高很多,并且不容易被系统杀死。有如下几个特点
IntentService是继承自服务并处理异步请求的一个类,其内部有一个工作线程处理耗时请求
任务执行完毕,会自动销毁
如果IntentService启动多次,每个耗时操作会以队列的方式在IntentService的onHandleIntent方法中依次执行,串行执行结束后,会自动销毁。
首先看一下IntentService的源码:
//继承自服务
公共抽象类IntentService扩展服务{
私有易变活套
@UnsupportedAppUsage
私有易失性ServiceHandler mServiceHandler
私有最终类服务处理程序扩展处理程序{
公共服务处理器(活套活套){
超级(looper);
}
@覆盖
公共void handleMessage(消息消息){
//处理消息时调用onHandleIntent方法我们需要重写这个方法实现自己的处理逻辑
onHandleIntent((Intent)msg。obj);
停止自我(消息。arg 1);
}
}
.
@覆盖
public void onCreate() {
超级棒。oncreate();
//创建手柄线程并启动
处理程序线程thread=新的处理程序线程(' intent service[' mName ']');
线程。start();
mServiceLooper=thread。get looper();
//初始化处理者并绑定手柄线程的尺蠖
mServiceHandler=新服务处理程序(mServiceLooper);
}
@覆盖
public void onStart(@ Nullable Intent Intent,int startId) {
message msg=mservicehandler。获取消息();
msg.arg1=startId
msg.obj=意图
//通过处理者发送消息给手柄线程处理
mservicehandler。发送消息(msg);
}
.
当IntentService被启动时,它的onCreate方法被调用,onCreate方法会创建一个手柄线程,然后使用它的尺蠖来构造一个处理者对象,这样通过处理者发送的消息最终会在手柄线程中执行。每次启动IntentService,它的onStartCommand方法就会调用一次,IntentService在onStartCommand中处理每个后台任务的互联网。onStartCommond调用了onStart,在onStart方法中,IntentService通过处理者发送一个消息,这个消息会在手柄线程中处理。当onHandleIntent方法执行结束后,IntentService会通过停止自我方法尝试停止服务,在HandlerIntent上是一个抽象方法,需要我们在子类中实现,它的作用是从目的参数中区分具体的任务并执行这些任务。
如何使用?
新建服务类并继承自IntentService
实现服务的构造方法
在manifast.xml中注册服务
在服务的onHandleIntent方法中实现业务逻辑
自定义一个IntentService
公共类MyIntentService扩展IntentService {
公共MyIntentService(字符串名称){
超级(名);
}
@覆盖
受保护的void onHandleIntent(@ Nullable Intent Intent){
如果(意图!=null){
字符串动作=意图。get action();
如果(行动。等于(' A '){
doSomeThingA();
} else if(动作。等于(' B '){
doSomeThingB();
}
}
}
}
JobIntentService/JobScheduler
系统不允许后台应用创建后台服务,因此8.0引入了Context.startForegroundService(),以在前台启动新服务,
由于安卓O的后台限制,创建后台服务需要使用作业调度程序来由系统进行调度任务的执行,而使用作业调度程序的方式比较繁琐,8.0及以上提供了JobIntentService帮助开发者更方便的将任务交给作业调度程序调度,其本质是服务后台任务在他的OnhandleWork()中进行,子类重写该方法即可。使用较简单。
公共类SimpleJobIntentService扩展JobIntentService {
/**
* 这个服务唯一的身份证明(识别)
*/
静态最终int JOB _ ID=1000
/**
* 将工作加入此服务的方法,使用中调用这个方法
*/
静态无效排队工作(上下文上下文,意图工作){
enqueueWork(context,SimpleJobIntentService.class,JOB_ID,work);
}
//在这里执行耗时任务
@覆盖
受保护的void onHandleWork(意向意向){
Log.i('SimpleJobIntentService ','执行工作:‘意图’);
字符串标签=意图。getstring extra(' label ');
if (label==null) {
标签=意图。tostring();
}
吐司(’正在执行: '标签');
for(int I=0;i5;i ) {
Log.i('SimpleJobIntentService ','运行服务(一1)
/5 @ '系统时钟。elapsedrealtime());
尝试{
线程。睡眠(1000);
} catch (InterruptedException e) {
}
}
Log.i('SimpleJobIntentService ',' Completed service @ '系统时钟。elapsedrealtime());
}
@覆盖
public void onDestroy() {
超级棒。on destroy();
吐司(“所有工作完成");
}
final Handler mHandler=new Handler();
//显示测试的帮助器
无效吐司(最终字符序列文本){
mHandler.post(新的Runnable() {
@Override public void run() {
吐司。生成文本(simplejobintentservice。这个,短信,祝酒词.LENGTH_SHORT).show();
}
});
}
}
活动通过调用enqueueWork()方法启动。
Intent work Intent=new Intent();
num
Log.d('houson ',' onClick:' num ');
workIntent.putExtra('work ',' work num:' num ');
//调用enqueueWork方法
myjobintentservice . enqueue work(getApplicationContext()、work intent);
工作管理器
根据不同的需求,Android为后台任务提供了多种解决方案,如JobScheduler、Loader、Service等。如果这些API使用不当,它们可能会消耗大量功率。WorkManager的出现为应用中那些不需要及时完成的任务提供了统一的解决方案,从而在设备功率和用户体验之间达到更好的平衡。
工作管理器至少可以与API级兼容。在API级别23,使用JobScheduler完成任务,23以下的设备结合AlarmManager和BroadCastReceivers完成任务。
工作主要针对不需要及时完成的任务,比如发送日志、同步应用数据、备份等。而且它可以保证任务会被执行,即使应用程序当前没有运行,WorkManager也有自己的数据库,所有关于任务的信息和数据都存储在这个数据库中。所以,只要你的任务交给了WorkManager,即使你的应用完全退出或者设备重启,WorkManager依然可以保证你被分配任务的完成。
怎么用?
添加依赖关系
依赖关系{
定义版本='2.2.0 '
实现“androidx . work:work-runtime:$ versions”
}
定义员工任务:
Worker是任务的执行者,是一个抽象类,需要继承才能实现要执行的任务。
//继承worker并自定义worker
类MyWork扩展Worker{
public MyWork(@ NonNull Context Context,@ NonNull worker parameters worker params){
super(context,worker params);
}
//执行耗时的任务
@NonNull
@覆盖
公共结果doWork() {
//获取传入的数据
String input_data=getInputData()。getString(' input _ data ');
//任务执行后返回数据。
数据输出数据=新数据。构建器()。putString('output_data ',' Success!').build();
//成功执行返回Result.success()
//执行失败返回Result.failure()
//需要重新执行才能返回Result.retry()
返回result . success(output data);
}
}
使用WorkRequest配置任务。
WorkRequest指定由哪个工人来执行任务、执行环境、执行顺序等。使用它的子类OneTimeWorkRequest(执行一次)或PeriodicWorkRequest(定期执行)。
配置我们的任务何时以及如何通过WorkRequest运行。
//设置任务的触发条件
约束约束=新约束。构建器()。setRequiresCharging(true)///在充电时执行。setRequireStorAgentLow(true)//存储容量不足时不执行。
//网络状态
//not _ required-没有要求。
///已连接-网络连接
///未计量-连接无限流量的网络。
///计量-连接到按流量收费的网络。
//not _ roaming-连接到非漫游网络。setRequiredNetworkType(网络类型。已连接)。setRequiresDeviceIdle(true)//API 23需要在待机模式下执行。setrequires batterynotlow(true)//电池电量低时不执行。build();
//然后将约束设置为WorkRequest。WorkRequest是一个抽象类,
//它有两个实现,OneTimeWorkRequest和PeriodicWorkRequest,分别对应一次性任务和周期性任务。
//定义要传递的数据
数据输入数据=新数据。构建器()。putString('input_data ',' Hello ')。build();
OneTimeWorkRequest OneTimeWorkRequest=new OneTimeWorkRequest。构建器(MyWork.class)。设置约束(约束)。设置输入数据(输入数据)。build();
WorkManager
将任务提交给系统。管理任务请求和任务队列,发起的工作请求将进入其任务队列。WorkManager.enqueue()方法会将您配置的工作请求发送给系统执行。
WorkManager.getInstance(this)。enqueue(oneTimeWorkRequest);
观察任务的状态。
任务提交给系统后,通过WorkInfo可以知道任务的状态,work info包含任务的id和标记、Worker对象传递的outputData以及任务的当前状态。有三种方法可以获得WorkInfo对象。
通过LiveData,我们可以在任务状态发生变化时得到通知。同时通过LiveData的WorkInfo.getOutputData()获取Worker传来的数据。
WorkManager.getInstance(this)。getWorkInfoByIdLiveData(onetimeworkrequest . getid())。观察(
MainActivity.this,new ObserverWorkInfo() {
@覆盖
公共void on changed(work info work info){
Log.d('onChanged()-',' workInfo:' workInfo ');
如果(工作信息!=null work info . getstate()==work info。状态.成功){
string output data=work info . get output data()。getString(' output _ data ');
Log.d('onChanged()-',' output data:' output data ');
}
}
}
);
取消任务
work manager . getinstance(main activity . this)。cancel allwork();
这些是工作管理器的一些基本用法。更多细节可以参考官方文件或者其他文章,这里就不解释了。在工作中,我们经常需要处理后台任务。如果用于处理后台任务的API使用不当,很可能会消耗大量设备的电量。考虑到Android设备的强大,为开发者提供了WorkManager,旨在给它一些不需要及时完成的任务。
利用协同效应
协同过程是一种并行设计模式。你可以在Android平台上使用它来简化异步执行的代码。协同进程是一个轻量级线程。为什么是轻量级的?因为它是基于线程池API的。协同进程可以用阻塞的方式写非阻塞的代码来解决回调到地狱的问题。
协成有以下特点:
轻量级:您可以在一个线程上运行多个协同程序,因为协同程序支持挂起,这不会阻塞运行协同程序的线程。比分块节省内存,支持多个并行操作。
内存泄漏少:使用结构化并发机制在一个作用域内执行多个操作。
对取消的内置支持:取消操作将自动在运行的协作层次结构中传播。
Jetpack集成:许多Jetpack库包含提供全面协作支持的扩展。一些库还提供了它们自己的协作范围,您可以将它们用于结构化并发。
介绍协调过程
依赖关系{
实现(' org . jetbrains . kot linx:kot linx-coroutines-Android:1 . 3 . 9 ')
}
使用
fun main()=runBlocking
启动{
延迟(1000升)
println('世界!')
}
println(“你好”)
}
当然,协成的使用没那么简单。API有很多,这里篇幅有限,就不深入讲解了。
异步任务
它是一个轻量级的异步任务类,在高版本中已经过时。但研究它还是有意义的。他可以在线程池中执行后台任务,然后将执行的进度和最终结果传递给主线程,并在主线程中更新ui。AsyncTask封装了线程和处理程序,使得在主线程中执行后台任务和访问ui更加容易。
AsyncTask提供了四种核心方法:
OnPreExecute:在主线程中执行。这个方法会在异步任务执行前被调用,一般用于一些准备工作。
DoInBackground:在线程池中执行。该方法用于执行异步任务,params参数表示异步任务的输入参数。在这种方法中,任务的进度可以通过publishProgress来更新。
OnProgressUpData(进度.values):在主线程中执行,后台任务执行进度变化时调用该方法。
onPostExecute:在主线程中执行,在异步任务执行之后,此方法会被调用,结果参数是后台任务的返回值,即背景音乐的返回值
异步任务在使用过程中有一些限制条件:
异步任务必须在主线程中加载,也就是第一次访问异步任务必须发生在主线程
异步任务的对象须在主线程中创建
执行方法必须在用户界面线程调用
不要在程序中直接调用onPreExecute、onPostExecute、doInBackground、onProgressUpdate方法
一个异步任务对象只能执行一次,也就是只能调用一次执行方法,否则会报异常
原理分析:
从执行方法开始分析,调用执行程序方法,sDefaultExecutor是一个串行的线程池如下:
@主线程
公共最终AsyncTaskParams,进度,结果执行(参数.params) {
返回executeOnExecutor(sDefaultExecutor,params);
}
@主线程
public final AsyncTaskParams,Progress,Result executeOnExecutor(Executor exec,
参数.params) {
如果(mStatus!=状态。待定){
开关(状态){
案例运行:
引发新的IllegalStateException('无法执行任务:'
任务已经在运行。);
案件结束:
引发新的IllegalStateException('无法执行任务:'
'任务已经执行'
(一个任务只能执行一次)');
}
}
mStatus=状态。跑步;
onPreExecute();
我是工人。MP arams=params
//线程池开始执行封装为未来任务交个线程池处理
执行。执行(m未来);
还这个;
}
sDefaultExecutor是一个串行线程池,一个进程中所有的异步任务任务都在这个线程池中排队执行。首先系统会把异步任务的参数参数封装为未来任务对象,这里未来任务充当可运行,接着这个未来任务会交给连续遗嘱执行人的执行方法处理,串行执行的执行方法首先把未来任务对象插入到任务队列中,接着调用计划下一个方法执行未来任务任务,从队列中取出任务交给线程池执行器线程池去真正执行任务内部处理程序用于将执行环境从线程池切换到主线程。
//串行线程池
私有静态易失性执行器sDefaultExecutor=SERIAL _ Executor;
公共静态最终执行器SERIAL _ Executor=新的串行执行程序();
//线程池用于真正执行任务
公共静态最终执行器线程池执行器
//用于将执行环境从线程池切换到主线程
私有静态内部搬运机
//初始化执行任务的线程池
静态{
ThreadPoolExecutor ThreadPoolExecutor=new ThreadPoolExecutor(
核心池大小、最大池大小、保持活动秒数、时间单位。秒,
sPoolWorkQueue,sThreadFactory);
threadpoolexecutor。allowcorethreadtimeout(true);
THREAD _ POOL _ EXECUTOR=线程池执行器;
}
//用于调度任务的串行线程池
私有静态类连续遗嘱执行人实现执行者{
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { //从队列中取出任务交个THREAD_POOL_EXECUTOR线程池去真正执行任务 if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }再看一下任务future,call方法中调用postResult方法,通过handler发送消息,最终在handleMessage方法中进行处理。
private final WorkerRunnable<Params, Result> mWorker; private final FutureTask<Result> mFuture; mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } };结语
以上介绍了几种实现多线程操作的方式,有些并没有深入分析,当然了,实现多线程的方式不止以上的几种,还请各位看官多多补充,如有错误的地方还请指出。
到此这篇关于Android中实现多线程操作的几种方式的文章就介绍到这了,更多相关Android多线程操作 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。