当前位置: 移动技术网 > 移动技术>移动开发>Android > 详解Android 进程间通信的几种实现方式

详解Android 进程间通信的几种实现方式

2019年07月24日  | 移动技术网移动技术  | 我要评论

一、概述

由于应用程序之间不能共享内存。在不同应用程序之间交互数据(跨进程通讯),在android sdk中提供了4种用于跨进程通讯的方式。

这4种方式正好对应于android系统中4种应用程序组件:activity、content provider、broadcast和service。其中activity可以跨进程调用其他应用程序的activity;content provider可以跨进程访问其他应用程序中的数据(以cursor对象形式返回),当然,也可以对其他应用程序的数据进行增、删、改操 作;broadcast可以向android系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播;service和content provider类似,也可以访问其他应用程序中的数据,但不同的是,content provider返回的是cursor对象,而service返回的是java对象,这种可以跨进程通讯的服务叫aidl服务。

 activity

activity的跨进程访问与进程内访问略有不同。虽然它们都需要intent对象,但跨进程访问并不需要指定context对象和activity的 class对象,而需要指定的是要访问的activity所对应的action(一个字符串)。有些activity还需要指定一个uri(通过 intent构造方法的第2个参数指定)。

在android系统中有很多应用程序提供了可以跨进程访问的activity,例如,下面的代码可以直接调用拨打电话的activity。

