代码地址如下:
http://www.demodashi.com/demo/12321.html

原文地址:http://blog.csdn.net/vnanyesheshou/article/details/79047650

AIDL(Android Interface Definition Language)——进程间通信的一种机制。它允许您定义客户端和服务端通过使用进程间通信(IPC)进行通信的编程接口。在Android上,一个进程无法正常访问另一个进程的内存。所以说,他们需要将他们的对象分解成操作系统能够理解的原语,并且把这些对象放在你的边界上。编写这些代码非常繁琐,所以Android使用AIDL来处理它。

1 使用AIDL的必要条件

  • 只有当你需要来自不同应用的客户端通过IPC(进程间通信)通信来访问你的服务时,并且想在服务里处理多线程的业务,这时就需要使用AIDL。
  • 如果你不需要同时对几个应用进程IPC操作,你最好通过实现Binder接口来创建你的接口。
  • 如果你仍需要执行IPC操作,但不需要处理多线程,使用Messenger来实现接口即可。

2 AIDL的使用

使用Java编程语言语法在.aidl文件中定义您的AIDL接口,然后将其保存在承载服务的应用程序和任何其他绑定到该服务的应用程序的源代码(在src /目录中)。

当应用程序构建包含.aidl文件时,Android SDK工具将生成一个基于.aidl文件的IBinder接口,并将其保存在项目的gen /目录中。 该服务必须适当地实现IBinder接口。 然后,客户端应用程序可以绑定到服务并从IBinder调用方法来执行IPC。

使用AIDL 创建绑定的服务,具体步骤:

  1. 创建.aidl文件

    这个文件用方法签名来定义编程接口。
  2. 实现接口

    Android SDK工具根据你的.aidl文件以Java编程语言生成一个接口 。这个接口有一个名为Stub的内部抽象类,它继承了Binder并实现了AIDL接口中的方法。你必须继承这个 Stub类并实现这些方法。
  3. 将接口公开给客户端

    实现一个服务并重写onBind() 来返回你的Stub类的实现。

2.1 创建.aidl文件

AIDL使用简单的语法,可以用一个或多个方法(可以接收参数和返回值)来声明接口。参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。

必须使用Java编程语言构建.aidl文件。 每个.aidl文件都必须定义一个接口,并且只需要接口声明和方法签名。

默认情况下,AIDL支持以下数据类型:

  • Java编程语言中的所有基本类型(如int,long,char,boolean等)
  • String
  • CharSequence
  • List

    List中的所有元素都必须是支持的数据类型之一,或者是您声明的其他AIDL生成的接口或可接受的元素之一。 列表可以选择性地用作“通用”类(例如List )。 对方收到的实际具体类始终是一个ArrayList,尽管生成的方法是使用List接口。
  • Map

    Map中的所有元素都必须是此列表中受支持的数据类型之一,或者是您声明的其他AIDL生成的接口或可接受元素之一。 通用映射(如Map <String,Integer>形式的映射)不被支持。对方接收的实际具体类总是一个HashMap,尽管该方法是使用Map接口生成的。

对于上面没有列出的每种附加类型,即使它们在与接口相同的包中定义,也必须包含一条import语句。

在定义服务接口时,注意:

  • 方法可以采用零个或多个参数,并返回一个值或void。
  • 所有非原始参数都需要一个指向数据的方向标签。in,out或者inout(见下面的例子)。基本数据默认是in的,不能以其他方式。

    警告:您应该将方向限制在真正需要的地方,因为编组参数非常昂贵。
  • 包含在.aidl文件中的所有代码注释都包含在生成的IBinder接口中(导入和包装语句之前的注释除外)。
  • 只支持方法; 您不能在AIDL中公开静态字段。

如下是一个.aidl 例子。IRemoteService.aidl

  1. package com.zpengyong.aidl;
  2. interface IRemoteService {
  3. void sendMessage(in String str);
  4. boolean play();
  5. boolean pause();
  6. boolean stop();
  7. }

只需将.aidl文件保存在项目src/目录中,SDK工具会在项目gen/目录中生成IBinder接口文件。生成的文件名与.aidl文件名相匹配,但带有.java扩展名(例如IRemoteService.aidl结果IRemoteService.java)。

