【安卓笔记】全面解析Service组件

service,android四大组件之一,是一个长期运行在应用后台的android组件,它没有与用户交互的界面。

service的特点是:
1.长期运行在后台;
2.其他组件可以通过绑定操作与service进行交互;
3.默认运行在UI线程上;
4.无用户界面。
下面总结了service常见的知识点,帮助大家理解和复习service。

-------------------------------------------------------------

目录:

service分类简介

一. 如何创建一个service?  

二.如何启动/关闭service? 

三.如何绑定/解绑service?   

四.如何创建前台service?     

五.service的生命周期?   

六.如何让service与activity进行交互?     

七.使用AIDL绑定远程服务 

八.service与thread的区别?

------------------------------------------------------------

service分类简介:                                                                                                       

     
service按照类型分为前台服务后台服务
前台服务有个很明显的标志就是在任务栏有个通知(notification),就像天天动听后台播放音乐那样。而后台服务在通知栏上是没有任何显示的,只有我们去应用程序管理器中才能看到它,默认情况下我们创建的都是后台服务,比如日期同步,邮件同步等操作。之所以有前台服务,除了给用户友好的提示之外还有一个用处,那就是提高服务的优先级别,让系统不敢随便的kill掉它。众所周知,android系统是将进程分为五个级别(前台进程,可见进程,服务进程,后台进程,空进程)的,级别低的进程会在系统内存告急的时候优先被kill,而前台进程(foreground process)优先级最高,所以基本不可能被干掉。前台服务保证了服务的持久性。

service其实又可分为本地服务远程服务
本地服务中服务是依附在主线程上的(main thread),节省了资源,但是缺点是当主进程被kill掉的时候服务也会挂掉。而远程服务会单独创建一个进程,当主进程销毁之后service仍然能够运行。但是这种方式需要用到AIDL(android interface description language),比较麻烦,而且还很耗资源。其实总体而言,远程服务还是比较少见的,所以能用本地服务就用本地服务吧!

---------------------------------------------------------------------------

一. 如何创建一个service?                                                                                             


在介绍如何创建service之前,希望大家明确一点,那就是用户体验问题,因为service是运行在UI线程上的,我们一定不可以在这里处理耗时操作,如果有耗时操作,务必开子线程或者使用AsyncTask。附上文档说明:

1.编写一个类继承android.app.Service抽象类
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class ServiceExample extends Service
{
    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }
    
}
其中,onBind是抽象方法,我们必须实现。另外这个类还有一些生命周期方法如:onCreate,onDestroy,onStartCommand等。与onBind绑定方法对应的还有onUbind(解绑)方法。
下面逐一介绍各个方法:
onCreate:一看就知道是初始化的方法,当系统启动服务或者绑定服务时会调用此方法。注意,这个方法只会被调用一次,适合做初始化工作。
onStartCommand:每次在activity中调用startService时,都会调用这个方法,但是绑定服务(bindService)时并不会调用这个方法。
    参数:
        Intent intent:即调用startService方法中传递进来的intent。
        int flags:启动服务的请求所携带的附加数据。
        int startID:一个唯一的整型数代表了当前的开启请求。
onDestroy:没什么好说的,这个方法用于销毁sevice。通常在这个方法中关闭资源,比如关闭线程,取消注册广播接收者等等。当在activity中调用stopService或者unBindService时都会调用这个方法。
onBind:当绑定服务(bindService)时会调用此方法。这个方法比较有意思,可以看到它的返回值是IBinder接口,这个接口是用来和与之绑定的组件进行通信的。一旦绑定了服务,这个IBinder便会返回给绑定组件,在该组件中我们就能通过调用IBinder中的方法间接与service进行通信了!当然这涉及了另一个类:ServiceConnection,这是一个服务连接器,关于这个类,我们在下一部分再介绍。
onUnBind:顾名思义,当组件与service取消绑定时,会调用这个方法。
注:跟activity不一样,service生命周期方法中不需调用父类方法。
   2.在清单文件AndroidManifest.xml中配置service
    在application节点下创建一个service节点:
<service android:name="com.example.xxx"></service>
还有一些常见选项:

android:name:服务类名

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

android:icon:服务的图标

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

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

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

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

具体的使用案例我放在了后面,请继续向下看。