intent callintent = new intent(intent.action_call, uri.parse("tel:12345678" ); 
startactivity(callintent);

content provider 

android应用程序可以使用文件或sqllite数据库来存储数据。content provider提供了一种在多个应用程序之间数据共享的方式(跨进程共享数据)。应用程序可以利用content provider完成下面的工作

1. 查询数据

2. 修改数据

3. 添加数据

4. 删除数据

虽然content provider也可以在同一个应用程序中被访问,但这么做并没有什么意义。content provider存在的目的向其他应用程序共享数据和允许其他应用程序对数据进行增、删、改操作。

android系统本身提供了很多content provider,例如,音频、视频、联系人信息等等。我们可以通过这些content provider获得相关信息的列表。这些列表数据将以cursor对象返回。因此,从content provider返回的数据是二维表的形式。

广播(broadcast) 

广播是一种被动跨进程通讯的方式。当某个程序向系统发送广播时,其他的应用程序只能被动地接收广播数据。这就象电台进行广播一样,听众只能被动地收听,而不能主动与电台进行沟通。
在应用程序中发送广播比较简单。只需要调用sendbroadcast方法即可。该方法需要一个intent对象。通过intent对象可以发送需要广播的数据。

service

1.利用aidl service实现跨进程通信

这是我个人比较推崇的方式,因为它相比broadcast而言,虽然实现上稍微麻烦了一点,但是它的优势就是不会像广播那样在手机中的广播较多时会有明显的时延,甚至有广播发送不成功的情况出现。

注意普通的service并不能实现跨进程操作,实际上普通的service和它所在的应用处于同一个进程中,而且它也不会专门开一条新的线程,因此如果在普通的service中实现在耗时的任务,需要新开线程。

要实现跨进程通信,需要借助aidl(android interface definition language)。android中的跨进程服务其实是采用c/s的架构,因而aidl的目的就是实现通信接口。

首先举一个简单的栗子。

服务端代码如下:

 首先是aidl的代码:

package com.android.service; 
 
interface idata 
{ 
  int getroomnum(); 
} 

roomservice的代码如下:

package com.android.service; 
 
import android.app.service; 
import android.content.intent; 
import android.os.ibinder; 
import android.os.remoteexception; 
 
public class roomservice extends service{ 
 
  private idata.stub mbinder=new idata.stub() { 
     
    @override 
    public int getroomnum() throws remoteexception { 
       return 3008; 
    } 
  }; 
 
  @override 
  public ibinder onbind(intent intent) { 
    return mbinder; 
  } 
 
} 

androidmanifest如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
  package="com.android.aidlsampleservice" 
  android:versioncode="1" 
  android:versionname="1.0" > 
 
  <uses-sdk 
    android:minsdkversion="8" 
    android:targetsdkversion="17" /> 
 
  <application 
    android:allowbackup="true" 
    android:icon="@drawable/ic_launcher" 
    android:label="@string/app_name" 
    android:theme="@style/apptheme"    
    <service android:name="com.android.service.roomservice"> 
      <intent-filter> 
        <action android:name="com.aidl.service.room"/> 
      </intent-filter> 
    </service> 
  </application> 
 
</manifest> 

然后运行该service所在的project即可。

客户端代码如下:

注意客户端也要有aidl文件,所以最简单的办法就是将service端中aidl所在的包直接复制过去。另外要注意的是在ondestroy中要解除和service的绑定。

mainactivity.java的代码如下:

package com.example.aidlsampleclient; 
 
import com.android.service.idata; 
 
import android.os.bundle; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.app.activity; 
import android.content.componentname; 
import android.content.intent; 
import android.content.serviceconnection; 
import android.util.log; 
import android.view.menu; 
import android.widget.button; 
import android.widget.toast; 
import android.view.view; 
 
public class mainactivity extends activity implements view.onclicklistener{ 
 
   
  private static final string tag="mainactivity"; 
  private static final string room_service_action="com.aidl.service.room"; 
   
  private button bindservicebutton; 
  private button getservicebutton; 
   
   
  idata mdata; 
   
  private serviceconnection conn=new serviceconnection() 
  { 
 
    @override 
    public void onserviceconnected(componentname name, ibinder service) { 
     
      log.i(tag,"----------------onserviceconnected--------"); 
      mdata=idata.stub.asinterface(service); 
    } 
 
    @override 
    public void onservicedisconnected(componentname name) { 
     
      log.i(tag,"----------------onservicedisconnected-------------"); 
      mdata=null; 
       
    } 
     
  }; 
   
  private void initview() 
  { 
    bindservicebutton=(button)findviewbyid(r.id.bindservicebutton); 
    getservicebutton=(button)findviewbyid(r.id.getservicebutton); 
    bindservicebutton.setonclicklistener(this); 
    getservicebutton.setonclicklistener(this); 
  } 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
    initview(); 
  } 
   
  @override 
  public void onclick(view v) { 
   switch(v.getid()) 
    { 
    case r.id.bindservicebutton: 
      bindservice(); 
      break; 
    case r.id.getservicebutton: 
      getservice();   
       break; 
    default: 
       break; 
    } 
  } 
   
   
  private void bindservice() 
  { 
    intent intent=new intent(); 
    intent.setaction(room_service_action); 
    bindservice(intent,conn,bind_auto_create); 
  } 
   
  private void getservice() 
  { 
    try 
    { 
      if(mdata!=null) 
      { 
        int roomnum=mdata.getroomnum(); 
        showlongtoast("roomnum:"+roomnum); 
      } 
       
    } 
    catch(remoteexception ex) 
    { 
      ex.printstacktrace(); 
    } 
     
  } 
   
  private void showlongtoast(string info) 
  { 
    toast.maketext(getbasecontext(), info, toast.length_long).show(); 
  } 
 
  @override 
  public boolean oncreateoptionsmenu(menu menu) { 
    getmenuinflater().inflate(r.menu.main, menu); 
    return true; 
  } 
  @override 
  protected void ondestroy() { 
    super.ondestroy(); 
    unbindservice(conn); 
  } 
} 

activity_main.xml的代码如下:

<relativelayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:paddingbottom="@dimen/activity_vertical_margin" 
  android:paddingleft="@dimen/activity_horizontal_margin" 
  android:paddingright="@dimen/activity_horizontal_margin" 
  android:paddingtop="@dimen/activity_vertical_margin" 
  tools:context=".mainactivity" > 
 
  <button  
    android:id="@+id/bindservicebutton" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="bindservice" 
    /> 
  <button 
    android:id="@+id/getservicebutton" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="getservice" 
    android:layout_below="@id/bindservicebutton" 
    />   
</relativelayout> 

运行结果如下:

bubuko.com,布布扣