2.2 实现接口

IRemoteService.java接口文件包含一个名为Stub的类 ,它继承了Binder ,实现了IRemoteService接口,并声明.aidl文件中的所有方法。

Stub还定义了一些辅助方法,最值得注意的是asInterface(),它接受一个IBinder(通常是传递给客户端的onServiceConnected()回调方法中的参数),并返回stub接口的一个实例。

要实现从.aidl生成的接口,请继承生成的Binder接口(例如IRemoteService.Stub),并实现从.aidl文件继承的方法。

下面是示例:

  1. private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
  2. public void sendMessage(String str){
  3. Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
  4. Message msg = new Message();
  5. msg.what = MSG_RECEIVE_MESSAGE;
  6. msg.obj = str;
  7. mHandler.sendMessage(msg);
  8. }
  9. public boolean play(){
  10. mService.play();
  11. return true;
  12. }
  13. public boolean pause(){
  14. mService.pause();
  15. return true;
  16. }
  17. public boolean stop(){
  18. mService.stop();
  19. return true;
  20. }
  21. };

现在mBinder是Stub类的一个实例(一个Binder),它定义了服务的RPC接口。 在下一步中,这个实例被暴露给客户,以便他们可以与服务交互。

在实现AIDL接口时,您应该注意一些规则:

  • 传入的调用并不保证在主线程中执行,所以需要从头开始考虑多线程,并将服务正确地构建为线程安全的。
  • 默认情况下,RPC调用是同步的。如果您知道该服务需要超过几毫秒才能完成请求,则不应该从活动的主线程调用该服务,因为它可能会挂起应用程序(Android可能会显示“应用程序不响应”对话框,应该通常从客户端的一个单独的线程调用它们。
  • 抛出的任何异常都将被发回给调用者。

2.3 将接口公开给客户端

为了暴露你的服务的接口,扩展Service并实现onBind()返回实现生成的Stub的类的实例。 这里是一个示例服务,将IRemoteService示例接口公开给客户端。

  1. public class AIDLService extends Service {
  2. @Override
  3. public void onCreate() {
  4. super.onCreate();
  5. }
  6. @Override
  7. public IBinder onBind(Intent intent) {
  8. // Return the interface
  9. return mBinder;
  10. }
  11. private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
  12. public void sendMessage(String str){
  13. Log.i(TAG,"message str:"+str +",thread:"+Thread.currentThread());
  14. Message msg = new Message();
  15. msg.what = MSG_RECEIVE_MESSAGE;
  16. msg.obj = str;
  17. mHandler.sendMessage(msg);
  18. }
  19. public boolean play(){
  20. mService.play();
  21. return true;
  22. }
  23. public boolean pause(){
  24. mService.pause();
  25. return true;
  26. }
  27. public boolean stop(){
  28. mService.stop();
  29. return true;
  30. }
  31. };
  32. }

现在,当一个客户端(比如一个activity)调用bindService()连接到这个服务时,客户端的onServiceConnected()回调会收到mBinder(服务onBind() 方法返回的 实例)。

客户端还必须能够访问接口类,所以如果客户端和服务在不同的应用程序中,那么客户端的应用程序必须在其src/目录中拥有该.aidl文件的副本(这会生成android.os.Binder 接口 - 为客户端提供对AIDL方法的访问)。

当客户端收到onServiceConnected()回调,得到IBinder,它必须调用 IRemoteService.Stub.asInterface(service)转换成IRemoteService类型。例如:

  1. private IRemoteService mIRemoteService;
  2. private ServiceConnection mConnection = new ServiceConnection() {
  3. // 当与服务端连接成功时,回调该方法。
  4. @Override
  5. public void onServiceConnected(ComponentName name, IBinder service) {
  6. //转换
  7. mIRemoteService = IRemoteService.Stub.asInterface(service);
  8. }
  9. // 当与服务端连接异常断开时,回调该方法。
  10. @Override
  11. public void onServiceDisconnected(ComponentName name) {
  12. mIRemoteService = null;
  13. }
  14. };

3 调用IPC方法

