当前位置: 首页 > news >正文

【Service专题】

[1] 什么是服务?

Service是一个应用程序组件,它能够在后台执行一些耗时较长的操作,并且不提供用户界面。服务能被其它应用程序的组件启动,即使用户切换到另外的应用时还能保持后台运行。此外,应用程序组件还能与服务绑定,并与服务进行交互,甚至能进行进程间通信(IPC)。 比如,服务可以处理网络传输、音乐播放、执行文件I/O、或者与content provider进行交互,所有这些都是后台进行的。

[2] Service 与Thread的区别

服务仅仅是一个组件,即使用户不再与你的应用程序发生交互,它仍然能在后台运行。因此,应该只在需要时才创建一个服务。

如果你需要在主线程之外执行一些工作,但仅当用户与你的应用程序交互时才会用到,那你应该创建一个新的线程而不是创建服务。 比如,如果你需要播放一些音乐,但只是当你的activity在运行时才需要播放,你可以在onCreate()中创建一个线程,在onStart()中开始运行,然后在onStop()中终止运行。还可以考虑使用AsyncTask或HandlerThread来取代传统的Thread类。

由于无法在不同的 Activity 中对同一 Thread 进行控制,这个时候就要考虑用服务实现。如果你使用了服务,它默认就运行于应用程序的主线程中。因此,如果服务执行密集计算或者阻塞操作,你仍然应该在服务中创建一个新的线程来完成(避免ANR)。

[3] 服务的分类

1. 按运行分类

  • 前台服务

前台服务是指那些经常会被用户关注的服务,因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知,并会置于“正在进行的”(“Ongoing”)组之下。这意味着只有在服务被终止或从前台移除之后,此通知才能被解除。例如,用服务来播放音乐的播放器就应该运行在前台,因为用户会清楚地知晓它的运行情况。 状态栏通知可能会标明当前播放的歌曲,并允许用户启动一个activity来与播放器进行交互。

要把你的服务请求为前台运行,可以调用**startForeground()**方法。此方法有两个参数:唯一标识通知的整数值、状态栏通知Notification对象。例如:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());
Intent notificationIntent = new Intent(this,ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);  
startForeground(ONGOING_NOTIFICATION, notification);

要从前台移除服务,请调用**stopForeground()**方法,这个方法接受个布尔参数,表示是否同时移除状态栏通知。此方法不会终止服务。不过,如果服务在前台运行时被你终止了,那么通知也会同时被移除。

  • 后台服务

2. 按使用分类

  • 本地服务:用于应用程序内部,实现一些耗时任务,并不占用应用程序比如Activity所属线程,而是单开线程后台执行。调用Context.startService()启动,调用Context.stopService()结束。在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。

  • 远程服务:用于Android系统内部的应用程序之间,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用Context.bindService()方法建立连接,并启动,以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。

[4] Service生命周期

img

【链接:https://www.jianshu.com/p/cc25fbb5c0b3】

Service生命周期方法:

public class ExampleService extends Service {
    int mStartMode;       // 标识服务被杀死后的处理方式
    IBinder mBinder;      // 用于客户端绑定的接口
    boolean mAllowRebind; // 标识是否使用onRebind

    @Override
    public void onCreate() {
        // 服务正被创建
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 服务正在启动,由startService()调用引发
        return mStartMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // 客户端用bindService()绑定服务
        return mBinder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // 所有的客户端都用unbindService()解除了绑定
        return mAllowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // 某客户端正用bindService()绑定到服务,
        // 而onUnbind()已经被调用过了
    }
    @Override
    public void onDestroy() {
        // 服务用不上了,将被销毁
    }
}

请注意onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand()的返回值必须是以下常量之一:

START_NOT_STICKY 如果系统在onStartCommand()返回后杀死了服务,则不会重建服务了,除非还存在未发送的intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。START_STICKY 如果系统在onStartCommand()返回后杀死了服务,则将重建服务并调用onStartCommand(),但不会再次送入上一个intent, 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完,那么这些剩下的intent会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。

START_REDELIVER_INTENT 如果系统在onStartCommand()返回后杀死了服务,则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。

服务的生命周期与activity的非常类似。不过,更重要的是你需密切关注服务的创建和销毁环节,因为后台运行的服务是不会引起用户注意的。

服务的生命周期——从创建到销毁——可以有两种路径:

  • 一个started服务

这类服务由其它组件调用startService()来创建。然后保持运行,且必须通过调用stopSelf()自行终止。其它组件也可通过调用stopService() 终止这类服务。服务终止后,系统会把它销毁。