二.如何启动/关闭service?                                                                                             


 其实在上面已经多次提到启动服务了,其实启动/关闭service非常简单,两个api搞定: 
startService(Intent intent)//启动服务
stopService(Intent intent)//关闭服务
ntent用于指定我们要开启的服务,可以用显示意图也可以用隐式意图。显式意图很简单,直接指明开启的服务的类名即可,像这样:
Intent intent1 = new Intent(this,MyService.class);
如果使用隐式意图,首先我们需要在配置service时增加一个意图过滤器:
 <service android:name="com.example.service.PayService">   
         <intent-filter>
                <action android:name="com.example.pay"/>
            </intent-filter>            
 </service>
然后我们开启服务的时候,也指定action:
Intent intent = new Intent("com.example.pay");
关闭服务同理,此处不再敖述。

三.如何绑定/解绑service?                                                                                               


这个比启动/关闭service稍微复杂点,关键是理解流程,api都是很简单的。下面先从api开始介绍:
public boolean bindService(Intent service, ServiceConnection conn, int flags)//绑定
public void unbindService(ServiceConnection conn)//解绑
bindService方法接收三个参数:
Intent service:指定要绑定的服务,跟startService的参数相同,不再介绍。
int flags:绑定选项,这个参数指定绑定的一些选项,常用的有BIND_AUTO_CREATE(这个参数很吊,当调用bindService时,如果没有开启服务,将会自动开启服务),BIND_DEBUG_UNBIND等
ServiceConnection conn:服务连接器.这个对象用于和service进行通信,之前我们在创建service时有个必须实现的方法叫onBind,这个方法返回的IBinder对象将会被此连接器获取,进而此连接器通过调用IBinder的方法与service交互。
这是一个接口类型,我们可以看看接口声明:
public interface ServiceConnection {
    public void onServiceConnected(ComponentName name, IBinder service);
    public void onServiceDisconnected(ComponentName name);
}
此接口有两个方法,从名字就可以看出来,一个是在与服务建立连接的时候调用,另一个是与服务失去连接的时候调用。其中onServiceConnected方法的第二个参数就是之前onBind返回的IBinder的引用!所以onBind方法如果返回null此处就获取不到IBinder引用了。我们将在后面介绍activity与service进行交互,具体使用示例请继续往下看。
unbindService方法接收的参数也是这个连接器,需要注意的是两个连接器必须是同一个,故我们通常将连接器设为全局的变量。另外还有一个需要注意的点,服务只能被解除绑定一次,如果第二次解除绑定,将会抛出异常。但是为了确保service解绑,通常会在activity的onDestroy中解绑service,为了不抛异常,我们有两种解决方案。一是定义一个标志位,表示当前是否已经解除绑定,这样只要判断标志位即可。另外一种方案是将unbindService方法try起来:
try
   {
     if(conn != null)
         unbindService(conn);
    } catch (Exception e)
     {
      }

  

四.如何创建前台service?                                              


在文章一开始我们介绍srvice分类的时候就已经说了前台service,故这里不再介绍前台service,咱们直接创建一个前台service。创建前台service需要调用一个api叫startForeground(),但是这个方法是在2.0之后才有的,在之前的版本,我们使用setForeground()方法。文档里面有如何兼容旧版本的demo程序,用到了反射。在这里为了方便起见,直接使用新版api了:
public final void startForeground(int id, Notification notification)
public final void stopForeground(boolean removeNotification)
  首先新建一个android工程,然后创建一个类继承service:
package com.example.foregroundservicedemo.service;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.example.foregroundservicedemo.MainActivity;
import com.example.foregroundservicedemo.R;
public class MyService extends Service
{
    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }
    
    @Override
    public void onCreate()
    {
        Notification notification = new Notification(R.drawable.notification,  "您有一条新通知", System.currentTimeMillis()); 
        Intent notificationIntent = new Intent(this, MainActivity.class);  
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,notificationIntent, 0);  
        notification.setLatestEventInfo(this, "通知的标题", "通知的内容",pendingIntent);   
      
        startForeground(1, notification); //开启前台服务 
    }
    @Override
    public void onDestroy()
    {
        stopForeground(true);//停止前台服务,并销毁通知
    }
}
可以看到,我们在onCreate方法中创建了一个通知,然后调用startForefround方法使当前service以前台方式运行,在onDestroy方法中停止前台服务,并销毁通知。
然后别忘了在清单文件中配置:
<service android:name="com.example.foregroundservicedemo.service.MyService"></service>
最后,我们编写一个activity,添加两个按钮分别用于启动/停止服务,代码很简单,下面只贴出onclick方法:
public void onClick(View v)
    {
        switch (v.getId())
        {
        case R.id.but_start:
            Intent intent = new Intent(this,MyService.class);
            this.startService(intent);
            break;
        case R.id.but_stop:
            Intent intent2 = new Intent(this,MyService.class);
            this.stopService(intent2);
            break;
        }
}
好了,大功告成,下面测试下:
主界面:

开启服务:

五.service的生命周期?                                               


上面介绍了好几种方式开启服务,下面总结下:
1.使用startService方式开启;
2.使用bindService方式开启;
其实还有第三种:
3.既startService又bindService。
那这三种方式开启服务的生命周期如何呢?我们先看下图:
从这个图可以看到前两种启动方式的生命周期,总结如下:

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

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

 至于第三种比较特殊了,我们也来总结下它的生命周期:
        3). 被启动又被绑定的服务的生命周期如果一个Service又被启动又被绑定,则该Service将会           一直在后台运行。并且不管如何调用,onCreate始终只会调用一次,对应startService调用多少次,  Service的onStart便会调用多少次。只调用unbindService将不会停止Service,只调用stopService也不会停止service,只有同时调两个方法才能销毁service。

光这么总结大家可能觉得比较生硬,其实我们可以采用打LOG的方式研究其生命周期,新建一个service如下,清单文件这里就不贴了:
package com.example.servicedemo2.myservice;

import com.example.servicedemo2.binder.IService;

import android.app.Service;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class MyService extends Service
{
    private static final String TAG = "MyService";

    @Override
    public void onCreate()
    {
        Log.i(TAG,"服务被创建");
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        Log.i(TAG,"服务被绑定");
        return new MyBinder();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        Log.i(TAG,"onstartCommand方法调用,startId = "+startId);
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy()
    {
        Log.i(TAG,"服务被销毁");
    }
    private class MyBinder extends Binder implements IService
    {
        @Override
        public void func()
        {
            myServiceFunc();//通过一个binder将服务中的方法暴露给外界
        }
    }
    /**
     * 服务中的私有的方法
     */
    private void myServiceFunc()
    {
        Toast.makeText(getApplicationContext(),"成功调用了服务中的方法...", 0).show();
    }

    @Override
    public boolean onUnbind(Intent intent)
    {
        Log.i(TAG,"服务被解除绑定...");
        return super.onUnbind(intent);
    }
}
测试的activity,布局也不贴了。
package com.example.servicedemo2;

import com.example.servicedemo2.binder.IService;
import com.example.servicedemo2.myservice.MyService;

import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

/**
 * @author Rowand jj
 *
 */
public class MainActivity extends Activity implements OnClickListener
{
    protected static final String TAG = "MainActivity";
    private Button but_start,but_stop,but_bind,but_unbind,but_func;
    private IService s = null;
    private ServiceConnection conn = null;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        but_start = (Button) findViewById(R.id.but_start);
        but_stop = (Button) findViewById(R.id.but_stop);
        but_bind = (Button) findViewById(R.id.but_bind);
        but_unbind = (Button) findViewById(R.id.but_unbind);
        but_func = (Button) findViewById(R.id.but_func);
        
        but_start.setOnClickListener(this);
        but_stop.setOnClickListener(this);
        but_bind.setOnClickListener(this);
        but_unbind.setOnClickListener(this);
        but_func.setOnClickListener(this);
    }
    @Override
    public void onClick(View v)
    {
        switch (v.getId())
        {
        case R.id.but_start:
            Intent intent1 = new Intent(this,MyService.class);
            this.startService(intent1);
            break;
        case R.id.but_stop:
            Intent intent2 = new Intent(this,MyService.class);
            this.stopService(intent2);
            break;
        case R.id.but_bind:
            Intent intent3 = new Intent(this,MyService.class);
            conn = new ServiceConnection()
            {
                @Override
                public void onServiceDisconnected(ComponentName name)
                {
                    Log.i(TAG,"onServiceDisconnected运行了...");
                }
                
                @Override
                public void onServiceConnected(ComponentName name, IBinder service)
                {
                    Log.i(TAG,"成功连接上了服务,现在可以调用服务中的方法了");
                    s = (IService) service;//因为我们知道在Myservice中的IBinder对象继承了Binder并实现了IService接口
                }
            };
            this.bindService(intent3, conn, Service.BIND_AUTO_CREATE);
            break;
        case R.id.but_unbind:
            if(conn != null)
                this.unbindService(conn);
            break;
        case R.id.but_func:
            if(s != null)
            {
                s.func();//调用IBinder的方法间接与service通信
            }
            else
            {
                Toast.makeText(this,"还没有绑定服务...",0).show();
            }
            break;
        }
    }
    @Override
    protected void onDestroy()//activity销毁时,为避免报错,自动解除绑定service,之所以try起来,是因为service只能被解除绑定一次
    {
        super.onDestroy();
        try
        {
            if(conn != null)
                unbindService(conn);
        } catch (Exception e)
        {
        }
    }

}
IService接口:
package com.example.servicedemo2.binder;
public interface IService
{
    public void func();
}
运行效果:

有了这个程序,观察生命周期就方便了,只要观察logcat就行了,这里我就不截图了,读者如果有兴趣可以下载示例代码(最下方),自己运行下,以加深对service的理解。
这里我们读者可能看到我创建了一个IService接口,并让自定义的MyBinder对象实现了IService方法,之所以这样做,是为了提高封装性,读者注意看,我将MyBinder定义成了Service中的私有类
,这样里面的方法就不会全部暴露,我们将想暴露给外界的方法抽象到IService接口中,这样外界只能访问到我们想暴露的方法了。

六.如何让service与activity进行交互?                                 


通常service与activity交互的方式有两种:
1.使用bindservice方式交互。
    这种交互方式在上面那个例子中已经体现出来了,绑定服务后,通过service返回的IBinder对象即可调用service中的方法。
2.使用broadcastReceiver交互(注意性能问题,广播接收者调用时间有限制)
    不熟悉广播接收者的请看这篇文章
    我们也是通过一个实例代码说明。原理很简单,在service内部创建广播接收者,接收一个固定的动作(action),然后在activity中发送广播即可。
    首先来看service(广播接收者采用动态注册的方式声明):
package com.example.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.widget.Toast;

public class MyService extends Service
{
    private MyReceiver receiver = null;
    @Override
    public IBinder onBind(Intent intent)
    {
        return null;
    }
    @Override
    public void onCreate()
    {
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.example.broadcast");//监听的动作
        receiver = new MyReceiver();
        this.registerReceiver(receiver, filter);//注册广播接收者
    }
    /**
     * 服务中的方法
     */
    private void serviceMethod()
    {
        Toast.makeText(getApplicationContext(),"我是服务中的方法...",0).show();
    }
    @Override
    public void onDestroy()
    {
        if(receiver!=null)
            this.unregisterReceiver(receiver);//解除注册
    }

    public class MyReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            serviceMethod();
        }
    }
}
测试的activity:
package com.example.myservicedemo3;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.example.service.MyService;

public class MainActivity extends Activity
{    
    private Button but_send = null;
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
//        开启service服务
        Intent intent = new Intent(this,MyService.class);
        this.startService(intent);
        
        but_send = (Button) findViewById(R.id.but_send);
        but_send.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                Intent broadcast = new Intent();
                broadcast.setAction("com.example.broadcast");//和service中的receiver接收的action一致
                MainActivity.this.sendBroadcast(broadcast);//发广播
            }
        });
        
    }
}
布局与清单文件略。
运行时,只有点击button,service中的广播接收者便会受到广播,进而调用service的方法。

七.使用AIDL绑定远程服务                                              


   这不是本文重点,下面简单以一个例子说明,如果读者想进一步了解,可以查看官方文档。