然后举一个稍微复杂一点的栗子,注意如果*.aidl文件中含有自定义的对象,那么该对象的类要实现parcelable接口,并且要新建一个该类的aidl文件,否则会出现could not find import for class com.android.service.xx的错误,其中xx为类名。还是上面的栗子,但是aidl文件中添加了一些新的方法。仍以上面的roomservice为例,

service端的代码如下:

room类的代码为:

package com.android.service; 
 
import android.os.parcel; 
import android.os.parcelable; 
 
public class room implements parcelable{ 
 
  //房间号 
  private int roomnum; 
  //房间大小 
  private float roomspace; 
  //是否有空调 
  private boolean hasairconditioner; 
  //是否有wifi 
  private boolean haswifi; 
  //房间内的装饰风格 
  private string decorativestyle; 
   
  public static final parcelable.creator<room>creator=new parcelable.creator<room>() { 
 
    @override 
    public room createfromparcel(parcel source) { 
      return new room(source); 
    } 
 
    @override 
    public room[] newarray(int size) { 
      return null; 
    } 
     
  }; 
   
  public room(int roomnum,float roomspace,boolean hasairconditioner,boolean haswifi,string decorativestyle) 
  { 
    this.roomnum=roomnum; 
    this.roomspace=roomspace; 
    this.hasairconditioner=hasairconditioner; 
    this.haswifi=haswifi; 
    this.decorativestyle=decorativestyle; 
  } 
   
  private room(parcel source) 
  { 
    roomnum=source.readint(); 
    roomspace=source.readfloat(); 
    boolean[]temparray=new boolean[2]; 
    source.readbooleanarray(temparray); 
    hasairconditioner=temparray[0]; 
    haswifi=temparray[1]; 
    decorativestyle=source.readstring();  
  } 
   
  @override 
  public string tostring() 
  { 
    stringbuilder sb=new stringbuilder(); 
    sb.append("basic info of room is as follows:\n"); 
    sb.append("roomnum:"+roomnum+"\n"); 
    sb.append("roomspace:"+roomspace+"\n"); 
    sb.append("hasairconditioner:"+hasairconditioner+"\n"); 
    sb.append("haswifi:"+haswifi+"\n"); 
    sb.append("decorative style:"+decorativestyle); 
    return sb.tostring(); 
     
  } 
   
  @override 
  public int describecontents() { 
    return 0; 
  } 
 
  @override 
  public void writetoparcel(parcel dest,int flags) { 
    dest.writeint(roomnum); 
    dest.writefloat(roomspace); 
    dest.writebooleanarray(new boolean[]{hasairconditioner,haswifi}); 
    dest.writestring(decorativestyle);    
  } 
 
} 

room的声明为:

package com.android.service; 
parcelable room; 

iroom.aidl的代码为:

package com.android.service; 
import com.android.service.room; 
 
interface iroom 
{ 
 room getroom(); 
} 

roomservice的代码为:

package com.android.service; 
 
import android.app.service; 
import android.content.intent; 
import android.os.ibinder; 
import android.os.remoteexception; 
 
public class roomservice extends service{ 
 
  private iroom.stub mbinder=new iroom.stub() { 
     
    @override 
    public room getroom() throws remoteexception { 
      room room=new room(3008,23.5f,true,true,"ikea"); 
      return room;     
    } 
  }; 
   
  @override 
  public ibinder onbind(intent intent) { 
    return mbinder; 
  } 
 
} 

由于androidmanifest.xml的代码不变,因而此处不再贴出。下面是客户端的代码:

package com.example.aidlsampleclient; 
import com.android.service.iroom; 
import android.os.bundle; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.app.activity; 
import android.content.componentname; 
import android.content.intent; 
import android.content.serviceconnection; 
import android.util.log; 
import android.view.menu; 
import android.widget.button; 
import android.widget.toast; 
import android.view.view; 
 
public class mainactivity extends activity implements view.onclicklistener{ 
 
   
  private static final string tag="mainactivity"; 
  //private static final string service_action="com.aidl.service.data"; 
  private static final string room_service_action="com.aidl.service.room"; 
   