如果一个Service被startService 方法多次启动,那么onCreate方法只会调用一次,onStart将会被调用多次(对应调用startService的次数),并且系统只会创建Service的一个实例(因此你应该知道只需要一次stopService调用)。该Service将会一直在后台运行,而不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法。当然如果系统资源不足,android系统也可能结束服务。

  • 一个bound服务

服务由其它组件(客户端)调用bindService()来创建。然后客户端通过一个IBinder接口与服务进行通信。客户端可以通过调用unbindService()来关闭联接。多个客户端可以绑定到同一个服务上,当所有的客户端都解除绑定后,系统会销毁服务。(服务不需要自行终止。)

如果一个Service被某个Activity 调用 Context.bindService 方法绑定启动,不管调用 bindService 调用几次,onCreate方法都只会调用一次,同时onStart方法始终不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService 断开连接或者之前调用bindService 的 Context 不存在了(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。

img

这两条路径并不是完全隔离的。也就是说,你可以绑定到一个已经用startService()启动的服务上。例如,一个后台音乐服务可以通过调用startService()来启动,传入一个指明所需播放音乐的 Intent。 之后,用户也许需要用播放器进行一些控制,或者需要查看当前歌曲的信息,这时一个activity可以通过调用bindService()与此服务绑定。在类似这种情况下,stopService()或stopSelf()不会真的终止服务,除非所有的客户端都解除了绑定。

当在旋转手机屏幕的时候,当手机屏幕在“横”“竖”变换时,此时如果你的 Activity 如果会自动旋转的话,旋转其实是 Activity 的重新创建,因此旋转之前的使用 bindService 建立的连接便会断开(Context 不存在了)。

在manifest中声明服务

无论是什么类型的服务都必须在manifest中申明,格式如下:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

Service 元素的属性有:

android:name  -------------  服务类名

android:label  --------------  服务的名字,如果此项不设置,那么默认显示的服务名则为类名

android:icon  --------------  服务的图标

android:permission  -------  申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务

android:process  ----------  表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字

android:enabled  ----------  如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false

android:exported  ---------  表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false

android:name是唯一必需的属性——它定义了服务的类名。与activity一样,服务可以定义intent过滤器,使得其它组件能用隐式intent来调用服务。如果你想让服务只能内部使用(其它应用程序无法调用),那么就不必(也不应该)提供任何intent过滤器。   此外,如果包含了android:exported属性并且设置为"false", 就可以确保该服务是你应用程序的私有服务。即使服务提供了intent过滤器,本属性依然生效。

startService 启动服务

从activity或其它应用程序组件中可以启动一个服务,调用startService()并传入一个Intent(指定所需启动的服务)即可。

	Intent intent = new Intent(this, MyService.class);
	startService(intent);

服务类:

public class MyService extends Service {

	  /**
     * onBind 是 Service 的虚方法,因此我们不得不实现它。
     * 返回 null,表示客服端不能建立到此服务的连接。
     */
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return null;
	}
    
	@Override
    public void onCreate() {
        super.onCreate();
    }
     
    @Override
 public int onStartCommand(Intent intent, int flags, int startId)     {  	
    //接受传递过来的intent的数据 
     return START_STICKY; 
    };
     
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
	
}

一个started服务必须自行管理生命周期。也就是说,系统不会终止或销毁这类服务,除非必须恢复系统内存并且服务返回后一直维持运行。 因此,服务必须通过调用stopSelf()自行终止,或者其它组件可通过调用stopService()来终止它。

bindService 启动服务

当应用程序中的activity或其它组件需要与服务进行交互,或者应用程序的某些功能需要暴露给其它应用程序时,你应该创建一个bound服务,并通过进程间通信(IPC)来完成。

方法如下:

 Intent intent=new Intent(this,BindService.class); 
 bindService(intent, ServiceConnection conn, int flags)  

注意bindService是Context中的方法,当没有Context时传入即可。

在进行服务绑定的时,其flags有:

  • Context.BIND_AUTO_CREATE

表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁

  • Context.BIND_DEBUG_UNBIND

通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用

  • Context.BIND_NOT_FOREGROUND

表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行。

服务类:

public class BindService extends Service {

	 // 实例化MyBinder得到mybinder对象;
	private final MyBinder binder = new MyBinder();
	
