zoukankan      html  css  js  c++  java
  • Android API Guides---Services

    服务
    在该文献
    基础
    声明在清单服务
    创建一个启动的服务
    扩展IntentService类
    扩展服务类
    启动服务
    停止服务
    创建绑定服务
    将通知发送给用户
    执行在前台服务
    管理服务生命周期
    实施生命周期回调
    重点班
    服务
    IntentService
    样本
    服务启动參数
    本地服务
    也能够看看
    绑定服务
    服务是能够在后台执行长时间执行操作,而且不提供用户界面的应用组件。还有一个应用程序组件能够启动服务。它就会继续在即使用户切换到还有一个应用程序在后台执行。此外,一个组件能够绑定到一个服务来与它进行交互,甚至进行进程间通信(IPC)。

    比如,服务可能处理网络交易,播放音乐,执行文件I / O。或与内容供应商进行互动,全部从背景。


    服务基本上能够採取两种形式:
    入门
    服务是“開始”的时候应用程序组件(如活动)。通过调用startService启动它()。

    一旦開始。一个服务能够无限期在后台执行,即使启动它的成分被破坏。通常,启动服务执行单次操作和结果不返回给调用者。比如,它可能下载或上传在网络上的文件。

    当操作完毕时,服务应该停止本身。

    服务是“必定”时,应用程序组件调用bindService绑定到它()。

    绑定的服务提供了一个client - server接口,同意组件与服务交互,发送请求。得到的结果。甚至跨越与进程间通信(IPC)的进程这样做。绑定的服务仅仅仅仅要其它应用程序组件绑定到它执行。多个组件能够绑定到服务一次,可是,当它们所有取消绑定。服务被破坏。


    尽管此文件通常分别讨论这两种类型的服务,您的服务能够两种方式工作,它能够启动(无限期执行),也同意绑定。它仅仅是你是否实现了几个回调方法的问题:onStartCommand(),以同意组件启动和onBind()同意绑定。
    不管应用程序是否是启动的,结合的。或两者的不论什么应用程序组件能够使用的服务(甚至从一个单独的应用程序),以相同的方式。不论什么组件能够由活性与意图启动它使用。

    可是,你能够声明服务为私有,在清单文件,并阻止来自其它应用程序的訪问。这是在大约在清单中声明该服务的部分很多其它的讨论。
    注意:服务在其宿主进程,该服务不会创建自己的线程的主线程中执行,在单独的进程不执行(除非另行指定)。这意味着。假设你的服务是打算做不论什么CPU密集型工作或堵塞操作(如MP3播放或网络),你应该在服务中创建一个新的线程来完毕这项工作。通过使用一个单独的线程,你会减少应用风险不响应(ANR)错误和应用程序的主线程能够继续致力于用户交互与你的活动。


    基础
    假设您使用服务或线程?
    服务不过能够在后台,即使用户不与应用程序交互执行的组件。

    因此,你应该创建一个服务只假设那是你所须要的。


    假设你须要你的主线程之外执行的工作。但仅仅有在用户与应用程序交互,那么你也许应该。而不是创建一个新的线程,而不是服务。比如,假设你要玩一些音乐,但你的活动正在执行仅在,你也许能够在一个的onCreate线程()。開始在onStart()执行它,然后停止它的onStop()。也能够考虑使用。而不是传统的Thread类的AsyncTask或HandlerThread。请參阅有关线程的具体信息的过程和线程的文件。


    请记住。假设你使用一个服务,它仍然在默认情况下,您的应用程序的主线程中执行,所以你还是应该在服务中创建一个新的线程。假设它执行密集或堵塞操作。
    要创建一个服务,你必须创建服务的一个子类(或者其现有的一个子类)。在您的实现。你须要重写处理服务生命周期的关键方面的一些回调方法,并提供了一​​种机制,组件,假设合适的话绑定到服务。你应该重写最重要的回调方法是:
    onStartCommand()
    系统调用此方法时还有一组件,如一个活动。要求该服务被启动,通过调用startService()。一旦这种方法执行。该服务已启动,能够在后台执行下去。假设实现这一点,这是你的责任,当其工作完毕后,通过调用stopSelf()或stopService()停止服务。

    (假设你仅仅希望提供绑定。你并不须要实现这种方法。


    onBind()
    该系统调用此方法时。还有一部分要与服务(如运行RPC)绑定,通过调用bindService()。

    在实现此方法。您必须提供client使用通过返回一个IBinder与服务进行通信,接口。

    你必须始终实现此方法,但假设你不想让绑定。那么你应该返回null。
    onCreate()
    该系统调用时,第一次创建服务这样的方法,进行一次性设置程序(它调用要么onStartCommand()或onBind()之前)。假设该服务已在执行,这样的方法不会被调用。
    onDestroy()
    系统调用当服务不再使用而被销毁此方法。您的服务应实现此清理不论什么资源如线程,注冊的监听器,接收器等,这是服务接收最后一次通话。
    假设一个组件通过调用startService()启动服务(这会导致以onStartCommand()的调用),则该服务一直执行,直到它停止本身stopSelf()或其它部件致电stopService停止它()。
    假设一个组件调用bindService()创建的服务(和onStartCommand()不被调用),则服务仅仅仅要该组件绑定到执行。

    一旦服务是全部client绑定,系统破坏它。


    仅仅有当内存不足的Andr​​oid系统将强制停止服务,它必须恢复系统资源为具实用户焦点的活动。假设该服务被绑定到用户具有焦点的活动,那么它不太可能被杀害,而假设该服务被宣布为在前台执行(稍后讨论),那么它差点儿不会被杀死。

    否则。假设该服务已启动。而且是长期执行的,那么系统会减少其在后台任务列表中的位置随着时间的推移。该服务将变得很easy杀死。假设您的服务已启动,则必须将其设计为优雅地处理由系统又一次启动。

    假设系统杀死你的服务,请尽快又一次启动它的资源再次可用(虽然这也取决于你从onStartCommand(返回值)。如稍后讨论)。有关当系统可能会破坏服务的很多其它信息,请參见进程和线程文件。
    在以下的章节中。您将看到怎样创建每种类型的服务,以及怎样从其它应用程序组件使用它。
    声明在清单服务
    之类的活动(和其它组件),则必须在应用程序的清单文件里声明的全部服务。
    要声明你的服务。加入一个<service>元素作为<application>元素的子项。

    比如:
    <

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

    请參阅有关清单声明你的服务的更​​多信息<service>元素引用。


    还有其它的属性能够包括在<service>元素来定义属性,如启动该服务,并在服务应该执行过程中所需的权限。 Android的:name属性是唯一必需的属性,它指定服务类的名称。一旦你公布你的应用程序,由于假设你这样做,你断码,由于在明白意图来启动或绑定服务的依赖风险不应该更改这个名字,(阅读博客文章,事情能够不改变)。


    为了确保您的应用程序是安全的,启动或​​结合您的服务时,一定要使用一个明白的意图,并没有为服务声明意图过滤器。假设它的关键。您同意的歧义一定量的该服务启动时,你能够为你的服务供应意图过滤器。并从意图排除组件的名称,但你必须将包与setPackage(),其目的这为目标服务足够消歧。


    此外,还能够确保您的服务仅仅提供给您的应用程序由包含Android:exported属性并将其设置为“假”。这有效地启动你的服务,使用一个明白的意图,即使停止其它应用程序。


    创建一个启动的服务
    已启动的服务是一个还有一个组件開始通过调用startService()。造成该服务的onStartCommand()方法的调用。
    当服务启动时,它有一个生命周期的自主启动它,服务能够在后台执行无限期组件。即使启动它的成分被破坏。这样,服务应该停止本身时,其作业是通过调用stopSelf(完毕),或其它部件能够通过调用stopService停止()。


    一个应用程序组件。如一个活性能够通过调用startService启动服务()并传递用于指定的服务。包含用于向使用该服务的不论什么数据的意图。该服务接收的onStartCommand()方法,这个意图。
    比如,如果一个活动须要一些数据保存到一个在线数据库。活动能够启动一个同伴服务,并提供其数据传递的意图,startService保存()。服务接收在onStartCommand()的意图。连接到互联网,并运行数据库事务。当交易完毕时。服务停止本身和它被破坏。
    注意:一个服务执行在同样的工序中声明它的应用程序,该应用程序的主线程,默认情况下。所以,假设你的服务,而用户从同一个应用程序的交互活动进行密集或堵塞操作,该服务将减慢活动的表现。为避免影响应用程序的性能,你应该開始在服务中一个新的线程。
    传统上,还有你能够扩展创建一个启动的服务两大类:
    服务
    这是对于全部服务的基类。当你扩展这个类,它是创建在当中做的全部服务工作的一个新的线程,由于该服务使用你的应用程序的主线程。默认情况下,这可能会减慢你的应用程序正在执行的不论什么活动的表现是非常重要的。
    IntentService
    这是服务的子类。使用工作线程来处理全部的開始请求,一次一个。这是最好的选择,假设你不要求你的服务同一时候处理多个请求。全部你须要做的是落实onHandleIntent()。它接收的每一个请求的開始。所以你能够做后台工作的意图。
    下面各节描写叙述了怎样使用不论什么一个这些类实现您服务。
    扩展IntentService类
    因为大多数启动的服务并不须要同一时候处理多个请求(这实际上是一个危急的多线程情况下),假设你使用IntentService类来实现你的服务非常可能是最好的。


    该IntentService运行下面操作:
    创建一个默认的工作线程运行交付给onStartCommand全部意图()从应用程序的主线程中分离出来。
    创建一个在同一时间到你onHandleIntent()实现通1意图,所以你永远不必操心多线程工作队列。


    毕竟停止服务启动请求已经被处理。所以你从来没有打电话给stopSelf()。
    提供onBind(缺省实现)的返回null。
    提供了发送意图的工作队列,然后到你的onHandleIntent()实现onStartCommand()的默认实现。
    这一切都添加了一个事实,即全部你须要做的就是实现onHandleIntent()做client提供的工作。

    (尽管,你还须要提供该服务的小构造函数)。


    以下是IntentService的演示样例实现:

    public class HelloIntentService extends IntentService {
    
      /**
       * A constructor is required, and must call the super IntentService(String)
       * constructor with a name for the worker thread.
       */
      public HelloIntentService() {
          super("HelloIntentService");
      }
    
      /**
       * The IntentService calls this method from the default worker thread with
       * the intent that started the service. When this method returns, IntentService
       * stops the service, as appropriate.
       */
      @Override
      protected void onHandleIntent(Intent intent) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
      }
    }

    这就是你须要:一个构造函数和onHandleIntent的实现()。


    假设您决定还覆盖其它回调方法。如OnCreate()中。onStartCommand(),或的onDestroy()时,一定要调用父类的实现。使IntentService能妥善处理工作线程的使用寿命。
    比如,onStartCommand()必须返回默认的实现(这是意图怎样被传递到onHandleIntent()):

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        return super.onStartCommand(intent,flags,startId);
    }

    除了onHandleIntent(),从中你不须要调用超类的唯一方法是onBind()(但你仅仅须要实现,假设你的服务同意绑定)。
    在下一节中。您将看到怎样扩展基本服务类。这是非常多很多其它的代码,当同类服务的实现,但假设你须要同一时候处理開始请求这可能是合适的。


    扩展服务类
    当你在上一节中所示,使用IntentService让您一開始服务的实现很easy。可是。假设你须要你的服务来运行多线程(而不是通过工作队列处理開始请求)。那么你能够扩展服务类来处理每个意图。
    为了进行比較。下面演示样例代码是运行全然同样的工作与上述使用IntentService的样例中的服务类的实现。也就是说。对于每个启动请求时。它使用一个工作线程来运行该作业和过程在一个时间仅仅有一个请求。

    public class HelloService extends Service {
      private Looper mServiceLooper;
      private ServiceHandler mServiceHandler;
    
      // Handler that receives messages from the thread
      private final class ServiceHandler extends Handler {
          public ServiceHandler(Looper looper) {
              super(looper);
          }
          @Override
          public void handleMessage(Message msg) {
              // Normally we would do some work here, like download a file.
              // For our sample, we just sleep for 5 seconds.
              long endTime = System.currentTimeMillis() + 5*1000;
              while (System.currentTimeMillis() < endTime) {
                  synchronized (this) {
                      try {
                          wait(endTime - System.currentTimeMillis());
                      } catch (Exception e) {
                      }
                  }
              }
              // Stop the service using the startId, so that we don't stop
              // the service in the middle of handling another job
              stopSelf(msg.arg1);
          }
      }
    
      @Override
      public void onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread thread = new HandlerThread("ServiceStartArguments",
                Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
    
        // Get the HandlerThread's Looper and use it for our Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
      }
    
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
          Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
    
          // For each start request, send a message to start a job and deliver the
          // start ID so we know which request we're stopping when we finish the job
          Message msg = mServiceHandler.obtainMessage();
          msg.arg1 = startId;
          mServiceHandler.sendMessage(msg);
    
          // If we get killed, after returning from here, restart
          return START_STICKY;
      }
    
      @Override
      public IBinder onBind(Intent intent) {
          // We don't provide binding, so return null
          return null;
      }
    
      @Override
      public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
      }
    }

    正如你所示。它比使用IntentService非常多工作。


    可是,由于你处理每一个调用onStartCommand()你自己,你能够同一时候执行多个请求。

    这不是这个样例做什么,但假设这是你想要的,那么你能够为每一个请求创建一个新的线程和执行它们的时候了(而不是等待前一个请求完毕)。
    请注意,onStartCommand()方法必须返回一个整数。

    整数是描写叙述系统应怎样继续在该系统杀死它时服务(如上面所讨论的,IntentService默认实现为您处理此,虽然你能够改动它)的值。从onStartCommand()的返回值必须是下面常量之中的一个:
    START_NOT_STICKY
    假设系统杀死onStartCommand()返回后,在服​​务,不又一次创建的服务,除非存在未处理的意图来提供。

    这是为了避免执行服务时,没有必要的。当你的应用程序能够简单地又一次启动不论什么未完毕的作业最安全的选择。
    START_STICKY
    假设系统杀死onStartCommand后服务()返回,又一次创建服务和呼叫onStartCommand()。但不又一次提交最后意图。

    相反。系统调用onStartCommand()用空的意图。除非有未决的意图来启动服务,在这样的情况下,那些意图传递。这是适合于不执行命令,但无限期执行和等待作业媒体播放器(或类似的服务)。
    START_REDELIVER_INTENT
    假设系统杀死onStartCommand后,在服务()返回。又一次创建服务并调用onStartCommand()与被输送到该服务的最后意图。不论什么挂起的意图被依次传递。这是适合于正在积极运行该应马上工作恢复时。例如以下载文件的服务。
    有关这些返回值的很多其它具体信息,请參阅各不变链接參考文档。
    启动服务
    你能够通过一个Intent(指定服务启动),以startService開始从一个活动或其他应用程序组件服务()。 Android系统调用服务的onStartCommand()方法,并传递给它的意图。 (你永远不应该直接调用onStartCommand()。)
    比如,一个活动能够使用带startService()显式意图启动在上一节(HelloSevice)在实施例的服务:

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

    该startService()方法马上返回,Android系统调用服务的onStartCommand()方法。

    假设该服务尚未执行,系统首先调用的onCreate()。然后调用onStartCommand()。


    假设服务不还提供了绑定,与startService()发表的意图是应用程序组件和服务之间的通信的唯一方式。只是,假设你希望服务的结果发回。然后启动该服务的client能够创建一个广播一的PendingIntent(带getBroadcast()),它在启动该服务的意图传递给服务。

    然后该服务能够使用广播来提供一个结果。


    多个请求启动该服务。导致多个对应的调用该服务的onStartCommand()。可是。仅仅有一个要求停止服务(与stopSelf()或stopService())来阻止它。
    停止服务
    已启动的服务必须管理自己的生命周期。也就是说,该系统不停止或破坏该服务,除非它必须恢复系统内存和服务继续onStartCommand()返回之后执行。因此,该服务必须通过调用stopSelf()停止自身或其它部件能够通过调用stopService停止它()。


    一旦请求停止与stopSelf()或stopService()时。系统马上破坏服务成为可能。


    可是。假设你的服务处理多个请求onStartCommand()兼任。那么你不应该由于收到一个新的開始请求(在第一年底停止停止服务。当你处理完一个開始请求,由于你可能有请求将终止,第二个)。

    为了避免这个问题,你可以使用stopSelf(INT)。以确保您的要求停止该服务总是基于最新的启动请求。也就是说。当你调用stopSelf(INT),您通过启动请求的ID(该startId交付给onStartCommand())到您的停止请求相应。

    假设服务接收到一个新的開始要求你可以调用stopSelf(INT)之前。则该ID将不匹配,该服务将不会停止。
    注意:重​​要的是,您的应用程序停止其服务时。它的完毕工作。避免浪费系统资源和消耗电池电量。假设有必要,其它部件能够通过调用stopService停止服务()。即使您启用服务绑定,必须始终自行停止该服务。假设它以前接到一个电话到onStartCommand()。


    有关服务的生命周期的很多其它信息,请參见以下有关管理服务的生命周期。
    创建绑定服务
    绑定的服务是一个同意应用程序组件通过,以创造一个长期的连接调用bindService()绑定到它(一般不同意组件通过调用startService启动它())。
    当你想从活动和其它组件在应用程序或暴露你的一些应用程序的功能到其它应用程序。通过进程间通信(IPC)的服务进行交互,你应该建立一个绑定的服务。


    要创建一个绑定服务,必须实现onBind()回调方法返回定义与服务通信的接口一个IBinder。然后。其它应用程序组件能够调用bindService()来检索界面,并開始调用服务方法。该服务仅仅生活服务绑定到它的应用程序组件,因此在没有绑定到的服务组件。系统破坏它(你不须要必须在服务启动时的方式停止绑定服务通过onStartCommand())。
    要创建一个绑定的服务,你必须做的第一件事就是定义指定client怎样与服务通信的接口。服务和client之间的接口必须的IBinder的实现,是你的服务必须从onBind()回调方法返回。一旦客户机接收到的IBinder,它能够開始通过该接口的服务进行交互。
    多个client能够结合到服务于一次。

    当client完毕与服务交互,它调用unbindService()来解除绑定。一旦没有绑定到服务的client,系统会破坏该服务。
    有实现绑定服务多种方式和实现比启动服务的很多其它复杂。所以绑定服务的讨论出如今有关绑定服务一个单独的文件。
    将通知发送给用户
    一旦执行,服务能够使用通知通知吐司或状态栏通知事件的用户。
    Toast通知是片刻然后消失当前窗体的表面上出现一则消息。而状态栏通知提供了一条消息,用户能够以採取行动选择状态栏(这种图标作为启动活动)。
    通常情况下,一个状态栏通知是最好的技术时。一些后台的工作已经完毕(如文件下载完毕)和用户如今能够採取行动。当用户选择从展开图的通知时,该通知能够開始一个活动(比如。以查看下载的文件)。
    见敬酒通知或状态栏通知开发人员指南获取很多其它信息。
    执行在前台服务
    前台服务是被觉得是东西服务的用户正在积极了解并因此不会对系统杀死时内存不足的候选人。前台服务必须为状态栏。这是摆在“持续”的标题,这意味着该通知不能被解雇。除非该服务停止或从前台删除的通知。


    比如,从服务播放音乐的音乐播放器应设置在前台执行,由于用户是明白地知道它的操作。在状态栏中的通知可能表明当前歌曲,并同意用户启动的活动与音乐播放器进行交互。

    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_ID, notification);

    要从前台服务,请致电stopForeground()。

    此方法须要一个布尔值。指示能否够取消状态栏通知为好。

    此方法不停止服务。可是,假设你停止服务,而它仍然在前台执行。则该通知也将被删除。


    有关通知的具体信息,请參阅创建状态栏通知。
    管理服务生命周期
    服务的生命周期比一个活动要简单得多。然而,它更重要的是你要密切关注怎样您的服务创建和销毁,由于服务能够在用户不知道的后台执行。
    服务生命周期 - 从当它创建时。它的破坏。能够遵循两条不同的路径:
    已启动的服务
    当还有一个组件调用startService()被创建的服务。然后服务将无限期执行,必须通过调用stopSelf自行停止()。还有一个组件还能够通过调用stopService停止服务()。假设该服务停止,系统破坏它..
    绑定的服务
    当还有一个组件(client)调用bindService()被创建的服务。然后。client通过一个IBinder接口服务进行通信。

    client能够关闭致电unbindService连接()。多个client能够结合到同样的服务,当它们所有取消绑定,则系统会破坏该服务。 (该服务并不须要自行停止。)
    这两条路径都没有全然分开。也就是说。能够绑定到一个已经開始与startService服务()。比如。一个背景音乐服务能够通过与标识所述音乐播放的意图调用startService()開始。

    以后。可能当用户想要实行一些控制播放器或获取有关当前歌曲的信息,一个活动能够通过调用bindService绑定到服务()。在这样的情况下。stopService()或stopSelf()实际上并没有停止服务,直到全部client解除绑定。


    实施生命周期回调
    就像一个活动,一个服务具有能够实现监控服务的状态变化,并在适当的时候进行的工作生命周期回调方法。

    以下的骨骼服务演示每一个生命周期方法:

    public class ExampleService extends Service {
        int mStartMode;       // indicates how to behave if the service is killed
        IBinder mBinder;      // interface for clients that bind
        boolean mAllowRebind; // indicates whether onRebind should be used
    
        @Override
        public void onCreate() {
            // The service is being created
        }
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // The service is starting, due to a call to startService()
            return mStartMode;
        }
        @Override
        public IBinder onBind(Intent intent) {
            // A client is binding to the service with bindService()
            return mBinder;
        }
        @Override
        public boolean onUnbind(Intent intent) {
            // All clients have unbound with unbindService()
            return mAllowRebind;
        }
        @Override
        public void onRebind(Intent intent) {
            // A client is binding to the service with bindService(),
            // after onUnbind() has already been called
        }
        @Override
        public void onDestroy() {
            // The service is no longer used and is being destroyed
        }
    }

    注:与活动生命周期回调方法。你是不是须要调用父类运行这些回调方法。


    图2.服务生命周期。

    左边的图显示()时,服务与startService创建的生命周期和右边的图显示了生命周期时bindService创建服务()。


    通过实施这些方法,你能够监视服务生命周期的两个嵌套循环:
    服务的整个生命周期中发生的onCreate()被调用的时候。时间的onDestroy()返回间。就像一个活动,服务确实在OnCreate()中的初始设置并释放全部的onDestroy剩余资源()。比如,音乐播放服务能够创造一个音乐将在OnCreate()中播放线程,然后停止的onDestroy线程()。


    在OnCreate()中和的onDestroy()方法调用全部服务,不管他们是由startService()或bindService()创建的。
    一个服务的活性寿命開始到呼叫或者onStartCommand()或onBind()。每种方法分别移交)传递给不论什么startService()或bindService(意图,。

  • 相关阅读:
    问题排查
    代码
    前端
    即时通信系统 偶尔看到 就想学着做一下
    Oracle 笔记
    javaweb dev 入
    mysql安装
    windows下安装nginx
    spring MVC上传附件
    mysql+mybatis递归调用
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/7234689.html
Copyright © 2011-2022 走看看