所谓aidl,就是一个接口定义语言,用于服务间的跨进程通信。比如你的应用需要调用支付宝的支付服务,这就需要使用到aidl了。下面的例子正是基于此场景:
第一步:新建一个android工程,作为”支付服务“。
package com.example.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class PayService extends Service
{
    private static final String TAG = "PayService";
    @Override
    public IBinder onBind(Intent intent)
    {
        Log.i(TAG,"绑定了支付服务...");
        return new MyBinder();
    }
    
    
    @Override
    public boolean onUnbind(Intent intent)
    {
        Log.i(TAG,"解除绑定支付服务...");
        return false;
    }
    private class MyBinder extends IService.Stub
    {
        @Override
        public void callServiceFunc()
        {
            pay();
        }
    }
    
    /**
     * 服务内部的支付方法
     */
    private void pay()
    {
        Log.i(TAG,"调用了支付的方法...");
    }
}
读者肯定注意到这里的MyBinder继承了IService.Stub,这个类是什么呢?
读者还记得上面的IService接口么,那个接口是将Binder中想暴露给外界的方法抽象出来而形成的。IService接口不仅在服务中调用,还要在Activity中调用,一个应用程序内部接口是公用的,但是不同的应用程序之间如果想公用一个接口,必须有某种机制才行。这就引出aidl了,下面我们建立一个.aidl格式的”接口“:
package com.example.service;
interface IService
{
    void callServiceFunc();
}
可以看到,这个aidl接口跟接口貌似没什么区别,除了后缀不一样之外。读者走到这一步请在看下R文件,看看有没有变化,如果aidl没错误的话,R文件中会自动生成这样一个文件:IService.java
,我们的IService.Stub就来自这里。这里的Stub类已经自动继承了Binder类!我们在其他应用如果想使用该接口,只需将aidl文件与对应包名拷贝到工程下即可,另外在ServiceConnection方法中可以通过:Stub.asInterface方法将Binder对象转化为IService对象。
工程截图如下:
第二步:在建一个android工程,作为第三方应用。
package com.example.thirdapplication;

import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

import com.example.service.IService;
import com.example.service.IService.Stub;

/**
 * @author Rowand jj
 *
 *这是第三方的应用,用来远程调用支付服务。
 *使用aidl调用远程服务。
 *    aidl:Android Interface Definition Language
 */
public class MainActivity extends Activity implements OnClickListener
{
    private Button but_bind = null;
    private Button but_pay = null;
    
    private IService myService = null;
    private ServiceConnection conn = null;
    
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        but_bind = (Button) findViewById(R.id.but_bind);
        but_pay = (Button) findViewById(R.id.but_pay);
        
        but_bind.setOnClickListener(this);
        but_pay.setOnClickListener(this);
    }

    @Override
    public void onClick(View v)
    {
        switch (v.getId())
        {
        case R.id.but_bind:
            Intent intent = new Intent("com.example.pay");
            conn = new ServiceConnection()
            {
                @Override
                public void onServiceDisconnected(ComponentName name)
                {
                }
                @Override
                public void onServiceConnected(ComponentName name, IBinder service)
                {
                    myService = Stub.asInterface(service);
                }
            };
            this.bindService(intent, conn, Service.BIND_AUTO_CREATE);
            Toast.makeText(this,"绑定成功...", 0).show();
            break;
        case R.id.but_pay:
            try
            {
                if(myService!=null)
                    myService.callServiceFunc();
            } catch (RemoteException e)
            {
                e.printStackTrace();
            }
            break;
        }
    }
    
    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        if(conn != null)
            this.unbindService(conn);
    }

}
工程截图如下:
运行截图:



八.service与thread的区别?                                           


service与thread是两个完全不同的概念。他们的使用场景也不一样
thread通常用于处理耗时操作,而service则是长期运行于后台的组件。它们的生命周期是不一样的。
1.thread的生命周期是不定的,当创建thread的组件比如activity被销毁后,thread如果没有执行完毕,该thread仍然会继续执行,这样该activity就不再只有thread的引用了,这导致该线程变得不可控。。
2.service是长期运行于后台的组件。它的生命周期很长,只有当在代码中调用unbindservice、stopservice、stopself或者在手机进程列表中kill掉该进程,service才会被销毁。当然,系统内存非常低的情况下,也会干掉service。
你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。


---------------------------------------------------------------------------------------------------------------------------------------------------

示例代码打包下载
------------------------------------------------------------------------------------------------------------------------------------------------------
ps.写一篇博客不容易,希望尊重作者的劳动成果,转载请声明出处:http://blog.csdn.net/chdjj/article/details/19333171








郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。