	  /**
     * 返回Binder对象。
     */
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		return binder;
	}
    
     /**
      * 新建内部类MyBinder,继承自Binder(Binder实现IBinder接口),
      * MyBinder提供方法返回BindService实例。
      */
  public class MyBinder extends Binder{
        
        public BindService getService(){
            return BindService.this;
        }
    }
     

	@Override
	public boolean onUnbind(Intent intent) {
		// TODO Auto-generated method stub
		return super.onUnbind(intent);
	}
}

启动服务的activity代码:

public class MainActivity extends Activity {

	/** 是否绑定 */  
	boolean mIsBound = false; 
	BindService mBoundService;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		doBindService();
	}
	/**
	 * 实例化ServiceConnection接口的实现类,用于监听服务的状态
	 */
	private ServiceConnection conn = new ServiceConnection() {  
		  
	    @Override  
	    public void onServiceConnected(ComponentName name, IBinder service) {  
	        BindService mBoundService = ((BindService.MyBinder) service).getService();  
	        
	    }  
	  
	    @Override  
	    public void onServiceDisconnected(ComponentName name) {  
	        mBoundService = null;  
	     
	    }  
	}; 
	
	/** 绑定服务 */  
	public void doBindService() {  
	    bindService(new Intent(MainActivity.this, BindService.class), conn,Context.BIND_AUTO_CREATE);  
	    mIsBound = true;  
	}  
	
	/** 解除绑定服务 */  
	public void doUnbindService() {  
	    if (mIsBound) {  
	        // Detach our existing connection.  
	        unbindService(conn);  
	        mIsBound = false;  
	    }  
	} 
	
	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		
		doUnbindService();
	}
}

注意在AndroidMainfest.xml中对Service进行显式声明

判断Service是否正在运行:

private boolean isServiceRunning() {
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    
  {
        if ("com.example.demo.BindService".equals(service.service.getClassName()))   {
            return true;
        }
    }
    return false;
}
[5] bindService与startService区别
  1. Started Service中使用startService()方法来进行方法的调用,调用者和服务之间没有联系,即使调用者退出了,服务依然在进行 【onCreate()- >onStartCommand()->startService()->onDestroy()】,注意其中没有 onStart(),主要是被onStartCommand()方法给取代了,onStart方法不推荐使用了。
  2. BindService中使用bindService()方法来绑定服务,调用者和绑定者绑在一起,调用者一旦退出服务也就终止了【onCreate()->onBind()->onUnbind()->onDestroy()】。

采用**Context.startService()**方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。采用startService()方法启动的服务,只能调用Context.stopService()方法结束服务,服务结束时会调用onDestroy()方法。

采用**Context.bindService()**方法启动服务,在服务未被创建时,系统会先调用服务的 onCreate()方法,接着调用onBind()方法。这个时候调用者和服务绑定在一起,调用者退出了,系统就会先调用服务的onUnbind()方 法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用bindService()方法并不会导致 多次创建服务及绑定(也就是说onCreate()和onBind()方法并不会被多次调用)。如果调用者希望与正在绑定的服务解除绑定,可以调用 unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()–>onDestroy()方法。

[6] IntentService详细解析
  1. IntentService定义

IntentService继承于Service,用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。这样以免事务处理阻塞主线程(ANR)。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent请求达到,则自动停止Service;否则执行下一个Intent请求所对应的任务。     IntentService在处理事务时,还是采用的Handler方式,创建一个名叫ServiceHandler的内部Handler,并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent所对应的事务都封装到叫做onHandleIntent的虚函数;因此我们直接实现虚函数onHandleIntent,再在里面根据Intent的不同进行不同的事务处理就可以了。 另外,IntentService默认实现了Onbind()方法,返回值为null。

使用IntentService需要实现的两个方法:

  • 构造函数

IntentService的构造函数一定是参数为空的构造函数,然后再在其中调用super(“name”)这种形式的构造函数。因为Service的实例化是系统来完成的,而且系统是用参数为空的构造函数来实例化Service的

  • 实现虚函数onHandleIntent

在里面根据Intent的不同进行不同的事务处理。好处:处理异步请求的时候可以减少写代码的工作量,比较轻松地实现项目的需求。

  1. IntentService与Service的区别

Service不是独立的进程,也不是独立的线程,它是依赖于应用程序的主线程的,不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。

IntentService 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents(把intent插入到工作队列中)。通过工作队列把intent逐个发送给onHandleIntent()。不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。

默认实现的onBind()返回null。

  1. IntentService实例介绍

首先是myIntentService.java