  private button bindservicebutton; 
  private button getservicebutton; 
   
   
  iroom mroom; 
   
  private serviceconnection conn=new serviceconnection() 
  { 
 
    @override 
    public void onserviceconnected(componentname name, ibinder service) { 
     
      log.i(tag,"----------------onserviceconnected--------"); 
      showlongtoast("onserviceconnected"); 
      mroom=iroom.stub.asinterface(service); 
    } 
 
    @override 
    public void onservicedisconnected(componentname name) { 
     
      log.i(tag,"----------------onservicedisconnected-------------"); 
      mroom=null; 
       
    } 
     
  }; 
   
  private void initview() 
  { 
    bindservicebutton=(button)findviewbyid(r.id.bindservicebutton); 
    getservicebutton=(button)findviewbyid(r.id.getservicebutton); 
    bindservicebutton.setonclicklistener(this); 
    getservicebutton.setonclicklistener(this); 
  } 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
    initview(); 
  } 
   
  @override 
  public void onclick(view v) { 
    switch(v.getid()) 
    { 
    case r.id.bindservicebutton: 
      bindservice(); 
      break; 
    case r.id.getservicebutton: 
      getservice();   
       break; 
    default: 
       break; 
    } 
  } 
   
   
  private void bindservice() 
  { 
    intent intent=new intent(); 
    intent.setaction(room_service_action); 
    bindservice(intent,conn,bind_auto_create); 
  } 
   
  private void getservice() 
  { 
    if(mroom!=null) 
    { 
      try 
      { 
        showlongtoast(mroom.getroom().tostring()); 
      } 
      catch (remoteexception e)  
      { 
        e.printstacktrace(); 
      } 
    } 
 
  } 
   
  private void showlongtoast(string info) 
  { 
    toast.maketext(getbasecontext(), info, toast.length_long).show(); 
  } 
 
  @override 
  public boolean oncreateoptionsmenu(menu menu) { 
    resent. 
    getmenuinflater().inflate(r.menu.main, menu); 
    return true; 
  } 
  @override 
  protected void ondestroy() { 
    super.ondestroy(); 
    unbindservice(conn); 
  } 
} 

注意首先仍然是要将room,iroom的代码复制过去,否则会出错。

运行结果如下:

bubuko.com,布布扣

显然,客户端已经成功读取到服务信息。

注意,上面的所举的栗子其实不只是跨进程,还是跨应用。要注意的是,跨应用一定跨进程,但是跨进程不一定是跨应用。对于跨应用的情况,利用aidl基本上是较好的解决了问题,但也只是“较好”而已,实际上并不完美,比如,如果要增加一个服务,如果利用aidl的话,那么又要改写aidl文件,如果是涉及自定义对象,则还要增加自定义对象的声明,而且这种改变不只是service端的改变,客户端也要跟着改变,显然这种解决方案不够优雅。

那么,有没有更优雅的方法呢?

当然有,那就是利用service的onstartcommand(intent intent, int flags, int startid)方法。

服务端代码如下:

package com.android.service; 
 
import android.app.service; 
import android.content.broadcastreceiver; 
import android.content.context; 
import android.content.intent; 
import android.content.intentfilter; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.util.log; 
import android.widget.toast; 
 
public class roomservice extends service{ 
 
  private static final string tag="roomservice"; 
  private static final int clean_service=0x1; 
  private static final int order_service=0x2; 
  private static final int package_service=0x3; 
  private static final string service_key="servicename";  
  @override 
  public void onstart(intent intent, int startid) { 
    showlog("onstart"); 
  } 
 
  @override 
  public int onstartcommand(intent intent, int flags, int startid) { 
    //string action=intent.getaction(); 
    log.i(tag,"onstartcommand"); 
     
    int actionflag=intent.getintextra(service_key, -1); 
   switch(actionflag) 
    { 
    case clean_service: 
      showshorttoast("start clean service right now"); 
      break; 
    case order_service: 
      showshorttoast("start order service right now"); 
      break; 
    case package_service: 
      showshorttoast("start package service right now"); 
      break; 
    default: 
      break; 
    } 
    return super.onstartcommand(intent, flags, startid); 
  } 
 
