AIDL完全学习手册

1.引言

打开Android源码,会发现在有些包的里面,在各个java类下面总是会有若干白色图标的文件,后缀为aidl。双击打开,也是一篇黑白,没有java代码的关键字变色突示,写法似java又总有些不同。这个文件好生怪异!

它到底是什么?有什么作用?怎么使用?

2.释义

AIDL(Android Interface Definition Language,Android接口定义语言)。

很多文章在引出AIDL之前,都会问“Android进程间如何通信?”,然后说出“是的,用AIDL的方式可以做到”。可是,每遇到这里脑中便会弹出一个问题“什么时候进程之间需要相互通信?”可能是碍于编程经验还很不足,才会问出后面这个问题。不过,我倒是真的很想知道这一点先。

2.1进程间通信

那么先认识进程间通信。随着人们对应用程序的要求越来越高,单进程应用在许多场合已不能满足人们的要求。编写多进程/多线程程序成为现代程序设计的一个重要特点,在多进程程序设计中,进程间的通信是不可避免的。

进程间通信就是在不同进程之间传播或交换信息,那么不同进程之间存在着什么双方都可以访问的介质呢?进程的用户空间是互相独立的,一般而言是不能互相访问的,唯一的例外是共享内存区。但是,系统空间却是”公共场所”,所以内核显然可以提供这样的条件。除此以外,那就是双方都可以访问的外设了。在这个意义上,两个进程当然也可以通过磁盘上的普通文件 交换信息,或者通过”注册表 “或其它数据库中的某些表项和记录交换信息。广义上这也是进程间通信的手段,但是一般都不把这算作”进程间通信”。因为那些通信手段的效率太低了,而人们对进程间通信的要求是要有一定的实时性。

进程间通信主要包括管道,系统IPC(包括消息队列,信号量,共享存储),SOCKET。

2.2AIDL服务

Java中不允许跨进程内存共享。因此传递对象,只能把对象拆分成操作系统能理解的简单形式,以达到跨界对象访问的目的。在Android系统中,采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案已于,Android使用一种借口定义语言(Interface Definition Language,IDL)来公开服务的接口。因此,可以将这种跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。

编译器可以通过AIDL文件生成一段代码,通过预先定义的接口达到两个进程内部通信的目的。如果需要在一个Activity中,访问另一个Service中的某个对象,需要先将对象转化成AIDL可识别的参数(可能是多个参数),然后使用AIDL来传递这些参数,在消息的接收端使用这些参数组装成自己需要的对象。

3.语法

先讲AIDL的语法,这样就可以解释为什么看到的aidl文件有些怪异了。

它并不是一门真正的编程语言,只是定义两个进程之间的通信接口,因此语法非常简单。它的语法与Java接口很相似,但存在以下几点差异:

3.1支持的数据类型:

  1. Java的简单类型(int、char、boolean等)。不需要导入(import)。
  2. String和CharSequence。不需要导入(import)。
  3. List和Map。但要注意,List和Map对象的元素类型必须是AIDL服务支持的数据类型。不需要导入(import)。
  4. AIDL自动生成的接口。需要导入(import)。
  5. 实现android.os.Parcelable接口的类。需要导入(import)。

3.2导包

aidl文件中的package和import语句需要自己添加,eclipse不会和.java文件一样自动添加。后两种数据类型需要使用import进行导入,即使它们在同一个包中也需要导包。

3.3注释

跟 java的一样,AIDL文件可以有注释,在package以前的注释将会被忽略,方法和变量以前的注释都会被加入到生产java代码中。

3.4实现接口时的几个原则:

  1. 抛出的异常不要返回给调用者,跨进程抛异常处理是不可取的;
  2. IPC调用是同步的,如果你知道一个IPC服务需要超过几毫秒的时间才能完成的话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单起一个线程来处理;
  3. 不能在AIDL接口中声明静态属性。

4.使用

讲完语法,再讲怎么从无到有建立并使用它。