public class myIntentService extends IntentService {

	//------------------必须实现-----------------------------
	
	public myIntentService() {
		super("myIntentService");
		// 注意构造函数参数为空,这个字符串就是worker thread的名字
	}

	@Override
	protected void onHandleIntent(Intent intent) {
		//根据Intent的不同进行不同的事务处理 
        String taskName = intent.getExtras().getString("taskName");  
        switch (taskName) {
		case "task1":
			Log.i("myIntentService", "do task1");
			break;
		case "task2":
			Log.i("myIntentService", "do task2");
			break;
		default:
			break;
		}		
	}
  //--------------------用于打印生命周期--------------------	
   @Override
  public void onCreate() {
		Log.i("myIntentService", "onCreate");
	super.onCreate();
}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i("myIntentService", "onStartCommand");
		return super.onStartCommand(intent, flags, startId);
	}
	
	@Override
	public void onDestroy() {
		Log.i("myIntentService", "onDestroy");
		super.onDestroy();
	}
}

然后记得在Manifest.xml中注册服务

 <service android:name=".myIntentService">
            <intent-filter >  
                <action android:name="cn.scu.finch"/>  
            </intent-filter>     
        </service>

最后在Activity中开启服务

public class MainActivity extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		
		//同一服务只会开启一个worker thread,在onHandleIntent函数里依次处理intent请求。
		
        Intent i = new Intent("cn.scu.finch");  
        Bundle bundle = new Bundle();  
        bundle.putString("taskName", "task1");  
        i.putExtras(bundle);  
        startService(i);  
           
        Intent i2 = new Intent("cn.scu.finch");  
        Bundle bundle2 = new Bundle();  
        bundle2.putString("taskName", "task2");  
        i2.putExtras(bundle2);  
        startService(i2); 
        
        startService(i);  //多次启动
	}
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5t1JA0u-1653120521233)(https://camo.githubusercontent.com/fb6bf8fc2ff7c1713ef558183b6ab0f47cb846e3/687474703a2f2f696d672e626c6f672e6373646e2e6e65742f3230313630353133313335343131303337)]

IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来依次处理所有Intent请求对象所对应的任务。通过onStartCommand()传递给服务intent被依次插入到工作队列中。工作队列又把intent逐个发送给onHandleIntent()。

注意: 它只有一个工作线程,名字就是构造函数的那个字符串,也就是“myIntentService”,我们知道多次开启service,只会调用一次onCreate方法(创建一个工作线程),多次onStartCommand方法(用于传入intent通过工作队列再发给onHandleIntent函数做处理)。

[7] IntentService——Handler与Service的结合

综述

我们都知道Service是作为后台服务运行再程序中的。但是Service他依然是运行在主线程中的,所以我们依然不能在Service中进行耗时的操作。所以当我们在Service处理时,我们需要在Service中开启一个子线程,并且在子线程中运行。当然为了简化我们的操作,在Android中为我们提供了IntentService来进行这一处理,下面我们就来看一下这个IntentService用法以及它的工作原理。

用法简介

IntentService它继承自Service,一来说我们开启一个Service可以通过startService和bindService两个方式进行开启一个服务,但是对于IntentService我们采用startService方法进行开启服务,对于为什么要这么做,在后面会进行分析讲解。下面我们来看一下如何使用这个IntentService的。

效果演示

在这里我们做一个倒计时的程序,以毫秒为单位。这里先看一下效果演示。

img

代码分析

在这里我们使用到了开源框架EventBus,对于EventBus的使用可以参考 EventBus3.0使用详解这篇文章。由于我们用到这EventBus,首先我们创建一个实体类,在EventBus中进行发送,接收处理。

img

下面我们看一下IntentService中的代码。

img
img
在上面的handleActionFoo方法中进行我们的耗时任务。然后我们在看一下Activity中的代码。

img
img

最后是布局代码。

img

对于上面代码实现起来都是非常的简单,在这里就不在进行详细介绍。

IntentService工作原理分析

其实对于IntentService的工作原理也不复杂,既然在IntentService中能够进行耗时操作,也就是说在这个IntentService中必然也创建了一个子线程,在Android中我们称为工作者线程。然后在这个工作者线程中进行我们的任务。在分析IntentService之前,我们先看一下HandlerThread。

HandlerThread

其实HandlerThread就是一个工作者线程,在这里看一下HandlerThread的源码。

img  img

​ 看过上篇文章 Android的消息机制——Handler的工作过程就很容易理解这个HandlerThread了。还记的我们在上篇文章的最后,新建了一个包含Looper的子线程。而这个HandlerThread也就是一个包含Looper的子线程。所以当我们需要创建一个包含Looper的线程时直接使用HandlerThread即可。对于HandlerThread有以下几点需要说明一下。

   1. 在构造方法中设置线程优先级的时候,使用的Process是android.os包中的而不是java.lang包内的。 
      2. 如果在Looper开启消息循环之前我们进行一些设置,我们可以继承HandlerThread并且重写onLooperPrepared方法。 
         3. 通过getLooper方法我们获取HandlerThread的Looper对象时,有可能Looper还未创建完成。所以在getLooper中未创建Looper是进行了线程等待操作,在创建完Looper以后在返回Looper对象。

IntentService

下面我们再看一下IntentService。

img
img

我们看一下这个IntentService的构造是不是很简单。在这里主要看一下onCreate和onStart方法即可。在onCreate中,我们开启了一个HandlerThread线程,之后获取HandlerThread线程中的Looper,并通过这个Looper创建了一个Handler。然后在onStart方法中通过这个Handler将intent与startId作为Message的参数进行发送到消息队列中,然后交由Handler中的handleMessage中进行处理。由于在onStart方法是在主线程内运行的,而Handler是通过工作者线程HandlerThread中的Looper创建的。所以也就是在主线程中发送消息,在工作者接收到消息后便可以进行一些耗时的操作。
  我们在看一下handleMessage中的操作,在handleMessage中调用onHandleIntent方法,他是一个抽象方法,所以在我们的Service中复写onHandleIntent方法并且将耗时的操作写在onHandleIntent方法内即可。当执行完onHandleIntent后通过stopSelf来停止服务,这样就不用我们手动停止服务了。所以也就回答了我们上面那个为什么要使用startService而不用onBind来开启一个IntentService。

总结

从我们的示例和源码分析中可以看出来。对于通过IntentService来执行任务,他是串行的。也就是说只有在上一个任务执行完以后才会执行下一个任务。因为Handler中将消息插入消息队列,而队列又是先进先出的数据结构。所以只有在上个任务执行完成以后才能够获取到下一个任务进行操作。在这里也就说明了对于高并发的任务同过IntentService是不合适.

相关文章:

  • C#套接字和windowsAPI套接字
  • 【ContentProvider专题】
  • 2.4-saltstack pillar
  • 【BroadcastReceiver专题】
  • 计算机存储形式与进制转换
  • 【hadler机制】
  • 【Mongo】uploadify插件帮助实现批量上传
  • 【view的绘制流程】
  • AOS V0.9 发布,JavaEE 应用基础平台
  • 【View事件分发机制 】
  • 闭包--循序学习
  • 【MVCMVPMVVM】
  • 【内存泄露】
  • 默认input=file样式美化的bug及解决
  • 【进程间通信】
  • SegmentFault for Android 3.0 发布
  • 【Leetcode】104. 二叉树的最大深度
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • ES2017异步函数现已正式可用
  • ES6 ...操作符
  • gitlab-ci配置详解(一)
  • Intervention/image 图片处理扩展包的安装和使用
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • Java比较器对数组,集合排序
  • Java多线程(4):使用线程池执行定时任务
  • Java教程_软件开发基础
  • js ES6 求数组的交集,并集,还有差集
  • Less 日常用法
  • nginx 负载服务器优化
  • oschina
  • Promise面试题,控制异步流程
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • SQLServer之创建数据库快照
  • Vue源码解析(二)Vue的双向绑定讲解及实现
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 聊聊hikari连接池的leakDetectionThreshold
  • 入手阿里云新服务器的部署NODE
  • 协程
  • 异步
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • MPAndroidChart 教程:Y轴 YAxis
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • #NOIP 2014# day.1 T2 联合权值
  • (JSP)EL——优化登录界面,获取对象,获取数据
  • (react踩过的坑)Antd Select(设置了labelInValue)在FormItem中initialValue的问题
  • *1 计算机基础和操作系统基础及几大协议
  • .Net IOC框架入门之一 Unity
  • .Net6 Api Swagger配置
  • .net专家(张羿专栏)
  • @Documented注解的作用
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [ C++ ] STL---stack与queue
  • [ linux ] linux 命令英文全称及解释
  • [AutoSar]工程中的cpuload陷阱(三)测试
  • [C# WPF] 如何给控件添加边框(Border)?