  private void showlog(string info) 
  { 
    log.i(tag,info); 
  } 
   
  private void showshorttoast(string info) 
  { 
    toast.maketext(getbasecontext(), info, toast.length_short).show(); 
  } 
  @override 
  public void ondestroy() { 
    showlog("ondestroy"); 
    super.ondestroy(); 
  } 
 
  @override 
  public void oncreate() { 
    showlog("oncreate"); 
    super.oncreate(); 
  } 
 
 
  @override 
  public ibinder onbind(intent intent) { 
    showlog("onbind"); 
    return null; 
  } 
 
  @override 
  public boolean onunbind(intent intent) { 
    showlog("onunbind"); 
    return super.onunbind(intent); 
  } 
   
} 

客户端代码如下:

package com.example.aidlsampleclient; 
import com.android.service.iroom; 
import com.android.service.roomservice; 
 
import android.os.bundle; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.app.activity; 
import android.content.componentname; 
import android.content.intent; 
import android.content.serviceconnection; 
import android.util.log; 
import android.view.menu; 
import android.widget.button; 
import android.widget.toast; 
import android.view.view; 
 
public class mainactivity extends activity implements view.onclicklistener{ 
 
  private static final string tag="mainactivity"; 
  private static final string room_service_action="com.aidl.service.room"; 
   
  private static final int clean_service=0x1; 
  private static final int order_service=0x2; 
  private static final int package_service=0x3; 
   
  private static final string service_key="servicename"; 
   
  private button cleanbutton; 
  private button orderbutton; 
  private button packagebutton; 
   
  private void initview() 
  { 
    cleanbutton=(button)findviewbyid(r.id.cleanbutton); 
    orderbutton=(button)findviewbyid(r.id.orderbutton); 
    packagebutton=(button)findviewbyid(r.id.packagebutton); 
   
    cleanbutton.setonclicklistener(this); 
    orderbutton.setonclicklistener(this); 
    packagebutton.setonclicklistener(this); 
  } 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
    initview(); 
  } 
   
  @override 
  public void onclick(view v) { 
  switch(v.getid()) 
    { 
    case r.id.cleanbutton: 
      cleanaction(); 
      break; 
    case r.id.orderbutton: 
      orderaction(); 
      break; 
    case r.id.packagebutton: 
      packageaction(); 
      break; 
    default: 
       break; 
    } 
  } 
     
  private void cleanaction() 
  { 
    startaction(room_service_action,clean_service); 
  } 
   
  private void orderaction() 
  { 
    startaction(room_service_action,order_service); 
  } 
   
  private void packageaction() 
  { 
    startaction(room_service_action,package_service); 
  } 
   
  private void startaction(string actionname,int serviceflag) 
  { 
    //intent intent=new intent(this,roomservice.class); 
    intent intent=new intent(); 
    intent.setaction(actionname); 
    intent.putextra(service_key, serviceflag); 
    this.startservice(intent); 
  } 
   
  private void showlongtoast(string info) 
  { 
    toast.maketext(getbasecontext(), info, toast.length_long).show(); 
  } 
 
  @override 
  public boolean oncreateoptionsmenu(menu menu) { 
   resent. 
    getmenuinflater().inflate(r.menu.main, menu); 
    return true; 
  } 
  @override 
  protected void ondestroy() { 
    super.ondestroy(); 
  } 
} 

运行结果如下:

bubuko.com,布布扣

显然,此时客户端顺利获取了服务。

上面举的是跨应用的例子,如果是在同一个应用的不同进程的话,则有更简单的实现方法。

roomservice的代码如下:

package com.android.service; 
 
import com.android.actions.actions; 
 
import android.app.service; 
import android.content.broadcastreceiver; 
import android.content.context; 
import android.content.intent; 
import android.content.intentfilter; 
import android.os.ibinder; 
import android.os.remoteexception; 
import android.util.log; 
import android.widget.toast; 
 
public class roomservice extends service{ 
 
  private static final string tag="roomservice"; 
  @override 
  public void onstart(intent intent, int startid) { 
    showlog("onstart"); 
  } 
 