IPC的调用步骤:

  1. 声明一个接口类型的变量,该接口类型在.aidl文件中定义。
  2. 实现ServiceConnection。
  3. 调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递。
  4. 在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的 Service)。调用YourInterfaceName.Stub.asInterface((IBinder)service)将参数转换为

    YourInterface类型。

  5. 调用接口中定义的方法。你总要检测到DeadObjectException异常,该异常在连接断开时被抛出。它只会被远程方法抛出。
  6. 断开连接,调用接口实例中的ApplicationContext.unbindService()。

建立AIDL服务要比建立普通的服务复杂一些,具体步骤如下:

  1. 在EclipseAndroid工程的Java包目录中建立一个扩展名为aidl的文件。
  2. 如果aidl文件的内容是正确的,ADT会自动在gen目录下生成一个Java接口文件(*.java)。
  3. 建立一个服务类(Service的子类)。
  4. 实现由aidl文件生成的Java接口。
  5. 在AndroidManifest.xml文件中配置AIDL服务,尤其要注意的事,标签中android:name的属性值就是客户端要引用该服务的ID,也就是Intent类的参数值。

5.简单实例

接下来让我们动手实现。

我们建立一个简单的AIDL服务。这个服务只有一个getValue方法,该方法返回一个String类型的值。在安装完服务后,会在客户端调用这个getValue方法,并将返回值在TextView组建中输出。

1.先建立服务端,工程名为AidlService2。

2.新建IMyService.aidl,如下图:

图1 新建aidl文件

3.键入代码如下:

package com.eyelike.mobile.aidl;
interface IMyService {
    String getValue();
}

4.此时,ADT会自动在gen/com.eyelike.mobile.aidl目录下面生成一个IMyService.java的文件,如下图所示。我们一般并不需要关心这个文件的具体内容,也不需要维护它。

图2 根据aidl文件自动生成接口类

5.接下来定义一个Service实现类MyService.java。MyService继承Service,在MyService类中定义一个内部类MyServiceImpl,该类是IMyService.Stub的子类。MyService代码如下:

package com.eyelike.mobile.aidl;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

public class MyService extends Service {

    public class MyServiceImpl extends IMyService.Stub {

        @Override
        public String getValue() throws RemoteException {
            return "Android AIDL Study by zhaozhao.li";
        }

    }
    /*
    // method2:继承Stub,也就是实现了IMyService接口,并实现了IBinder接口
    // 需要import com.eyelike.mobile.aidl.IMyService.Stub;
    private IMyService.Stub MyServiceImpl2 = new Stub() {

        @Override
        public String getValue() throws RemoteException {
            return "Android AIDL Study by zhaozhao.li";
        }
    };
    */
    @Override
    public IBinder onBind(Intent arg0) {
        // onBind方法必须返回MyServiceImpl类的对象实例,否则客户端无法获得服务对象
        return new MyServiceImpl();
        /*
        // method2:
        // 如果使用第二种方式,则return这里需要稍做修改为下面形式。
        return MyServiceImpl2;
        */
    }

}

6.接着,需要在AndroidManifest.xml文件中配置该Service,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.eyelike.mobile.aidl"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AidlService2Activity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MyService">
            <intent-filter>
                <action android:name="com.eyelike.mobile.aidl.IMyService"/>
            </intent-filter>
        </service>
    </application>

</manifest>

7.以上,就完成类AIDL服务的建立。接下来,就可以实现客户端进行调用该服务了。我们先新建一个工程AidlClient2作为客户端。

8.AIDL定义了两个进程之间的通信接口,因此,不仅服务器端需要AIDL接口,客户端同样需要前面定义的AIDL接口,因此新建号客户端工程后的第一步就是将Service端的AIDL接口文件IMyService.java复制到客户端应用中,复制到客户端后ADT工具会为AIDL接口生成相应的实现。

因为服务端的接口文件所在的包与客户端的包名不同,因此复制的时候,先在客户端建立该接口类IMyService.java相同包名的包。如下图所示:

图3 新建包

9.然后将服务端gen/com.eyelike.mobile.aidl目录下的IMyService.java文件拷贝至刚才新建的包的下面。如下图:

图4 拷贝IMyService.java文件