以下是调用类必须用来调用AIDL定义的远程接口的步骤:

  1. 将.aidl文件包含在项目src /目录中。
  2. 声明一个IBinder接口的实例(基于AIDL生成)。
  3. 实现ServiceConnection.
  4. 调用Context.bindService(),传入你的ServiceConnection实现。
  5. 在onServiceConnected()实现中,将收到一个IBinder实例。 调用YourInterfaceName.Stub.asInterface((IBinder)service)将返回的参数强制转换为YourInterfaceName类型。
  6. 调用你在接口上定义的方法。 您应该始终捕获连接断开时引发的DeadObjectException异常; 这将是远程方法抛出的唯一异常。
  7. 要断开连接,调用Context.unbindService()。

如下:

  1. package com.zpengyong.aidlclient;
  2. import com.zpengyong.aidl.IRemoteService;
  3. import com.zpengyong.aidl.IRemoteServiceCallback;
  4. import android.app.Activity;
  5. import android.content.ComponentName;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.content.ServiceConnection;
  9. import android.os.Bundle;
  10. import android.os.IBinder;
  11. import android.os.RemoteException;
  12. import android.text.Editable;
  13. import android.util.Log;
  14. import android.view.View;
  15. import android.view.View.OnClickListener;
  16. import android.widget.Button;
  17. import android.widget.EditText;
  18. import android.widget.TextView;
  19. public class MainActivity extends Activity implements OnClickListener {
  20. private final static String TAG = "MainActivity";
  21. private TextView mStateText, mMusicState;
  22. private Button mBtnHello, mBtnBind, mBtnStart, mBtnPause, mBtnStop;
  23. private EditText mTextMessage;
  24. private IRemoteService mIRemoteService;
  25. private final int STATE_DISCONNECTED = 1;
  26. private final int STATE_CONNECTING = 2;
  27. private final int STATE_CONNECTED = 3;
  28. private final int STATE_DISCONNECTING = 4;
  29. //与服务端的连接状态
  30. private int mBindState = STATE_DISCONNECTED;
  31. private ServiceConnection mConnection = new ServiceConnection() {
  32. // 当与服务端连接成功时,回调该方法。
  33. @Override
  34. public void onServiceConnected(ComponentName name, IBinder service) {
  35. Log.i(TAG, "onServiceConnected");
  36. mIRemoteService = IRemoteService.Stub.asInterface(service);
  37. mStateText.setText("connected");
  38. mBindState = STATE_CONNECTED;
  39. mBtnBind.setText("解绑");
  40. try {
  41. mIRemoteService.registerCallback(mIRemoteServiceCallback);
  42. } catch (RemoteException e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. // 当与服务端连接异常断开时,回调该方法。
  47. @Override
  48. public void onServiceDisconnected(ComponentName name) {
  49. Log.i(TAG, "onServiceDisconnected");
  50. mIRemoteService = null;
  51. mStateText.setText("disconnected");
  52. mBindState = STATE_DISCONNECTED;
  53. mBtnBind.setText("绑定");
  54. }
  55. };
  56. @Override
  57. protected void onCreate(Bundle savedInstanceState) {
  58. super.onCreate(savedInstanceState);
  59. setContentView(R.layout.activity_main);
  60. mStateText = (TextView) findViewById(R.id.connectState);
  61. mBtnHello = (Button) findViewById(R.id.sendMessage);
  62. mBtnBind = (Button)findViewById(R.id.bind);
  63. mBtnStart = (Button)findViewById(R.id.start_play);
  64. mBtnPause = (Button)findViewById(R.id.pause);
  65. mBtnStop = (Button)findViewById(R.id.stop_play);
  66. mBtnHello.setOnClickListener(this);
  67. mBtnStart.setOnClickListener(this);
  68. mBtnPause.setOnClickListener(this);
  69. mBtnStop.setOnClickListener(this);
  70. mBtnBind.setOnClickListener(this);
  71. mTextMessage = (EditText) findViewById(R.id.message);
  72. mMusicState = (TextView)findViewById(R.id.musicState);
  73. }
  74. private void bind() {
  75. mBindState = STATE_CONNECTING;
  76. Intent intent = new Intent();
  77. // Android 5.0 以上显示绑定服务
  78. intent.setComponent(new ComponentName("com.zpengyong.aidl", "com.zpengyong.aidl.AIDLService"));
  79. // 绑定服务
  80. this.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  81. mStateText.setText("connecting");
  82. }
  83. private void unbind() {
  84. mBindState = STATE_DISCONNECTING;
  85. try {
  86. mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
  87. } catch (RemoteException e) {
  88. e.printStackTrace();
  89. }
  90. mStateText.setText("disconnecting");
  91. //解除与Service的连接
  92. unbindService(mConnection);
  93. mBindState = STATE_DISCONNECTED;
  94. mStateText.setText("disconnected");
  95. mBtnBind.setText("绑定");
  96. mIRemoteService = null;
  97. }
  98. @Override
  99. protected void onDestroy() {
  100. super.onDestroy();
  101. if(mBindState != STATE_DISCONNECTED){
  102. unbind();
  103. }
  104. }
  105. @Override
  106. public void onClick(View v) {
  107. switch (v.getId()) {
  108. case R.id.sendMessage:
  109. String str = mTextMessage.getText().toString();
  110. if(str == null ||str.length() == 0)
  111. return;
  112. if(mIRemoteService == null)
  113. return;
  114. try {
  115. mIRemoteService.sendMessage(str);
  116. } catch (RemoteException e1) {
  117. e1.printStackTrace();
  118. }
  119. break;
  120. case R.id.bind:
  121. if(mBindState == STATE_DISCONNECTED){
  122. bind();
  123. }else if(mBindState == STATE_CONNECTED){
  124. unbind();
  125. }
  126. break;
  127. case R.id.start_play:
  128. if(mIRemoteService == null)
  129. return;
  130. try {
  131. boolean ret = mIRemoteService.play();
  132. Log.i(TAG, "play ret="+ret);
  133. } catch (RemoteException e) {
  134. e.printStackTrace();
  135. }
  136. break;
  137. case R.id.pause:
  138. if(mIRemoteService == null)
  139. return;
  140. try {
  141. boolean ret = mIRemoteService.pause();
  142. Log.i(TAG, "pause ret="+ret);
  143. } catch (RemoteException e) {
  144. e.printStackTrace();
  145. };
  146. break;
  147. case R.id.stop_play:
  148. if(mIRemoteService == null)
  149. return;
  150. try {
  151. boolean ret = mIRemoteService.stop();
  152. Log.i(TAG, "stop ret="+ret);
  153. } catch (RemoteException e) {
  154. e.printStackTrace();
  155. }
  156. break;
  157. default:
  158. break;
  159. }
  160. }
  161. }

效果图如下:

4 服务端回调客户端

如上的列子中只有客户端调用服务端的方法,并不能服务端调用客户端。

在之前的IRemoteService.aidl文件中添加接口

  1. package com.zpengyong.aidl;
  2. import com.zpengyong.aidl.IRemoteServiceCallback;
  3. interface IRemoteService {
  4. void registerCallback(in IRemoteServiceCallback cb);
  5. void unregisterCallback(in IRemoteServiceCallback cb);
  6. void sendMessage(in String str);
  7. boolean play();
  8. boolean pause();
  9. boolean stop();
  10. }

IRemoteServiceCallback.aidl中添加服务端调用客户端的接口。

该文件服务度和客户端都需要包含该文件。

  1. package com.zpengyong.aidl;
  2. interface IRemoteServiceCallback {
  3. void stateChange(int value);
  4. }

1 客户端实现回调接口

要实现从IRemoteServiceCallback.aidl生成的接口,请继承生成的Binder接口(IRemoteServiceCallback.Stub),并实现从IRemoteServiceCallback.aidl文件继承的方法。

  1. private IRemoteServiceCallback mIRemoteServiceCallback = new IRemoteServiceCallback.Stub() {
  2. @Override
  3. public void stateChange(int value) throws RemoteException {
  4. Log.i(TAG, "stateChange value="+value);
  5. if(value == 1){
  6. mMusicState.setText("开始播放");
  7. }else if(value == 2){
  8. mMusicState.setText("暂停播放");
  9. }else if(value == 3){
  10. mMusicState.setText("停止播放");
  11. }else if(value == 4){
  12. mMusicState.setText("播放出错");
  13. }else {
  14. mMusicState.setText("unknown");
  15. }
  16. }
  17. };

2 注册回调

客户端bindservice成功后会回调onServiceConnected,客户端可以获取到mIRemoteService,可以调用远端的放,这时可以通过调用远端方法注册回调接口实例。

  1. private ServiceConnection mConnection = new ServiceConnection() {
  2. // 当与服务端连接成功时,回调该方法。
  3. @Override
  4. public void onServiceConnected(ComponentName name, IBinder service) {
  5. Log.i(TAG, "onServiceConnected");
  6. mIRemoteService = IRemoteService.Stub.asInterface(service);
  7. mStateText.setText("connected");
  8. mBindState = STATE_CONNECTED;
  9. mBtnBind.setText("解绑");
  10. try {
  11. //注册回调。
  12. mIRemoteService.registerCallback(mIRemoteServiceCallback);
  13. } catch (RemoteException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }

3 服务端保存回调接口

由于AIDl支持多个客户端绑定,并处理并发请求。所以这里要将回调接口存到列表中,避免后注册的将前面注册的回调接口覆盖。

  1. //aidl支持多个客户端绑定,并且处理并发进程间通信,所以这里要存列表中。
  2. final RemoteCallbackList<IRemoteServiceCallback> mCallbackList
  3. = new RemoteCallbackList<IRemoteServiceCallback>();
  4. private final IRemoteService.Stub mBinder = new IRemoteService.Stub(){
  5. public void registerCallback(IRemoteServiceCallback cb){
  6. if(cb != null)mCallbackList.register(cb);
  7. }
  8. public void unregisterCallback(IRemoteServiceCallback cb){
  9. if(cb != null)mCallbackList.unregister(cb);
  10. }
  11. 。。。
  12. }

4 服务器调用客户端方法

遍历回调list,分别调用其stateChange方法,实现服务端调用客户端,实现双方通信。

  1. private void callstateChange(int value){
  2. //遍历保存的IRemoteServiceCallback,发送状态改变的消息。
  3. int num = mCallbackList.beginBroadcast();
  4. for(int i=0; i<num; i++){
  5. try {
  6. mCallbackList.getBroadcastItem(i).stateChange(value);
  7. } catch (RemoteException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. mCallbackList.finishBroadcast();
  12. }

当服务端调用回调接口的方法后,客户端的接口实现中就会收到响应。

4 取消注册

客户端unbindService前 调用取消注册的方法。

  1. private void unbind() {
  2. mBindState = STATE_DISCONNECTING;
  3. try {
  4. mIRemoteService.unregisterCallback(mIRemoteServiceCallback);
  5. } catch (RemoteException e) {
  6. e.printStackTrace();
  7. }
  8. mStateText.setText("disconnecting");
  9. //解除与Service的连接
  10. unbindService(mConnection);
  11. mBindState = STATE_DISCONNECTED;
  12. mStateText.setText("disconnected");
  13. mBtnBind.setText("绑定");
  14. mIRemoteService = null;
  15. }

5 项目结构截图

客户端项目结构



服务端项目结构

6 运行效果图

客户端接收服务端的回调,效果显示如下:

Android 进程间通信——AIDL

代码地址如下:
http://www.demodashi.com/demo/12321.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

Android 进程间通信——AIDL的更多相关文章

  1. Android进程间通信-AIDL实现原理

    Android进程间通信基于Proxy(代理)与Stub(桩或存根)的设计模式(如图1-1所示).其中,Proxy将特殊性接口转换成通用性接口,Stub将通用性接口转换成特殊性接口,二者之间的数据转换 ...

  2. Android开发之IPC进程间通信-AIDL介绍及实例解析

    一.IPC进程间通信 IPC是进程间通信方法的统称,Linux IPC包括以下方法,Android的进程间通信主要采用是哪些方法呢? 1. 管道(Pipe)及有名管道(named pipe):管道可用 ...

  3. Android 进程间通信

    什么鬼!单例居然失效了,一个地方设置值,另个地方居然取不到,这怎么可能?没道理啊!排查半天,发现这两就不在一个进程里,才恍然大悟-- 什么是进程 按照操作系统中的描述:进程一般指一个执行单元,在 PC ...

  4. Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL

    服务端: 最终项目结构: 这个项目中,我们将用到自定义类CustomData作为服务端与客户端传递的数据. Step 1:创建CustomData类 package com.ldb.android.e ...

  5. Android 使用AIDL调用外部服务

    好处:多个应用程序之间建立共同的服务机制,通过AIDL在不同应用程序之间达到数据的共享和数据相互操作, 本文包括: 1 .创建AIDL 服务端.2 .创建AIDL 客户端. 3.客户端调用服务端提供的 ...

  6. 浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6621566 上一篇文章Android进程间通信 ...

  7. [转]Android进程间通信

    Android进程间通信 一.Linux系统进程间通信有哪些方式? 1.socket: 2.name pipe命名管道: 3.message queue消息队列: 4.singal信号量: 5.sha ...

  8. android基础---->AIDL服务的使用

    AIDL和其他的IDL类似,它允许你定义程序接口,以便客户端与服务器端通过IPC机制交互.在android上面,一个进程一般不能访问另外进程的内存.因此,Android平台将这些跨进程访问的对象分解成 ...

  9. Android之——AIDL深入

    转载请注明出处:http://blog.csdn.net/l1028386804/article/details/47071927 在上一篇博文<Android之--AIDL小结>中,我们 ...

随机推荐

  1. 抓取js动态生成数据

    最近在抓数据,一般的网页数据抓取相对容易一些,今天在抓电视猫的节目单,发现有些数据时抓取不到的,Java端得到的HTML文件里面没有某一段代码,查了很多资料,发现说是js动态生成的数据,无法直接抓取, ...

  2. linux删除大量文件

    1.建立一个空目录 mkdir -p /tmp/rsync_blank 2.确立需要清空的目标目录 /data/web/vip/htdocs/tuan 3.使用rsync同步删除(注意目录后面的“/” ...

  3. Linux调用fork()编程

    本文出自:svitter's blog #include <iostream> #include <cstdio> #include <unistd.h> usin ...

  4. css 文字垂直居中问题

    CSS 文字垂直居中问题 问题:在 div 中文字居中问题: 当使用 line-height:100%%; 时,文字没有居中,如下: html: <div id="header_log ...

  5. Apache Commons 工具集介绍

    Apache Commons包含了很多开源的工具,用于解决平时编程经常会遇到的问题,减少重复劳动.下面是我这几年做开发过程中自己用过的工具类做简单介绍. 组件 功能介绍 BeanUtils 提供了对于 ...

  6. javascript实现htmlEncode与htmlDecode

    原文发布时间为:2011-04-19 -- 来源于本人的百度文章 [由搬家工具导入] htmlencode with javascript function htmlEncode(html) {    ...

  7. 【spring专题】spring简介

    前景概要 对于现在的Java开发基本上可以说成是spring开发,spring全家桶可以说是把整个Java web安排的明明白白的.正因为使用的很多,所以作为一名开发者不应该仅仅是会使用spring, ...

  8. uva 1442:Cave(贪心)

    题意:一个洞穴长n,告诉你每个位置的地面高度和顶部高度,让你往里灌水,要求水不能碰到天花板(但可以无限接近).求最多的水量.(洞穴两边视为封闭) 思路:如果知道一个位置向左看最高可以多高,向右看最高可 ...

  9. sublime text3中成功使用bootstrap3

    在视图这里卡了挺久的,一直是自己在研究.其实自己有一个坏毛病,遇到问题,在网上搜集下找不到便寻求帮助(大多数是求助无效果,因为自己也没搞懂), 这时候自己就会懈怠一会,然后隔一两天心血起伏后便又继续干 ...

  10. JavaScripts广告轮播图以及定时弹出和定时隐藏广告

    轮播图: 函数绑定在body标签内 采用3张图,1.jpg   2.jpg  3.jpg  利用定时任务执行设置图片属性 src  利用for循环可以完成3秒一次 一替换. 定时弹出广告: 由于bod ...