  @override 
  public int onstartcommand(intent intent, int flags, int startid) { 
    //string action=intent.getaction(); 
    log.i(tag,"onstartcommand"); 
    string action=intent.getaction(); 
    if(actions.clean_action.equals(action)) 
    {   
      showshorttoast("start clean service right now"); 
    } 
    else if(actions.order_action.equals(action)) 
    { 
      showshorttoast("start order service right now"); 
    } 
    else if(actions.package_action.equals(action)) 
    { 
      showshorttoast("start package service right now"); 
    } 
    else 
    { 
      showshorttoast("wrong action"); 
    } 
    return super.onstartcommand(intent, flags, startid); 
  } 
 
  private void showlog(string info) 
  { 
    log.i(tag,info); 
  } 
   
  private void showshorttoast(string info) 
  { 
    toast.maketext(getbasecontext(), info, toast.length_short).show(); 
  } 
  @override 
  public void ondestroy() { 
    showlog("ondestroy"); 
    super.ondestroy(); 
  } 
 
  @override 
  public void oncreate() { 

    showlog("oncreate"); 
    super.oncreate(); 
  } 
 
 
  @override 
  public ibinder onbind(intent intent) { 
    showlog("onbind"); 
    return null; 
  } 
 
  @override 
  public boolean onunbind(intent intent) { 
    showlog("onunbind"); 
    return super.onunbind(intent); 
  } 
   
}

mainactivity的代码如下:

package com.android.activity; 
import com.android.activity.r; 
import com.android.service.roomservice; 
 
import android.app.activity; 
import android.content.intent; 
import android.os.bundle; 
import android.view.menu; 
import android.widget.button; 
import android.widget.toast; 
import android.view.view; 
import com.android.actions.actions; 
 
public class mainactivity extends activity implements view.onclicklistener{ 
 
  private static final string tag="mainactivity"; 
 
  private static final string service_key="servicename"; 
   
  private button cleanbutton; 
  private button orderbutton; 
  private button packagebutton; 
   
  private void initview() 
  { 
    cleanbutton=(button)findviewbyid(r.id.cleanbutton); 
    orderbutton=(button)findviewbyid(r.id.orderbutton); 
    packagebutton=(button)findviewbyid(r.id.packagebutton); 
   
    cleanbutton.setonclicklistener(this); 
    orderbutton.setonclicklistener(this); 
    packagebutton.setonclicklistener(this); 
  } 
  @override 
  protected void oncreate(bundle savedinstancestate) { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_main); 
    initview(); 
  } 
   
  @override 
  public void onclick(view v) { 
   switch(v.getid()) 
    { 
    case r.id.cleanbutton: 
      cleanaction(); 
      break; 
    case r.id.orderbutton: 
      orderaction(); 
      break; 
    case r.id.packagebutton: 
      packageaction(); 
      break; 
    default: 
       break; 
    } 
  } 
     
  private void cleanaction() 
  { 
    startaction(actions.clean_action); 
  } 
   
  private void orderaction() 
  { 
    startaction(actions.order_action); 
  } 
   
  private void packageaction() 
  { 
    startaction(actions.package_action); 
  } 
   
  private void startaction(string actionname) 
  { 
    intent intent=new intent(this,roomservice.class); 
    intent.setaction(actionname); 
    this.startservice(intent); 
  } 
   
  private void showlongtoast(string info) 
  { 
    toast.maketext(getbasecontext(), info, toast.length_long).show(); 
  } 
 
  @override 
  public boolean oncreateoptionsmenu(menu menu) { 
   resent. 
    getmenuinflater().inflate(r.menu.main, menu); 
    return true; 
  } 
  @override 
  protected void ondestroy() { 
    super.ondestroy(); 
  } 
} 

从打印的log可看出,客户端每调用一次context.startservice(intent),service就会重新执行一次onstartcommand---->onstart;但是使用aidl的话,绑定服务之后,不会重复执行onstart,显然后者的代价更小。

service:前台service,像我们经常用的天气、音乐其实都利用了前台service来实现

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网