10.接下来我们编写客户端的调用代码,在AidlClient2Activity.java中键入下面代码:

package com.eyelike.mobile;

import com.eyelike.mobile.aidl.IMyService;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class AidlClient2Activity extends Activity implements OnClickListener {
    private IMyService mMyService = null;
    private Button mBtnInvokeAIDLService;
    private Button mBtnBindAIDLService;
    private TextView mTextView;
    private ServiceConnection mServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获得服务对象
            mMyService = IMyService.Stub.asInterface(service);
            mBtnInvokeAIDLService.setEnabled(true);
        }
    };
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mBtnInvokeAIDLService = (Button) findViewById(R.id.btn_call_service);
        mBtnBindAIDLService = (Button) findViewById(R.id.btn_bind_service);
        mBtnInvokeAIDLService.setEnabled(false);
        mTextView = (TextView) findViewById(R.id.tv_show);
        mBtnInvokeAIDLService.setOnClickListener(this);
        mBtnBindAIDLService.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.btn_bind_service:
            // bind AIDL service
            bindService(new Intent("com.eyelike.mobile.aidl.IMyService"),
                    mServiceConnection, Context.BIND_AUTO_CREATE);
            break;
        case R.id.btn_call_service:
            try {
                // 调用服务端的getValue方法
                mTextView.setText(mMyService.getValue());
            } catch (Exception e) {

            }
        default:
            break;
        }

    }
}

在编写上面的代码时需要特别注意以下两点:

  1. 使用bindService方法来绑定AIDL服务。其中需要使用Intent对象指定AIDL服务的ID,该ID也就是我们之前在服务端的AndroidManifest.xml文件中定义的标签中android:name属性的值
  2. 在绑定时需要一个ServiceConnection对象。创建ServiceConnection对象的过程中如果绑定成功,系统会调用onServiceConnected方法,通过该方法的service参数值可以获得AIDL服务对象。

11.首先运行AIDL服务程序,然后运行客户端程序,单击【绑定AIDL服务】按钮,如果绑定成功,【调用AIDL服务】按钮会变为可选状态,单击这个按钮,会输出getValue方法的返回值,如下图所示:

图5 实例运行效果

6.进阶实例-传递复杂数据的AIDL服务

在前面讲AIDL支持的数据类型的时候提到AIDL服务只支持有限的数据类型,上面的例子比较简单,如果用AIDL服务传递一些复杂的数据就需要做更一步处理。传递不需要import的数据类型的值方式与上面的例子相同。传递一个需要import的数据类型的值(例如实现android.os.Parcelable接口的类)的步骤要复杂一些。除了要建立一个实现android.os.Parcelable接口的类外,还需要为这个类单独建立一个aidl文件,并使用parcelable关键字进行定义。

具体的实现步骤如下:

1.建立复杂数据传递的服务端,工程名为AidlService3。

2.建立一个IMyService.aidl文件,键入如下代码:

package com.eyelike.mobile.complex.type.aidl;

import com.eyelike.mobile.complex.type.aidl.Product;
interface IMyService {
    Map getMap(in String country, in Product product);
    Product getProduct();
}

在编写上面的代码时需要注意如下两点:

  1. Product是一个实现android.os.Parcelable接口的类,需要使用import导入这个类。
  2. 如果方法的类型是非简单类型,例如String、List或自定义的类,需要使用in、out或inout修士。其中in表示这个值被客户端设置;out表示这个值被服务端设置;inout表示这个值既被客户端设置,又被服务端设置。

3.编写Product类。该类是用于传递的数据类型,代码如下:

package com.eyelike.mobile.complex.type.aidl;

import android.os.Parcel;
import android.os.Parcelable;

public class Product implements Parcelable {
    private int id;
    private String name;
    private float price;
    public static final Parcelable.Creator<Product> CREATOR = new Creator<Product>() {

        @Override
        public Product[] newArray(int size) {
            return new Product[size];
        }

        @Override
        public Product createFromParcel(Parcel in) {
            return new Product(in);
        }
    };
    public Product() {

    }
    public Product(Parcel in) {
        readFromParcel(in);
    }

    private void readFromParcel(Parcel in) {
        id = in.readInt();
        name = in.readString();
        price = in.readFloat();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
        dest.writeFloat(price);
    }

    public int getId() {
        return id;
    }

    public void setId(int aId) {
        this.id = aId;
    }

    public String getName() {
        return name;
    }

    public void setName(String aName) {
        this.name = aName;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float aPrice) {
        this.price = aPrice;
    }

}

在编写Product类时应注意如下3点:

  1. Product类必须实现android.os.Parcelable接口。该接口用于序列化对象。在Android中之所以使用Parcelable接口序列化,而不是java.io.Serializable接口,是因为Google在开发Android时发现Serializable序列化的效率并不高,因此特意提供了一个Parcelable接口来序列化对象。
  2. 在Product类中必须有一个静态常量CREATOR,它的名字必须是CREATOR,而且数据类型必须是Parcelable.Creator。
  3. 在writeToParcelable方法中需要将要序列化的值写入Parcel对象。

4.建立一个Product.aidl文件,键入下面代码:

parcelable Product;

这里特别说明:由于这个aidl文件中并未声明接口,所以,并不会在gen目录下面自动生成和它相关的Product.java文件。而且回过头来看我们第3步的工作,我们自己已经将其实现了。

5.编写一个MyService类,代码如下:

package com.eyelike.mobile.complex.type.aidl;

import java.util.HashMap;
import java.util.Map;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

/**
 * AIDL Service
 * */
public class MyService extends Service {
    public class MyServiceImpl extends IMyService.Stub {

        @Override
        public Map getMap(String country, Product product)
                throws RemoteException {
            Map mMap = new HashMap<String, String>();
            mMap.put("country", country);
            mMap.put("id", product.getId());
            mMap.put("name", product.getName());
            mMap.put("price", 31000);
            mMap.put("product", product);
            return mMap;
        }

        @Override
        public Product getProduct() throws RemoteException {
            Product mProduct = new Product();
            mProduct.setId(1234);
            mProduct.setName("汽车");
            mProduct.setPrice(31000);
            return mProduct;
        }

    }
    @Override
    public IBinder onBind(Intent intent) {
        return new MyServiceImpl();
    }

}

6.在AndroidManifest.xml文件中配置MyService类,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.eyelike.mobile.complex.type.aidl"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AidlService3Activity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".MyService">
            <intent-filter>
                <action android:name="com.eyelike.mobile.complex.type.aidl.IMyService"/>
            </intent-filter>
        </service>
    </application>

</manifest>

7.至此,传递复杂数据的AIDL服务端就完成了。接下来建立调用该服务的客户端,工程名为AidlClient3。

8.首先还是需要将服务端的文件复制到客户端工程中去。这里需要特别注意:与第一个例子不同的是,由于我们需要传递复杂的数据(Product),因此,除了IMyService.java文件,Product.java文件也必须一同复制过去。方法还是先建立它们的包,然后复制进该包内。如下图:

图6 复制服务端文件到客户端

9.然后,在AidlClient3Activity.java中键入如下代码:

package com.eyelike.mobile;

import com.eyelike.mobile.complex.type.aidl.IMyService;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class AidlClient3Activity extends Activity implements OnClickListener{
    private IMyService mMyService = null;
    private Button mBtnInvokeAIDLService;
    private Button mBtnBindAIDLService;
    private TextView mTextView;
    private ServiceConnection mServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获得AIDL服务对象
            mMyService = IMyService.Stub.asInterface(service);
            mBtnInvokeAIDLService.setEnabled(true);
        }
    };
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mBtnInvokeAIDLService = (Button) findViewById(R.id.btn_call_service);
        mBtnBindAIDLService = (Button) findViewById(R.id.btn_bind_service);
        mBtnInvokeAIDLService.setEnabled(false);
        mTextView = (TextView) findViewById(R.id.tv_show);
        mBtnInvokeAIDLService.setOnClickListener(this);
        mBtnBindAIDLService.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.btn_bind_service:
            // 绑定AIDL服务
            bindService(new Intent("com.eyelike.mobile.complex.type.aidl.IMyService"),
                    mServiceConnection, Context.BIND_AUTO_CREATE);
            break;
        case R.id.btn_call_service:
            try {
                String mStr = "";
                // 调用AIDL服务中的方法
                mStr = "Product.id = " + mMyService.getProduct().getId() + "\n";
                mStr += "Product.name = " + mMyService.getProduct().getName() + "\n";
                mStr += "Product.price = " + mMyService.getProduct().getPrice() + "\n";
                mStr += mMyService.getMap("China", mMyService.getProduct()).toString();
                mTextView.setText(mStr);
            } catch (Exception e) {

            }
            break;
        default:
            break;
        }

    }
}

10.首先运行服务端程序,然后运行客户端程序,单击【绑定AIDL服务】按钮,待成功绑定后单击【调用AIDL服务】按钮,会输出如下图所示内容:

图7 实例运行效果

7.总结

经过上面的例子练习,我们能够发现AIDL的使用流程分为三步:aidl→Service→Client。浓缩为这三步的时候,就能恍然发现,我们做的就是在Service和Client间使用aidl的过程。

三步的阐述如下:

(1)建立aidl文件,在其中声明接口。gen目录自动生成.java文件,拥有内部类Stub.java。

(2)建立Service文件,在其中继承Stub即可实现aidl中声明的方法,在AndroidManifest.xml文件中声明aciton。

(3)建立Client文件,在其中建立ServiceConnection实例,实现onServiceConnected方法,在其中通过IMyService.Stub.asInterface(service)方法即可获得AIDL服务对象。有了该对象便可使用它的各种方法与Service进行通信。

(4)附加一条复杂数据阐述:自定义的数据类必须implements Parcelable,必须添加一个名为CREATOR的静态成员,对数据的读写定义在readFromParcel和writeToParcel方法中,并建立一个对应的aidl文件进行声明。

整个过程并没有特别难以理解的地方,但由于需要记忆性特别注意实现的地方较多,因此,独立实现AIDL服务,尤其是复杂数据的传递,对于一个新手来说还是颇具难度的。以上简单阐述就是为了在记忆有些模糊的时候便于快速回忆和确认的。凡事熟能生巧,只要真正理解了AIDL服务的实现思路,相信我们都可掌握它!

参考文献

  1. 《疯狂Android讲义》第10章10.2节 跨进程调用Service(AIDL服务)
  2. 《Android开发完全讲义》第8章8.4节 跨进程访问(AIDL服务)
  3. http://wayfarer.iteye.com/blog/562836
  4. http://dwtf55dwtf.iteye.com/blog/1362796
    • [email protected]
时间: 2024-10-13 09:31:11

AIDL完全学习手册的相关文章

Redis学习手册(目录)

Posted on 2012-04-16 07:40 Stephen_Liu 阅读(29155) 评论(25) 编辑 收藏 为什么自己当初要选择Redis作为数据存储解决方案中的一员呢?现在能想到的原因主要有三.其一,Redis不仅性能高效,而且完全免费.其二,是基于C/C++开发的服务器,这里应该有一定的感情因素吧.最后就是上手容易,操作简单.记得在刚刚接触Redis的时候,由于当时项目的工期相当紧张,留给我们做出选择的空间也是非常有限,一旦技术决策失误,造成的后果也比较严重.所以在做出决定之

SQLite学习手册(目录)

Posted on 2012-03-09 07:36 Stephen_Liu 阅读(11956) 评论(22) 编辑 收藏 在实际的应用中,SQLite作为目前最为流行的开源嵌入式关系型数据库,在系统的架构设计中正在扮演着越来越为重要的角色.和很多其它嵌入式NoSQL数据库不同的是,SQLite支持很多关系型数据库的基本特征,这在数据移植.程序演示等应用中有着不可替代的优势.从官方文档中我们可以获悉到,SQLite支持的数据量和运行效率都是非常骄人的,因此在海量数据的解决方案中,SQLite可以

Git版本控制软件结合GitHub从入门到精通常用命令学习手册

GIT 学习手册简介 本站为 Git 学习参考手册.目的是为学习与记忆 Git 使用中最重要.最普遍的命令提供快速翻阅. 这些命令以你可能需要的操作类型划分,并且将提供日常使用中需要的一些常用的命令以及参数. 本手册将从入门到精通指导大家. 首先,我们要从如何以 Git 的思维方式管理源代码开始. 如何以 GIT 的方式思考(这里可以不用看懂,接着看下面的内容,看完就全懂了.) 懂得 Git,第一件重要的事情就是要知道它与 Subversion.Perforce 或者任何你用过的版本控制工具都有

PostgreSQL学习手册(五) 函数和操作符

PostgreSQL学习手册(五) 函数和操作符 一.逻辑操作符:    常用的逻辑操作符有:AND.OR和NOT.其语义与其它编程语言中的逻辑操作符完全相同. 二.比较操作符:    下面是PostgreSQL中提供的比较操作符列表: 操作符 描述 < 小于 > 大于 <= 小于或等于 >= 大于或等于 = 等于 != 不等于 比较操作符可以用于所有可以比较的数据类型.所有比较操作符都是双目操作符,且返回boolean类型.除了比较操作符以外,我们还可以使用BETWEEN语句,如

Java开发手册 Java学习手册教程(MtJava开发手册)

本文档的版权归MtJava文档小组所有,本文档及其描述的内容受有关法律的版权保护,对本文档内容的任何形式的非法复制,泄露或散布,将导致相应的法律责任. MtJava只是一个学习Java的简化版本,适合有一些Java基础的人学习参考,主要是为了辅助MtAndroid的学习者学习的文档 Java开发手册 Java学习手册教程(MtJava开发手册),布布扣,bubuko.com

使用.net(C#)发送邮件学习手册(带成功案例)

使用.net(C#)发送邮件学习手册(带成功案例) 1.了解发送邮件的三种方式 2.实例介绍使用client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.PickupDirectoryFromIis 3.如何设定本机IIS的SMTP服务器 1.了解发送邮件的三种方式 第一:client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network; //通過遠程SMTP服務器傳送

SQL语句学习手册实例版

SQL语句学习手册实例版 表操作 例1  对于表的教学管理数据库中的表 STUDENTS ,可以定义如下: CREATE  TABLE  STUDENTS (SNO      NUMERIC (6, 0) NOT NULL SNAME    CHAR (8) NOT NULL AGE      NUMERIC(3,0) SEX      CHAR(2) BPLACE  CHAR(20) PRIMARY KEY(SNO)) 例2  对于表的教学管理数据库中的表 ENROLLS ,可以定义如下: C

《经济学原理:宏观经济学分册》学习手册 第7版_P248_2015.09_完整版PDF电子书下载 带索引书签目录高清版

<经济学原理:宏观经济学分册>学习手册  第7版_P248_2015.09_完整版PDF电子书下载 带索引书签目录高清版_13864820 下载地址 http://pan.baidu.com/s/1bJgNbK [作 者]付达院主编 [形态项] 248 [出版项] 北京:北京大学出版社 , 2015.09 [ISBN号]978-7-301-26243-6 [中图法分类号]F015 [原书定价]35.00 [主题词]宏观经济学-高等学校-自学参考资料 [参考文献格式] 付达院主编. <经济

《经济学原理:微观经济学分册》学习手册 第7版_P359_2015.09_完整版PDF电子书下载 带索引书签目录高清版

<经济学原理:微观经济学分册>学习手册  第7版_P359_2015.09_完整版PDF电子书下载 带索引书签目录高清版_13869902 下载地址    http://pan.baidu.com/s/1jHVHEM6 [作 者]付达院主编 [形态项] 359 [出版项] 北京:北京大学出版社 , 2015.09 [ISBN号]978-7-301-26244-3 [中图法分类号]F016 [原书定价]42.00 [主题词]微观经济学-高等学校-自学参考资料 [参考文献格式] 付达院主编. &l