精华内容
下载资源
问答
  • 上一篇文章:Android中程序与Service交互方式——综述 简述了Service的一些基础知识以及Service和Thread的简单区别,本文将着重讲解与Service交互的五种基本方式:广播交互、共享文件交互、Mssenger(信使)交互、...
    上一篇文章:Android中程序与Service交互的方式——综述 简述了Service的一些基础知识以及Service和Thread的简单区别,本文将着重讲解与Service交互的五种基本方式:广播交互、共享文件交互、Mssenger(信使)交互、自定义接口交互、AIDL交互。
    

           1. 广播交互

           提到Activity与Service的交互,可能狠多人首先想到的就是BroadCast——广播。在Android中,广播是系统提供的一种很好的交互方式。比如:在电池电量过低,开机完成等情况下,系统都会发出相应的系统广播,我们的应用程序只需要注册相应的广播接收器,就可以接收到这些系统的广播。同时,我们也可以定义自己的广播,这样在不同的Activity、Service以及应用程序之间,就可以通过广播来实现交互。我们通过模拟应用程序后台下载的情况来分析Service与Activity的交互方式。实现效果如图1.1:

    图1.1

            当我们点击StartService按钮之后,界面上的进度条将会每隔一秒加1。因为是模拟下载,因此下载动作我们在Service中通过一个Timer定时器来实现,在Timer中对一个整型数据i进行自加(i++),然后Client端获取Server端的i值并显示在界面上,从而达到模拟的目的。

            1.1. 实现原理

            Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。

            1.2. 实现步骤

            1.2.1 在Client端中通过startService()啟动Service。

    [java] view plaincopy
    1. if(v == startBtn){  
    2.     Log.i(TAG, "start button clicked...pid: "+Process.myPid());  
    3.     mIntent.setClass(BroadCastService.this, DownLoadService.class);  
    4.     startService(mIntent);  
    5. }  
              这里的mIntent = new Intent();Process.myPid()方法可以获取当前进程的ID号。
           1.2.2 DownLoadService接到启动的命令之后,执行onCreate()方法,并在其中开启timer计数模拟下载。

    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "DownLoadService.onCreate()...pid: "+Process.myPid());  
    5.     intent = new Intent("com.seven.broadcast");  
    6.     mTimer = new Timer();  
    7.     mTimer.schedule(new MyTimerTask(), 0 , TIME * 1000);  
    8. }  
              这里的intent是Server端向Client端传送数据用的,使用的action是”com.seven.broadcast”,Client端只有註册了相应action才能够接收到Server端的广播,并解析其中的内容。Process.myPid()是获取当前进程的ID。
            1.2.3 在Server端的timer计数其中发送广播,告知Client端目前下载进度。

    [java] view plaincopy
    1. class MyTimerTask extends TimerTask{  
    2.     @Override  
    3.     public void run() {  
    4.         if(i==100){  
    5.             i=0;  
    6.         }  
    7.         intent.putExtra("CurrentLoading", i);  
    8.         sendBroadcast(intent);  
    9.         i++;  
    10.         Log.e(TAG, "i= "+i);  
    11.     }  
    12. }  
              通过intent.putExtra(key,value);设置intent的值,然后通过sendBroadcast(intent)2方法,将广播发送出去。

           1.2.4 在Client端通过匿名内部类的方式实例化BroadcastReceiver并覆写其中的onReceive()方法。

    [java] view plaincopy
    1. BroadcastReceiver receiver = new BroadcastReceiver() {  
    2.     @Override  
    3.     public void onReceive(Context context, Intent intent) {  
    4.         if(MYACTION.equals(intent.getAction())){  
    5.             Log.i(TAG, "get the broadcast from DownLoadService...");  
    6.             curLoad = intent.getIntExtra("CurrentLoading", ERROR);  
    7.             mHandler.sendMessage(mHandler.obtainMessage());  
    8.         }  
    9.     }  
    10. };  
              在onReceive()方法中,判断是否为Server端发送的广播,如果是则对广播中携带的intent数据进行解包处理。这裡也可以单独写一个类继承自BroadcastReceiver,在其中覆写onReceive()方法,在Client端中实例化其对象,同样可以达到相应的效果,这样做可以为后面实现静态注册广播。
            1.2.5 更新主介面下载进度。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         Log.i(TAG, "current loading: "+curLoad);  
    6.         if(curLoad<0||curLoad>100){  
    7.             Log.e(TAG, "ERROR: "+curLoad);  
    8.             return;  
    9.         }  
    10.         mProgressBar.setProgress(curLoad);  
    11.         mTextView.setText(curLoad+"%");  
    12.     }  
    13. };  
                 这里对获取到的进度进行了一次判断,如果获取到的值没有异常,那么将会显示到界面,并更新进度条的进度,如果异常则返回。
             1.2.6 一定要对Broadcast进行注册和取消注册。只有注册之后相应的broadcast之后才能接收到广播注册方法有两种。
             动态注册/取消注册:

    [java] view plaincopy
    1. @Override  
    2. protected void onResume() {  
    3.     super.onResume();  
    4.     Log.i(TAG, "register the broadcast receiver...");  
    5.     IntentFilter filter = new IntentFilter();  
    6.     filter.addAction(MYACTION);  
    7.     registerReceiver(receiver, filter);  
    8. }  
    9. @Override  
    10. protected void onDestroy() {  
    11.     super.onDestroy();  
    12.     Log.i(TAG, "unregister the broadcast receiver...");  
    13.     unregisterReceiver(receiver);  
    14. }  
               动态註册可以随时註册随时取消。

            静态註册:

    [html] view plaincopy
    1. <receiver android:name="MyBroadcastReceiver">  
    2.     <intent-filter>  
    3.         <action android:name="com.seven.broadcast" />  
    4.     </intent-filter>  
    5. </receiver>  
               注:这里的MyBroadcastReceiver是一个继承自BroadcastReceiver的类。静态注册只要注册了一次那么只要该程序没有被卸载那么该广播将一直有效。

            最后贴出整个AndroidManifest.xml文件

    [html] view plaincopy
    1. <application android:icon="@drawable/icon" android:label="@string/app_name">  
    2.     <activity android:name=".BroadCastService"  
    3.               android:label="@string/app_name">  
    4.         <intent-filter>  
    5.             <action android:name="android.intent.action.MAIN" />  
    6.             <category android:name="android.intent.category.LAUNCHER" />  
    7.         </intent-filter>  
    8.     </activity>  
    9.     <service android:name="DownLoadService" android:process=":remote"/>  
    10. </application>  
              这里的android:process =”:remote”可以使该Service运行在单独进程中,从而可以模拟跨进程通信。

           1.3 小结

           通过广播的方式实现Activity与Service的交互操作简单且容易实现,可以胜任简单级的应用。但缺点也十分明显,发送广播受到系统制约。系统会优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟。同时在广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应。

            2. 共享文件交互

            这里提到的共享文件指的是Activity和Service使用同一个文件来达到传递数据的目的。我们使用SharedPreferences来实现共享,当然也可以使用其它IO方法实现,通过这种方式实现交互时需要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。实现效果如图2.1:

    图2.1

             2.1 实现原理

             Server端将当前下载进度写入共享文件中,Client端通过读取共享文件中的下载进度,并更新到主界面上。

             2.2 实现步骤

             2.2.1 在Client端通过startService()啟动Service。

    [java] view plaincopy
    1. if(startSerBtn==v){  
    2.     Log.i(TAG, "Start Button Clicked.");  
    3.     if(intent!=null){  
    4.     startService(intent);  
    5.     timer.schedule(new MyTimerTask(), 0, TIME * 1000);  
    6.     }  
    7. }  
              这里的intent = new Intent()2只是为了启动Server端。
            2.2.2 Server端收到启动intent之后执行onCreate()方法,并开启timer,模拟下载,以及初始化SharedPreferences对象preferences。

    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "DownLoadService.onCreate()...");  
    5.     preferences = getSharedPreferences("CurrentLoading_SharedPs"0);  
    6.     timer = new Timer();  
    7.     timer.schedule(new MyTimerTask(), 0, TIME*1000);  
    8. }  
              通过preferences=getSharedPreferences(String,MODE)2可以在/data/data/com.seven.servicetestdemo/shared_prefs文件夹下建立相应的xml文件。

           2.2.3 开始计数并将下载进度写入shared_prefs文件夹下的xml文件中,内容以键值对的方式保存。

    [java] view plaincopy
    1. class MyTimerTask extends TimerTask{  
    2.     @Override  
    3.     public void run() {  
    4.         setCurrentLoading();  
    5.         if(100==i){  
    6.             i=0;  
    7.         }  
    8.         i++;  
    9.     }         
    10. }     
    11. private void setCurrentLoading() {  
    12.     preferences.edit().putInt("CurrentLoading", i).commit();  
    13. }  
               对於SharedPreferences的使用需要注意一下几点:

            首先,使用sharedPreferences前需要获取文件引用。

            preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);

            其次,使用sharedpreferences写数据方式。

            preferences.edit().putInt("CurrentLoading", i).commit();

            最后,读取数据的方式。

            int couLoad = preferences.getInt("CurrentLoading", 0);

            2.2.4 Client端通过读取/data/data/com.seven.servicetestdemo/shared_prefs文件夹下的xml文件,并取得里面的键值对,从而获取到当前的下载进度,并更新到主界面上。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         int couLoad = preferences.getInt("CurrentLoading"0);  
    6.         mProgressBar.setProgress(couLoad);  
    7.         currentTv.setText(couLoad+"%");  
    8.     }  
    9.  };  

              2.3 小结

            因為方法简单,因此就不贴出AndroidManifest.xml文件了。对於这种方式实现Activity与Service的交互,可以说很方便,就像使用管道,一个往裡写,一个往外读。但这种方式也有缺陷,写入数据较为复杂以及数据量较大时,就有可能导致写入与读数据出不一致的错误。同时因为经过了一个中转站,这种操作将更耗时。

            3. Messenger交互(信使交互)

            Messenger翻译过来指的是信使,它引用了一个Handler对象,别人能够向它发送消息(使用mMessenger.send(Message msg)方法)。该类允许跨进程间基于Message通信,在服务端使用Handler创建一个 Messenger,客户端只要获得这个服务端的Messenger对象就可以与服务端通信了。也就是说我们可以把Messenger当做Client端与Server端的传话筒,这样就可以沟通交流了。实现效果如图3.1:

    图3.1

            3.1 实现原理

            在Server端与Client端之间通过一个Messenger对象来传递消息,该对象类似于信息中转站,所有信息通过该对象携带。

            3.2 Messenger的一般用法

            (1). 在Server端创建信使对象。

                   mMessenger = new Messenger(mHandler)

            (2). Client端使用bindService()绑定Server端。

            (3). Server端的onBind()方法返回一个binder对象。

                   return mMessenger.getBinder();

            (4). Client端使用返回的binder对象得到Server端信使。

    [java] view plaincopy
    1. public void onServiceConnected(ComponentName name, IBinder service) {    
    2.               rMessenger = new Messenger(service);        
    3.              ......  
    4.  }  
              这里虽然是new了一个Messenger,但我们查看它的实现

    [java] view plaincopy
    1. public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);  }   
               发现它的mTarget是通过AIDL得到的,实际上就是远程创建的那个。

            (5). Client端可以使用这个Server端的信使对象向Server端发送消息。
                   rMessenger.send(msg);

            这样Server端的Handler对象就能收到消息了,然后可以在其handlerMessage(Message msg)方法中进行处理。经过这5个步骤之后只有Client端向Server端发送消息,这样的消息传递是单向的,那么如何实现消息的双向传递呢?

            首先需要在第5步做修改,在send(msg)前通过msm.replyTo = mMessenger将Client端自己的信使设置到消息中,这样Server端接收到消息时同时也得到了Client端的信使对象,然后Server端也可以通过使用得到的Client端的信使对象来项Client端发送消息 cMessenger = msg.replyTo2  cMessenger.send(message);

           这样即完成了从Server端向Client端发送消息的功能,这样Client端可以在自己的Handler对象的handlerMessage()方法中接收服务端发送来的message进行处理。

            3.3 实现步骤

            3.3.1 创建并初始化Server端的信使对象。

    [java] view plaincopy
    1. private Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         switch (msg.what) {  
    6.         case TEST:  
    7.             Log.e(TAG, "Get Message from MainActivity.");  
    8.             cMessenger = msg.replyTo;  
    9.             mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);  
    10.             break;  
    11.             default:  
    12.                 break;  
    13.             }  
    14.         }         
    15. };  
    16. //It's the messenger of server  
    17. private Messenger mMessenger = new Messenger(mHandler);  

              3.3.2 在Client端使用bindService()方法绑定Server端。

    [java] view plaincopy
    1. private void doBindService(){  
    2.         Log.i(TAG, "doBindService()...");  
    3.     mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);//if bind success return true  
    4.         Log.e(TAG, "Is bind: "+mIsBind);  
    5. }  

              3.3.3 在Server端的onBind()方法中返回一个binder对象。

    [java] view plaincopy
    1. @Override  
    2. public IBinder onBind(Intent intent) {  
    3.     Log.i(TAG, "MessengerService.onBind()...");  
    4.     return mMessenger.getBinder();  
    5. }  
              这裡的mMessenger就是Server端的信使对象。

            3.3.4 Client端使用ServiceConnected()方法来获取Server端的信使对象。

    [java] view plaincopy
    1. private ServiceConnection serConn = new ServiceConnection() {     
    2.     @Override  
    3.     public void onServiceDisconnected(ComponentName name) {  
    4.         Log.i(TAG, "onServiceDisconnected()...");  
    5.         rMessenger = null;  
    6.     }         
    7.     @Override  
    8.     public void onServiceConnected(ComponentName name, IBinder service) {  
    9.         Log.i(TAG, "onServiceConnected()...");  
    10.     rMessenger = new Messenger(service);//get the object of remote service  
    11.     mMessenger = new Messenger(mHandler);//initial the object of local service  
    12.     sendMessage();  
    13.     }  
    14. };  
               获取Server端的信使对象的同时,也初始化Client端的自己的信使对象,并且通过sendMessage()方法发送消息给Server端,表示可以开始下载了。

            3.3.5 Client端使用获取到的rMessenger来发送消息给Server端,同时将Client端的信使封装到消息中,一并发送给Server端。

    [java] view plaincopy
    1. private void sendMessage() {  
    2.     Message msg = Message.obtain(null, MessengerService.TEST);//MessengerService.TEST=0  
    3.     msg.replyTo = mMessenger;  
    4.     try {  
    5.         rMessenger.send(msg);  
    6.     } catch (RemoteException e) {  
    7.         e.printStackTrace();  
    8.     }  
    9. }  
               这里的MessengerService.TEST為Server端里的一个静态常量。Msg.replyTo=mMessenger;表示发送给Server端的信息里携带Client端的信使。

            3.3.6 Server端获取Client端发送的消息并得到Client端的信使对象。

    [java] view plaincopy
    1. private Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         switch (msg.what) {  
    6.         case TEST:  
    7.             Log.e(TAG, "Get Message from MainActivity.");  
    8.             cMessenger = msg.replyTo;//get the messenger of client  
    9.             mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);  
    10.             break;  
    11.         default:  
    12.             break;  
    13.         }  
    14.     }  
    15. };  
               在接收到Client端的信息之后,Server端开啟timer模拟下载,并接收Client端的信使对象。

            3.3.7 Server端向Client端发送数据。

    [java] view plaincopy
    1. class MyTimerTask extends TimerTask {  
    2.     @Override  
    3.     public void run() {  
    4.         if (i == 100) {  
    5.             i = 0;  
    6.         }  
    7.         try {  
    8.             //send the message to the client  
    9.         Message message = Message.obtain(null, MessengerService.TEST,i, 0);  
    10.             cMessenger.send(message);  
    11.         } catch (RemoteException e) {  
    12.                 e.printStackTrace();  
    13.         }  
    14.             i++;  
    15.     }  
    16. }  
              直接使用接收到的Client端的信使对象来发送当前下载进度给Client端。

            3.3.8 Client端接收来自Server端的数据。


    [java] view plaincopy
    1. private Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         switch (msg.what) {  
    6.         case MessengerService.TEST:  
    7.             Log.e(TAG, "Get Message From MessengerService. i= "+msg.arg1);  
    8.             int curLoad = msg.arg1;  
    9.             mTextView.setText(curLoad+"%");  
    10.             mProgressBar.setProgress(curLoad);  
    11.             break;  
    12.         default:  
    13.             break;  
    14.         }  
    15.     }  
    16. };  
               Client端的接收和Server端的接收狠类似。接收到Server端传过来的数据之后进行介面更新,以及下载进度更新。

            以下是AndroidManifest.xml文件:

    [html] view plaincopy
    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    3.       package="com.seven.messengerservicedemo"  
    4.       android:versionCode="1"  
    5.       android:versionName="1.0">  
    6.     <uses-sdk android:minSdkVersion="10" />  
    7.     <application android:icon="@drawable/icon" android:label="@string/app_name">  
    8.         <activity android:name=".MainActivity"  
    9.                   android:label="@string/app_name">  
    10.             <intent-filter>  
    11.                 <action android:name="android.intent.action.MAIN" />  
    12.                 <category android:name="android.intent.category.LAUNCHER" />  
    13.             </intent-filter>  
    14.         </activity>  
    15.     <service android:name="MessengerService">  
    16.         <intent-filter>  
    17.     <action ndroid:name="com.seven.messagerservice.MessengerService" />  
    18.         </intent-filter>  
    19.     </service>  
    20. </application>  
    21. </manifest>  
               这里在Service的註册中加入了过滤动作,只有相匹配的action才能启动相应的Service。

            3.4 小结

            通过Messenger来实现Activity和Service的交互,稍微深入一点我们就可以知道,其实Messenger也是通过AIDL来实现的。对於前两种实现方式,Messenger方式总体上来讲也是比较容易理解的,这就和平时使用Handler和Thread通信一个道理。

            4. 自定义接口交互

            何谓自定义接口呢,其实就是我们自己通过接口的实现来达到Activity与Service交互的目的,我们通过在Activity和Service之间架设一座桥樑,从而达到数据交互的目的,而这种实现方式和AIDL非常类似(后文会说到)。实现效果如图4.1:

    图4.1

            4.1 实现原理

            自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server端用一个类继承自Binder并实现该接口,覆写了其中获取当前下载进度的方法。Client端通过ServiceConnection获取到该类的对象,从而能够使用该获取当前下载进度的方法,最终实现实时交互。

            4.2 实现步骤

            4.2.1 新建一个Interface,并在其中创建一个用于获取当前下载进度的的空方法getCurrentLoad()。

    [java] view plaincopy
    1. package com.seven.servicetestdemo;  
    2.   
    3. public interface ICountService {  
    4.     public int getCurrentLoad();  
    5. }  

            4.2.2 新建Server端DownService实现ICountService并在其中通过一个内部类ServiceBinder继承自Binder并实现ICoutService接口。

    [java] view plaincopy
    1. public class DownLoadService extends Service implements ICountService{  
    2. private ServiceBinder serviceBinder = new ServiceBinder();    
    3. public class ServiceBinder extends Binder implements ICountService{  
    4.     @Override  
    5.     public int getCurrentLoad() {  
    6.         Log.i(TAG, "ServiceBinder getCurrentLoad()... i=:"+i);  
    7.         return i;  
    8.     }     
    9. }  
    10. @Override  
    11. public int getCurrentLoad() {  
    12.     return 0;  
    13. }  
    14. }  
              在Server端中,实现获取下载进度的空方法getCurrentLoad();这是Eclipse自动生成的,重点不在这裡。我们需要在ServiceBinder类中覆写getCurrentLoad()方法,这裡我们返回当前的下载进度i。

           4.2.3 Client端使用bindService()绑定Server端。

    [java] view plaincopy
    1. if (startSerBtn == v) {  
    2.     Log.i(TAG, "Start Button Clicked.");  
    3.     bindService(intent, serConn, BIND_AUTO_CREATE);  
    4.     timer.schedule(new MyTimerTask(), 1000, TIME * 1000);//这里一定要延迟一下再开始获取数据,不然会报空指针异常  
    5. }  
               在Client端绑定Server端的同时,延迟1s开始获取下载进度。其中的intent = new Intent(“com.seven.test”)2com.seven.test该字符串要与在AndroidManifest.xml中申明的一致。

           4.2.4 Server端返回binder对象。

    [java] view plaincopy
    1. @Override  
    2. public IBinder onBind(Intent intent) {  
    3.     Log.i(TAG, "DownLoadService.onBind()...");  
    4.     return serviceBinder;  
    5. }  
               这里的serviceBinder因为继承了Binder因此也是Binder对象。

            4.2.5 Client端通过ServiceConnection来获取Server端的binder对象。

    [java] view plaincopy
    1. private ServiceConnection serConn = new ServiceConnection() {  
    2. @Override  
    3.     public void onServiceDisconnected(ComponentName name) {  
    4.         iCountService = null;  
    5.     }         
    6.     @Override  
    7.     public void onServiceConnected(ComponentName name, IBinder service) {  
    8.         Log.i(TAG, "onServiceConnected()...");  
    9.         iCountService = (ICountService)service;  
    10.     }  
    11. };  
              获取的过程是在bindService()过程中完成的,这里的iCountService是接口ICountService的对象,在这里得到实例化。

            4.2.6 在绑定完成之后,Server端会开启下载,在实际情况中Server端会开启独立线程用于下载,这里用i++来代替。

    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "DownLoadService.onCreate()...");  
    5.     timer = new Timer();  
    6.     timer.schedule(new MyTimerTask(), 0, TIME*1000);  
    7. }  
    8. class MyTimerTask extends TimerTask{  
    9.     @Override  
    10.     public void run() {  
    11.         if(100==i){  
    12.             i=0;  
    13.         }  
    14.         i++;  
    15.     }  
    16. }  
              bindService()方法执行之后会调用DownLoadService中的onCreate()方法,在其onCreate()方法中开启timer使得i++。

            4.2.7 Server端已经开启了下载,那么Client端需要及时获取下载进度并在主界面上更新。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         Log.i(TAG, "handleMessage...");  
    6.         int curLoad = iCountService.getCurrentLoad();  
    7.         mProgressBar.setProgress(curLoad);  
    8.         currentTv.setText(curLoad+"%");  
    9.     }  
    10.  };  
    11. class MyTimerTask extends TimerTask{  
    12.     @Override  
    13.     public void run() {  
    14.         mHandler.sendMessage(mHandler.obtainMessage());  
    15.     }  
    16. }  
               Client端的Timer在bindService()完成之后1秒再开始获取下载进度,获取方法是直接通过int curLoad = iCountService.getCurrentLoad();这里的getCurrentLoad()方法是DownLoadService内部类ServiceBinder中的方法。Client端将获取到的下载进度更新到介面上并更新进度条。

            4.3 小结

            通过上面的例子可以知道,这种方法简单实用,扩展性强,但其也有一些缺点,比如需要延迟一些再开始获取Server端的数据,从而无法完全实现从零开始同步更新。综其所述,通过自定义接口实现Activity与Service交互的方法还是比较实用的。适用於同进程中通信,不能进行跨进程通信。

            5. AIDL交互

            什么是AIDL?

            AIDL是Android Interface Definition Language的首字母缩写, 也就是Android接口定义语言。提及AIDL就不得不说下Android的服务,Android 支持两种服务类型的服务即本地服务和远程服务。

            本地服务无法供在设备上运行的其他应用程序访问,也就是说只能该应用程序内部调用,比如某些应用程序中的下载类服务,这些服务只能由内部调用。而对于远程服务,除了可以由本应用程序调用,还可以允许其他应用程序访问。远程服务一般通过AIDL来实现,可以进行进程间通信,这种服务也就是远程服务。

            本地服务与远程服务还是有一些重要的区别。具体来讲,如果服务完全只供同一进程中的组件使用(运行后台任务),客户端一边通过调用 Context.startService()来启动该服务。这种类型的服务为本地服务,它的一般用途是后台执行长耗时操作。而远程服务一般通过bindService()方法启动,主要为不同进程间通信。我们也将远程服务称为AIDL支持服务,因为客户端使用 AIDL 与服务通信。Android中对于远程服务有多种叫法:远程服务、AIDL服务、外部服务和RPC服务。

            5.1 AIDL实现流程图

    图5.1

            这属于代理/存根结构,通过这张AIDL的流程图,很容易发现Android实现IPC其实是在原来的C/S框架上加入了代理/存根结构。

            比如,你到自动取款机上去取款。那么你就是客户(Client),取款机就是你的代理(Proxy);你不会在乎钱具体放在那里,你只想将你的钱从取款机中取出来。你同银行之间的操作完全是取款机代理实现。你的取款请求通过取款机传到另一边,即银行的服务器(Server)。它也没有必要知道你在哪儿取钱,它所关心的是你的身份和你取款多少。当它确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。取款机不是直接同服务器连接的,他们之间还有一个“存根(Stub)”,取款机与存根通信,服务器与存根通信,从某种意义上说存根就是服务器的代理。实现效果如图5.2:

    图5.2

            5.3 实现原理

            AIDL属于Android的IPC机制,常用于跨进程通信,主要实现原理基于底层Binder机制。

            5.4 实现步骤

            5.4.1 建立工程。按照图5.3和图5.4建立AIDLServer端以及AIDLClient端。在AIDLServer端中只有一个服务程序,没有主界面,其主要功能就是负责下载。AIDLClient端从AIDLServer端获取当前下载进度(注:AIDLServer端和AIDLClient端是不同的两个APK,在模拟本例的时候,需要先在模拟器上安装AIDLServer编译出来的APK,安装方法可以直接在模拟器上运行一次,可以通过adb install your.apk 来安装)。

    图5.3

            AIDLServer端中新建了一个ICountService.aidl的文件,该文件内容如下:

    [plain] view plaincopy
    1. package com.seven.aidlserver;  
    2.   
    3. interface ICountService{  
    4.     int getCount();  
    5. }  

              aidl文件的书写规范如下:

            (1). Android支持String和CharSequence(以及Java的基本数据类型);

            (2). 如果需要在aidl中使用其它aidl接口类型,需要import,即使是在相同包结构下;

            (3). Android允许传递实现Parcelable接口的类,需要import;

            (4). Android支持集合接口类型List和Map,但是有一些限制,元素必须是基本型或者前面三种情况,不需要import集合接口类,但是需要对元素涉及到的类型import;

            (5). 非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。

    图5.4

             AIDLClient端需要将AIDLServer端的ICountService.aidl文件复製过去,这裡为了方便,新建了一个和Server端同名的包,并将ICountService.aidl放与其中。

             5.4.2 我们在Server端建立好ICoutService.aidl文件之后,Eclipse会在/gen/com.seven.aidlserver/目录下自动生成ICountService.java文件。该文件由Eclipse自动生成,请勿随便修改,后文我们需引用到的内容如下:

    [java] view plaincopy
    1. public static com.seven.aidlserver.ICountService asInterface(android.os.IBinder obj) {  
    2.     if ((obj == null)) {  
    3.         return null;  
    4.     }  
    5. android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);  
    6.     if (((iin != null) && (iin instanceof com.seven.aidlserver.ICountService))) {  
    7.         return ((com.seven.aidlserver.ICountService) iin);  
    8.     }  
    9.     return new com.seven.aidlserver.ICountService.Stub.Proxy(obj);  
    10. }  

              5.4.3 在Server端新建一个内部类继承自ICountService.Stub并覆写其中的getCount()方法,以及实例化该类的一个对象serviceBinder。

    [java] view plaincopy
    1. private AIDLServerBinder serviceBinder = new AIDLServerBinder();  
    2. class AIDLServerBinder extends ICountService.Stub{  
    3.     @Override  
    4.     public int getCount() throws RemoteException {  
    5.         return i;  
    6.     }  
    7. }  
             这里与前面提到的“通过接口实现交互”非常类似。

            5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder对象。

    [java] view plaincopy
    1. @Override  
    2. public IBinder onBind(Intent intent) {  
    3.     Log.i(TAG, "AIDLServer.onBind()...");  
    4.     return serviceBinder;  
    5. }  

             5.4.5 在Server端的onCreate()方法中,开启timer,模拟下载。在Client端通过bindService()绑定Server端的时候,会首先执行Server端的onCreate()方法。


    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "AIDLServer.onCreate()...");  
    5.     mTimer = new Timer();  
    6.     mTimer.schedule(new MyTimerTask(), 0,TIME * 1000);  
    7. }  
    8. class MyTimerTask extends TimerTask{  
    9.     @Override  
    10.     public void run() {  
    11.         if(i==100){  
    12.             i=0;  
    13.         }  
    14.         i++;  
    15.     }  
    16. }  

              5.4.6 Client端通过bindService()绑定Server端。


    [java] view plaincopy
    1. if(startBtn==v){  
    2.     Log.i(TAG, "start button click.");  
    3.     mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);  
    4.     mTimer.schedule(new MyTimerTask(), 1000 ,TIME * 1000);  
    5. }  
              这里的intent = new Intent(“com.seven.aidlserver”);这里跟Server端注册Service时过滤的要一致,也就是说只有发出相同的action才会启动该Service。同时开启了一个timer用于获取下载进度。
            5.4.7 Client端通过ServiceConnection来获取Server端的binder对象。

    [java] view plaincopy
    1. private ServiceConnection serConn = new ServiceConnection() {         
    2.     @Override  
    3.     public void onServiceDisconnected(ComponentName name) {  
    4.         iCountService = null;  
    5.     }         
    6.     @Override  
    7.     public void onServiceConnected(ComponentName name, IBinder service) {  
    8.         Log.i(TAG, "AIDLClient.onServiceConnected()...");  
    9.         iCountService = ICountService.Stub.asInterface(service);  
    10.     }  
    11. };  
              这里的iCountService对象实际上就是ICountService的对象在此实例化。

            5.4.8 获取当前下载进度并更新到界面上。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         try {  
    6.             int count =  iCountService.getCount();  
    7.             mTextView.setText(count+"%");  
    8.             mProgressBar.setProgress(count);  
    9.         } catch (RemoteException e) {  
    10.             e.printStackTrace();  
    11.         }  
    12.     }  
    13. };  
              通过更新介面上的进度条,可以狠容易的后去当前下载进度。因為AIDLServer端只是一个继承自Service的服务,因此就不贴出其AndroidManifest.xml文件了。

            5.5 小结

            AIDL在Android中是进程间通信常用的方式,可能使用较為复杂,但效率高,扩展性好。同时很多系统服务就是以这种方式完成与应用程序通信的。

            本文通过五个例子,分别介绍了五种与Service交互的方法,这些方法有的简单,有的可能要复杂一些。在这里只是做为对Servie的一些总结。后文附上源码下载链接,不需要积分的哦。:D

            源码下载

            所有源码均在Ubuntu 10.04 Eclipse-Indigo下实验通过 模拟器采用的是2.3的镜像

    展开全文
  • flex数据交互 方式

    千次阅读 2009-07-06 14:58:00
    数据具有流动性,数据传输是指根据用户...21.1 数据传输的方式Flex 3.0中的数据传输方式包括内部数据传输、文件流方式传输、XML方式传输、其他方式传输。应用程序内部的数据传输大多通过变量传递来实现。外部文件的数

    数据具有流动性,数据传输是指根据用户控制传递至指定目的地。数据交互是指Flex与其他程序进行数据交换,包括传递数据给其他程序和接收其他程序返回的数据。本章将详细讲解数据传输的各种方法及如何与其他程序进行交互。

    21.1  数据传输的方式

    Flex 3.0中的数据传输方式包括内部数据传输、文件流方式传输、XML方式传输、其他方式传输。应用程序内部的数据传输大多通过变量传递来实现。外部文件的数据可分为简单文本数据、XML数据和复杂数据。对于简单的文本数据可采用文件流方式传输。对于XML数据可采用XML方式传输。对于复杂的数据,如大型数据库中的数据,需要通过其他程序来辅助数据传输。

     21.1.1  内部数据传输

    内部数据传输是指应用程序内部的数据流动,而变量传递是其中最常使用的传输方式。对于同一文件或类中的变量可采用直接赋值的方式。对于不同文件或类中的变量可采用公有变量的方式。

    1.直接赋值方式

    直接赋值是指将一变量赋值给另一变量。以下代码将变量b的值直接赋值给变量a。

    var a:int,b:int=12;

    a=b;

    大部分情况下,使用“=”符将两变量连接起来就实现了变量传递,但有两种情况下需要特殊处理。一种情况是两个变量的类型不相同,需要强制转换。需要说明的是,若两个变量类型相近,编译器可自动转换。例如,将int类型的变量赋值给Number类型变量时,编译器自动执行变量传递。若两个变量类型相差甚远,如Object型与Array型,就需要强制转换。

    Flex 3.0中数据类型强制转换的语法如下所示。

    变量名 as 强制类型

    或者如下所示。

    (强制类型)变量名

    以下代码将int类型强制转换为Number类型。

    var s:Number=y as Number;

    var t:Number=(Number)y;

    另一种情况是特殊的变量类型,如Array等多维数据变量。前面章节中详细介绍过数组变量。为了节约变量空间,Array类型的变量并不存储全部数据,而是存储数组的首地址。若两个数组变量直接赋值,结果是两个变量都存储了同一数组的首地址,改变任何变量中的数据也就改变了数组的内容。

    以下代码中两个数组变量直接赋值,带来了错误的结果。

    var a:Array,b:Array=[1,2,3];

    a=b;

    a[0]=100;

    trace(a);           结果:100,2,3

    trace(b);           结果:100,2,3

    为了帮助读者理解,假设数组在内存的首地址为000001。变量赋值后,变量a、b都指向首地址000001。对变量a进行数据修改后,数组数据发生改变。但变量a、b仍然指向同一首地址。

    正确的做法是使用concat方法复制变量b。

    上述代码修改如下所示。

    var b:Array=[1,2,3];

    var a:Array=b.concat();

    a[0]=100;

    trace(a);           结果:100,2,3

    trace(b);           结果:1,2,3

    2.公有变量方式

    声明变量为公有的关键字为public”。其语法如下所示。

    public var 变量名:变量类型;

    以下代码定义了公有变量s。

    public var s:String="aafdsfdsa";

    不同文件或类中使用公有变量方式传输变量,其步骤如下所示。

    * 在工程中新建名为“Model”文件夹下,并在此文件夹下新建名为“model”的类。类中定义一公有变量a。

    以下代码定义了model类。

    //model.cs

    package Model

    {

        public class model

        {

            public static var a:String="123456";            //定义一个静态变量

        }

    }

     在MXML文件中调用model类的公有变量a。

    以下代码在MXML文件中调用公有变量a。

    <?xml version="1.0" encoding="utf-8"?>

    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" fontSize="13">

        <mx:Script>

            <![CDATA[

                import Model.model;

                var s:String=model.a;       //将"mode"l类中的"a"变量值传递给"s"变量

            ]]>

        </mx:Script>

        <mx:Panel title="内部数据传输" horizontalAlign="center" verticalAlign= "middle" width="288" height="148" x="109.5" y="56">

            <mx:Label text="来自model类的变量:{s}"/>

        </mx:Panel>

    </mx:Application>

    文本框:   图21-1  公有变量方式传输数据 按下Ctrl+F11快捷键编译运行程序。运行效果如图21-1所示。

     21.1.2  文件流方式传输

    文件流方式传输是指数据以二进制文件流的形式流动。简单的数据可存储于文本文件中,通过Flex AIR工程中新增的File、FileStream等类可以方便地操作本地文件。

    使用文件流方式传输数据的步骤如下所示。

    * 新建AIR工程。Flex 3.0中新增AIR工程项目及对本地文件操作类。

     在工程根目录中新建名为“test.txt”的文件。文件中的内容可随意。

     定义File类变量,并指向“test.txt”文件。

    创建File类变量的语法如下所示。

    var File变量:File=new File(文件路径);

    以下代码定义File类变量,并指向根目录下的“test.txt”文件。

    var file:File=new File(File.applicationResourceDirectory.nativePath+ "//test.txt");

    “File.applicationResourceDirectory.nativePath”表示工程路径。

     使用FileStream类打开文件“test.txt”。打开文件需要使用FileStream类,其语法如下所示。

    var FileStream变量:FileStream=new FileStream();

    FileStream变量.open(File变量,打开方式);

    打开方式可为“FileMode.READ”、“FileMode.WRITE”、“FileMode.APPEND”、“FileMode. UPDATE”四种。本程序中使用“FileMode.READ”。以下代码使用FileStream类打开文件“test.txt”。

    var stream:FileStream = new FileStream();   //定义FileStream类实例,用以处理文件流

    stream.open(file,FileMode.READ);            //以读的方式打开文件

     读取FileStream类中的数据。在使用FileStream类打开文件后,数据存储于FileStream变量中。可使用readUTFBytes方法读取数据。其语法如下所示。

    FileStream变量.readUTFBytes();

    readUTFBytes方法返回类型为String型。以下代码使用readUTFBytes方法将数据赋值给文本组件显示。

    txtFile.text=stream.readUTFBytes(stream.bytesAvailable);

    “stream.bytesAvailable”是readUTFBytes方法的可选参数,表示读取全部文件流数据。

     在MXML的代码模式下查看完整代码。以下代码是完整的MXML程序。

    <?xml version="1.0" encoding="utf-8"?>

    <mx:WindowedApplicationxmlns:mx="http://www.adobe.com/2006/mxml"layout="absolute"fontSize="13" creationComplete="initApp()">

        <mx:Script>

            <![CDATA[

                import flash.filesystem.*;             //引用filesystem下的全部类

                [Bindable]

                //定义File变量,并指向“test.txt”文件。

                var file:File=new File(File.applicationResourceDirectory. nativePath+"//test.txt");

                private function initApp():void

                {

                    //定义FileStream类实例,用以处理文件流

                    var stream:FileStream = new FileStream();

                    stream.open(file,FileMode.READ);    //以读的方式打开文件

                    //读取文件中的内容

                    txtFile.text=stream.readUTFBytes(stream.bytesAvailable);

                    stream.close();                     //关闭文件流

                }

            ]]>

        </mx:Script>

        <mx:Panel title="文件流方式传输" verticalAlign="middle" horizontalAlign ="center" width="446" height="295">

            <mx:TextArea id="txtFile"  width="426" height="250"/>

        </mx:Panel>

    </mx:WindowedApplication>

    文本框:   图21-2  文件流方式传输数据 按下Ctrl+F11编译运行程序。运行效果如图21-2所示。

     21.1.3  XML方式传输

    XML优点是简单小巧、存储方便、检索快速。所以,XML常用于数据存储和数据交换。Flex 3.0使用URLLoader类可方便地传输XML数据。使用XML方式传输数据的步骤如下所示。

    * 新建Flex工程。

     新建名为“TreeMenus.xml”文件,用以存储XML数据。以下代码是“TreeMenus.xml”文件中的数据定义。

    //TreeMenus.xml

    <?xml version="1.0" encoding="utf-8"?>

        <menus>

            <node label="Mail">

                <node label="Inbox"/>

                <node label="Personal Folder">

                    <node label="Demo"/>

                    <node label="Personal"/>

                    <node label="Saved Mail"/>

                    <node label="bar"/>

                </node>

                <node label="Calendar"/>

                <node label="Sent"/>

                <node label="Trash"/>

            </node>

        </menus>

     编写应用程序初始化处理函数initApp。函数initApp的主要处理是加载“TreeMenus.xml”文件。加载XML文件时需要定义一个URLRequest类变量,用以指明XML文件路径。其语法如下所示。

    var URLRequest变量:URLRequest=new URLRequest(XML路径);

    使用URLLoader类的load方法加载XML文件。其语法如下所示。

    URLLoader变量.load(URLRequest变量)

    以下代码定义了初始化函数initApp。

    public function initApp():void                      //应用程序初始化处理函数

    {

        //定义URLRequest实例,指定文件地址。

        var request:URLRequest=new URLRequest("TreeMenus.xml");

        loader.load(request);                           //加载XML文件

        loader.addEventListener(Event.COMPLETE,completeHandle);     //添加加载完成时的监听

    }

    “loader.addEventListener(Event.COMPLETE,completeHandle)”语句表示添加对XML加载完成事件的监听。一旦加载完成执行completeHandle函数。

     完成剩余MXML代码。剩余代码包括completeHandle函数,<mx:Tree>组件设计等。以下代码是完整的MXML代码。

    <?xml version="1.0" encoding="utf-8"?>

    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

        fontFamily="simsun" fontSize="12"

        layout="absolute"width="242" height="442" creationComplete="initApp()">

        <mx:Script>

            <![CDATA[

                import mx.collections.ArrayCollection;  //引用ArrayCollection类

                import mx.rpc.events.ResultEvent;       //引用ResultEvent事件类

                //定义一个URLLoader类实例

                public var loader:URLLoader=new URLLoader();

                public var menus:XML=new XML();         //定义一个XML类实例

                public function initApp():void          //应用程序初始化处理函数

                {

                    //定义URLRequest实例,指定文件地址。

                    var request:URLRequest=new URLRequest("TreeMenus.xml");

                    loader.load(request); //加载XML文件

                    loader.addEventListener(Event.COMPLETE,completeHandle);                                                         //添加加载完成时的监听

                }

                public function completeHandle(e:Event):void                                                                            //加载完成处理函数

                {

                    menus=XML(loader.data);             //数据传递给menus变量

                    var results:XMLList=menus.node;     //结果集传递给results变量

                    //results变量值传递给树形列表

                    tree.dataProvider=results; 

                }

            ]]>

        </mx:Script>

         <mx:Tree id="tree"x="10" y="35" width="218" height="397" labelField="@label" />

        <mx:Label x="10" y="10" text="Tree Nodes From XML File"/>

     </mx:Application>

    文本框:   图21-3  使用XML方式传输数据<mx:Tree>组件中labelField属性指定显示内容。运行效果如图21-3所示。

     21.1.4  其他方式传输

    除了上述小节中介绍的数据外,Flex应用程序可能还会遇到其他类型的外部数据。例如,SQL Server数据库数据、MySQL数据库数据、Oracle数据库数据等。Flex 3.0不能直接接收这些数据,需要其他程序处理这些数据后以特定的类型,如数组型、XML型、Object型传递给Flex。有关如何与其他程序交互将在后续章节中详细介绍。

    另外,Flex 3.0中新增了对本地数据库(.db格式)操作的类,可用于读取本地的数据库数据。新增了对PDF数据操作的类,可用于读取PDF数据。有关新增的特性将在后续章节中介绍。

    21.2  使用<mx:HTTPService>组件与HTTP程序交互

    <mx:HTTPService>组件可与所有的HTTP程序交互。例如,ASP、ASP.Net、JSP、PHP等。使用<mx:HTTPService>组件与HTTP程序交互的基本语法如下所示。

    <mx:HTTPService id="HTTPService组件id" url="HTTP程序地址"/>

    以下代码使用<mx:HTTPService>组件调用“www.google.cn”。

    <mx:HTTPService id="httpser" url=" www.google.cn"/>

    所有的HTTP程序都支持的网址带参方式,其语法如下所示。

    网址?参数名1=值1&参数名2=值2…&参数名n=值n

    以下代码传递参数a、b、c。

    http://www.baidu.com?a=11&b=22&c=33

    网址带参方式的优点是传参简单,但缺点是参数必须先转化为字符串类型,因为网址中只能是字符串类型。另外,若传递的参数较多,网址字符串就会变得很长。

    使用网址带参方式能把Flex应用程序中的数据传递给HTTP程序。HTTP程序接收数据后进行处理,并返回Flex可识别的数据类型,如数组型、XML型、Object型等。

    <mx:HTTPService>组件返回的数据存储于ResultEvent类中。使用<mx:HTTPService>组件的result事件可处理HTTP程序返回的数据。其语法如下所示。

    private function 处理函数名(e:ResultEvent):void

    {

        e.result                            //返回数据

    }

    <mx:HTTPService … result="处理函数名">

    返回的数据存储于ResultEvent类的result属性下。各种数据的具体位置与HTTP程序的处理结果有关。建议采用断点调试的方法确定数据的具体位置,即调试时查看ResultEvent变量的内部结构。

    以下代码传递用户名和密码给HTTP程序,HTTP程序处理后返回验证结果。

    import mx.rpc.events.ResultEvent;                       //引用ResultEvent类

    import mx.controls.Alert;                               //引用Alert类

    private function showResult(e:ResultEvent):void

    {

        Alert.show(e.result as String);                     //显示验证结果

    }

    <mx:HTTPService id="hs"

        url="http://www.aaaa.com?username=a&password=123"

        result="showResult(event)"/>

    21.3  <mx:HTTPService>组件实例

    本小节以实例讲解如何使用<mx:HTTPService>组件与HTTP程序交互。实例实现的功能为:Flex客户端传递操作数参数给ASP.NET程序,ASP.NET程序计算全部参数的和,并将结果返回给Flex应用程序。

     21.3.1  编写ASP.NET程序

    本实例的ASP.NET程序使用Visual Studio 2005(简称VS 2005)编写,创建步骤如下所示。

    * 双击打开VS 2005。

     单击“文件”|“新建”|“网站…”命令,打开“新建网站”对话框,如图21-4所示。

    图21-4  新建网站对话框

     在“模板”区域选择“ASP.NET网站”模板。在“位置”文本框中输入网站的存放路径。单击“确定”按钮完成网站的创建。

     编写ASP.NET代码。创建网站工程后自动生成了“Default.aspx”文件。按下F7快捷键打开“Default.aspx.cs”文件。“Default.aspx.cs”文件是“Default.aspx”网页的后台代码页。

    以下代码是“Default.aspx.cs”文件的具体代码,主要是实现连加功能,并返回XML数据。

    //Default.aspx.cs

    using System;                           //引用System名称空间下的所有类

    using System.Data;                      //引用Data名称空间下的所有类

    using System.Configuration;             //引用Configuration名称空间下的所有类

    using System.Web;                       //引用Web名称空间下的所有类

    using System.Web.Security;              //引用Security名称空间下的所有类

    using System.Web.UI;                    //引用UI名称空间下的所有类

    using System.Web.UI.WebControls;        //引用WebControls名称空间下的所有类

    using System.Web.UI.WebControls.WebParts;//引用WebParts名称空间下的所有类

    using System.Web.UI.HtmlControls;       //引用HtmlControls名称空间下的所有类

    public partial class _Default : System.Web.UI.Page

    {

        protected void Page_Load(object sender, EventArgs e)                                                                //页面初始化处理函数

        {

            int sum = 0;                    //定义int型变量sum,并初始化为0

            //接收网址中名为“num”的参数。多个“num”参数时,结果形如“1,2,3,4”。

            string s=Request.QueryString["num"];

            if (s != null)

            {

                string[] arr = s.Split(',');//将参数值分离并存储于arr数组中

                foreach (string i in arr)   //使用循环计算全部参数值的和

                {

                    sum += Convert.ToInt32(i);

                }

            }

            //返回XML数据。元素标签为<ComputeResult>。

            Response.Write("<ComputeResult>"+sum.ToString()+"</ComputeResult>");

        }

    }

    (12)       本程序中引用名称空间及Page_Load函数的代码都是VS 2005自动生成的。

    (13)       Request类的QueryString方法接收网址中传递来的数据。若有多个同名参数,结果以“,”分隔,形如“1,3,4,33”。

    (14)       Convert类的ToInt32方法将String型转为Int32型,用于计算。

    (15)       Response类的Write方法输出数据。本程序中将计算结果放于<ComputeResult>标签中返回。

    文本框:   图21-5  发布网站对话框 按下Ctrl+F5快捷键编译运行ASP.NET程序。用户可自带参数测试程序是否正确。例如,在浏览器中输入“http://localhost/ASP.NetExample/Default. aspx?num=2&num=5”。

     单击“生成”|“发布网站”命令,弹出【发布网站】对话框,如图21-5所示。

     在“目标位置”文本框中输入发布网站的本地路径,其他可默认。单击“确定”按钮,完成发布网站。发布网站的结果是生成不带后台文件(.cs文件)的网站。

     21.3.2  创建虚拟目录

    IIS是Internet Information Service的简称,用以提供对ASP、ASP.Net等HTTP程序的解释。Windows系统默认安装时未带IIS程序,用户可将系统安装光盘插入光驱安装。本书略过IIS的安装。

    IIS的默认路径为“系统盘:/Inetpub/wwwroot”,用户可将网站放置于此路径下。若用户需要将网站放置于别处,则需要配置虚拟目录,其步骤如下所示。

    * 单击“开始”|“控制面板”命令,打开控制面板,如图21-6所示。

    图21-6  控制面板

     双击“管理工具”|“Internet信息服务”项,打开“Internet信息服务”对话框,如图21-7所示。

    图21-7  Internet信息服务对话框

     在左侧树形列表中右击“默认网站”,选择“新建”|“虚拟目录…”命令,弹出“虚拟目录创建向导”对话框,如图21-8所示。

     单击“下一步”按钮,弹出“虚拟目录创建向导”第二步对话框,如图21-9所示。

             

    图21-8 “虚拟目录创建向导”对话框               图21-9 “虚拟目录创建向导”第二步对话框

     在“别名”文本框中输入虚拟目录别名。单击“下一步”按钮,弹出“虚拟目录创建向导”第三步对话框,如图21-10所示。

     在“目录”文本框中输入网站路径。单击“下一步”,弹出“虚拟目录创建向导”第四步对话框,如图21-11所示。

            

    图21-10 “虚拟目录创建向导”第三步对话框         图21-11 “虚拟目录创建向导”第四步对话框

     根据需要选择项目,一般默认即可。单击“下一步”按钮,完成虚拟目录的创建。

     在新建的虚拟目录中右击相应的网页,如“Default.aspx”,选择“浏览”命令测试网页是否运行正常。

     21.3.3  编写Flex程序

    编写Flex程序的步骤如下所示。

    * 新建Flex工程。

     设计外观模型。

    本实例中使用的可视组件包括Label组件、Button组件、DataGrid组件等。

    以下代码是外观模型的MXML代码。

    <mx:Panel title="使用HTTPService交互" width="368" height="334" x="78" y="30" layout="absolute">

        <mx:Label text="参数:" x="119" y="26"/>

        <mx:TextInput id="txtPara" x="161" y="24" width="95"/>  <!--输入框组件-->

        <!--DataGrid组件,用以显示参数数据-->

        <mx:DataGrid id="dg" x="76" y="64" height="166" width="179">

            <mx:columns>

                <mx:DataGridColumn dataField="para" headerText="参数"/>

            </mx:columns>

        </mx:DataGrid>

        <mx:Button label="添加" click="addData();" x="277" y="26"/>  <!--"添加"按钮组件-->

        <mx:Button label="清空" click="delData();" x="277" y="64"/>  <!--"清空"按钮组件-->

        <mx:Label text="连加结果为:" x="58" y="253"/>

        <mx:Label x="126" y="253" id="lblResult"/>  <!--Label组件,用以显示结果-->

        <mx:Button label="计算" click="addHandle();" x="277" y="249"/>   <!--"计算"按钮组件-->

    </mx:Panel>

    外观模型效果如图21-12所示。

    图21-12  <mx:HTTPService>实例的外观效果

     添加<mx:HTTPService>组件。在<mx:HTTPService>组件的result事件中显示ASP.NET程序返回的数据。以下代码定义了<mx:HTTPService>组件及result事件的处理函数。

    private function httpHandle(e:ResultEvent):void //执行<mx:HTTPService>组件后的处理函数

    {

        lblResult.text=e.result.ComputeResult;//显示结果

    }

    <!--HTTPService组件,用以调用HTTP服务-->

    <mx:HTTPService id="httpser" showBusyCursor="true" result="httpHandle (event);" useProxy="false"/>

    前面的ASP.NET程序将结果存于<ComputeResult>标签中。Flex程序接收后具体位置在“e.result.ComputeResult”中。

     编写按钮处理函数。本例中有三个按钮“添加”、“清空”、“计算”。“添加”按钮的处理是将输入框中的数据加入到DataGrid组件中。“清空”按钮的处理是清空DataGrid组件。“计算”按钮的处理是将DataGrid组件中数据传递给ASP.NET程序处理。以下代码定义了各个按钮的处理函数。

    import mx.rpc.events.ResultEvent;               //引用ResultEvent类

    import mx.controls.Alert;                       //引用Alert类

    [Bindable]

    private var arr:Array=new Array();              //定义数组arr,用以存放参数

    private function addHandle():void               //单击"计算"按钮时的处理函数

    {

        httpser.url="http://localhost:88/ASP.NetExample/Default.aspx";                                                              //初始化url

        if(arr.length>0)                            //若有参数时,字符串加上"?"

            httpser.url+="?";

        for(var i:int=0;i<arr.length;i++)           //添加参数,形如"num=1"

        {

            if(i!=arr.length-1)

                httpser.url+="num="+arr[i].para.toString()+"&";

            else

                httpser.url+="num="+arr[i].para.toString();

        }

        httpser.send();                         //开始执行<mx:HTTPService>组件

    }

    private function addData():void             //单击"添加"按钮时的处理函数

    {

        var obj:Object=new Object();            //定义Object变量

        obj.para=txtPara.text;                  //Object变量的para属性加入数据

        arr.push(obj);                          //添加Object变量到数组arr

        dg.dataProvider=arr;                    //将数组arr绑定至DataGrid组件上显示

        txtPara.text="";                        //清空输入框

        dg.validateNow();                       //刷新DataGrid组件

    }

    private function delData():void             //单击"清空"按钮时的处理函数

    {

        arr=null;                               //清空数组arr

        dg.dataProvider=arr;                    //将数组arr绑定至DataGrid组件上显示

        dg.validateNow();                       //刷新DataGrid组件

    }

    需要注意的是,<mx:HTTPService>组件的url属性指向的HTTP程序必须正确。用户可在浏览器中输入HTTP程序路径测试。

     按下Ctrl+F11快捷键编译运行程序。运行效果如图21-13所示。

    图21-13  <mx:HTTPService>实例运行效果

    21.4  使用<mx:WebService>组件与WebService程序交互

    Flex 3.0中的<mx:WebService>组件专门用于调用和处理WebService。本小节将介绍WebService的相关知识及如何使用<mx:WebService>组件。

     21.4.1  WebService概述

    WebService是一种在互联网中提供服务的技术。WebService技术标准由各大软件开发商制定,主要解决了不同开发语言间的沟通问题。例如,.Net程序调用WebService服务,而此WebService服务是由Java语言开发的。

    WebService具有通用性。不论用何种语言开发的WebService服务,调用的结果都是一致的。这是因为WebService有自身的标准,与开发语言无关。用户可使用几乎任何语言调用WebService服务,只要能找到WebService服务并且传递的参数正确。

    WebService技术在国内外已有较广泛的应用。例如,每日的天气、股票走势等都有免费的WebService服务。Flex应用程序中使用<mx:WebService>组件可方便地调用WebService服务。后续章节的实例中将介绍如何使用Visual Studio 2005开发WebService。

     21.4.2  如何使用<mx:WebService>组件

    <mx:WebService>组件的使用方法与<mx:HTTPService>组件的使用方法大同小异。最主要的区别在于参数的传递方式。<mx:WebService>组件中传递参数的语法如下所示。

    <mx:WebService id=" WebService组件id"

        wsdl="WebService地址">

        <mx:operation name="方法名">

            <mx:request>

                <参数名1>值1</参数名1>

                <参数名2>值2</参数名2>

                …

            </mx:request>

        </mx:operation>

    </mx:WebService>

    (16)       <mx:operation>组件表示WebService的一个方法,必须与WebService中的定义名称相同。

    (17)       <mx:request>组件存储参数,参数值以标签形式存储。需要注意的是参数的名称及顺序必须与WebService中的定义相同。

    以下代码定义了一个<mx:WebService>组件。<mx:WebService>组件中包含带参的getMostPopularPosts方法。

    <mx:WebService id="wsBlogAggr"

        wsdl="http://weblogs.macromedia.com/mxna/webservices/mxna2.cfc?wsdl"

        useProxy="false">

        <mx:operation name="getMostPopularPosts">

            <mx:request>

                <daysBack>30</daysBack>

                <limit>{cbxNumPosts.value}</limit>

            </mx:request>

        </mx:operation>

    </mx:WebService>

    本程序中limit参数值是动态的,绑定于下拉框组件cbxNumPosts上。

    <mx:WebService>组件的方法也可不显性表示参数,只需调用方法时指明即可。

    以下代码中<mx:WebService>组件不显性定义参数。

    wsBlogAggr.getMostPopularPosts(30,cbxNumPosts.value).send();

    <mx:WebService id="wsBlogAggr"

        wsdl="http://weblogs.macromedia.com/mxna/webservices/mxna2.cfc?wsdl"

        useProxy="false">

        <mx:operation name="getMostPopularPosts"/>

    </mx:WebService>

    调用<mx:Webservice>组件中的方法的语法如下所示。

    WebService变量.方法.send();

    以下代码调用getMostPopularPosts方法。

    wsBlogAggr.getMostPopularPosts.send();

    <mx:operation>组件中的result事件用以处理返回数据。使用方法与<mx:HTTPService>组件相同。

    以下代码调用地址为“http://www.wopos.com/webservice/Stock.asmx”的WebService服务并显示结果。

    <?xml version="1.0" encoding="GB2312"?>

    <mx:Applicationxmlns:mx="http://www.adobe.com/2006/mxml"layout="absolute"fontSize="13"creationComplete="wsStock.HelloWopos.send();">

        <mx:Script>

            <![CDATA[

                import mx.rpc.events.ResultEvent;           //引用ResultEvent类

                import mx.controls.Alert;                   //引用Alert类

                //HelloWopos方法返回数据时的处理函数

                private function wsHandle(e:ResultEvent):void

                {

                    lbl.text=e.result as String;            //显示返回数据

                }

            ]]>

        </mx:Script>

        <mx:Panel title="使用WebService交互" horizontalAlign="center" verticalAlign="middle" width="398" height="138">

            <mx:Label id="lbl" textAlign="center" width="378" height="24"/>

        </mx:Panel>

        <mx:WebService id="wsStock" showBusyCursor="true"

            wsdl="http://www.wopos.com/webservice/Stock.asmx?wsdl"

            fault="Alert.show(event.fault.faultString, 'Error')">

            <mx:operation name="HelloWopos" result="wsHandle(event);"/>

         </mx:WebService>

    </mx:Application>

    文本框:   图21-14  使用<mx:WebService>组件与WebService交互效果本程序在creationComplete事件,即初始化事件时调用WebService的方法。结果显示于Label组件上。

    程序的运行效果如图21-14所示。

    21.5  <mx:WebService>组件实例

    本小节的实例实现了如下功能:Flex程序通过WebService获取SQL Server 2000数据库中的数据,并将数据显示于DataGrid组件上。涉及的技术包括创建SQL Server 2000数据库、使用VS 2005编写WebService、使用<mx:WebService>组件等。

     21.5.1  创建数据库

    设计数据库名为“Company”。数据库中只有一张名为“Employee”的表。表的列设计如表21-1所示。

    表21-1 “Employee”表的列设计

       

       

    是否为主码

    employeeId

    Char(20)

    firstName

    Char(50)

     

    lastName

    Char(50)

     

    建立数据库步骤如下所示。

    * 单击“开始”|“所有程序”|“Microsoft SQL Server”|“查询分析器”命令,打开查询分析器,如图21-15所示。

    图21-15  查询分析器

     在查询分析器的编辑区中输入SQL语句。创建数据库的SQL语句如下所示。

    /*创建数据库Company*/

    create database Company

    on

    ( NAME = 'Company_dat',

        FILENAME = 'E:/Flex基础教程/源程序/第7章/GetDataFromSQLServer2000 /Company.mdf',

        SIZE = 10,

        MAXSIZE = 50,

        FILEGROWTH = 5 )

    LOG ON

    ( NAME = 'Company_log',

        FILENAME = 'E:/Flex基础教程/源程序/第7章/GetDataFromSQLServer2000 /Company.ldf',

        SIZE = 5MB,

        MAXSIZE = 25MB,

        FILEGROWTH = 5MB )

    GO

    /*设置当前数据库为Company*/

    use  Company

    go

    /*创建表Employee*/

    create table Employee

    (

      employeeId char(20) primary key,

      firstName char(50) ,

      lastName char(50)

    )

    /*插入三组测试数据*/

    insert into Employee (employeeId,firstName,lastName) values ('0','a','aaa')

    insert into Employee (employeeId,firstName,lastName) values ('1','b','bbb')

    insert into Employee (employeeId,firstName,lastName) values ('2','c','ccc')

    数据库所在路径的用户可自行更改。

     选择创建数据库Company的全部SQL语句(到第一个“go”为止)。

     单击  按钮执行SQL语句。

     选择设置当前数据库为Company的全部SQL语句(从第一个“go”到第二个“go”为止)。

     单击  按钮执行SQL语句。

     选择剩余的SQL语句(从第二个“go”至最后)。

     单击  按钮执行SQL语句。

     21.5.2  编写WebService

    使用VS 2005编写WebService的步骤如下所示。

    * 单击“文件”|“新建”|“网站”命令,弹出“新建网站”对话框,如图21-16所示。

     选择“ASP.NET Web服务”模板,在“位置”文本框中输入项目路径。单击“确定”按钮,完成项目的创建。

     编写WebService。在新建“ASP.NET Web服务”工程后会自动生成一个名为“Service.asmx”的文件。asmx格式文件即为WebService文件。按下F7快捷键,打开“Service.asmx”的后台代码页“Service.cs”。

    图21-16  新建网站对话框

    以下代码是“Service.cs”文件的具体定义。主要是定义了GetData方法获取数据库中的数据,并返回数组类型。

    //Service.cs

    using System;                                   //引用System名称空间下的类

    using System.Web;                               //引用Web名称空间下的类

    using System.Web.Services;                      //引用Services名称空间下的类

    using System.Web.Services.Protocols;            //引用Protocols名称空间下的类

    using System.Data.SqlClient;                    //引用SqlClient名称空间下的类

    using System.Collections;                       //引用Collections名称空间下的类

    using System.Data;                              //引用Data名称空间下的类

    [WebService(Namespace = "http://tempuri.org/")]//默认的名称空间

    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

    public class Service : System.Web.Services.WebService

    {

        public Service ()                           //默认构造函数

        {

        }

        public class Employee                       //自定义类,用以存储数据

        {

            public string employeeId;

            public string firstName;

            public string lastName;

        }

         [WebMethod]

         [System.Xml.Serialization.XmlInclude(typeof(Employee))]    //声明“Employee”类可写入XML

        public ArrayList GetData()                      //获得数据库数据

        {

            SqlConnection conn = new SqlConnection();  //定义“SqlConnnection”类实例

            //数据库连接字符串

            conn.ConnectionString = "Data Source=.;Initial Catalog=Company; Persist Security Info=True;User ID=sa;Password=sa";

            //定义“SqlCommand”实例,从“Employee”表中取数据

            SqlCommand command = new SqlCommand( "select * from Employee",conn);

            conn.Open();                                //打开连接

            SqlDataAdapter da = new SqlDataAdapter();   //定义“SqlDataAdapter”类实例

            //将“command”值传递给“SqlDataAdapter”的“SelectCommand”属性

            da.SelectCommand = command;

            DataSet ds = new DataSet();                 //定义“DataSet”类实例

            da.Fill(ds, "tables");                      //取数据

            ArrayList al = new ArrayList();             //定义“ArrayList”类实例

            for (int i = 0; i < ds.Tables[0].Rows.Count; i++)   //将全部数据存储于al变量中

            {

                Employee em = new Employee();               //定义“Employee”类实例

                //添加数据到“al”变量中

                  em.employeeId= ds.Tables[0].Rows[i]["employeeId"].ToString().Trim();

                  em.firstName = ds.Tables[0].Rows[i]["firstName"].ToString().Trim();

                  em.lastName=ds.Tables[0].Rows[i]["lastName"].ToString().Trim();

                al.Add(em);

            }

            conn.Close();                                   //关闭数据库

            return al;

        }

    }

    (18)       引用名称空间、类声明、默认构造函数等代码都是VS 2005自动生成的。

    (19)       “[WebService(Namespace = "http://tempuri.org/")]”表示WebService的名称空间。

    (20)       [WebMethod]关键字指明此函数为WebService的方法。

    (21)       DataSet类型是一种多表数据类型。若数据以此类型返回,Flex程序将很难处理。所以本程序中自定义了Employee类,将数据全部存储于ArrayList类型变量中。

    (22)       由于Employee类是自定义的,WebService在生成WSDL(WebService标准文件)时并不认识,所以需要声明。声明语句为“[System.Xml.Serialization.XmlInclude (typeof(Employee))]”。

    (23)       数据库连接语句“Data Source=.;Initial Catalog=Company;Persist Security Info=True; User ID=sa;Password=sa”必须正确。用户可根据实际设置修改用户名和密码。

    (24)       获取Employee表全部数据的SQL语句为“select * from Employee”。

     按下Ctrl+F5快捷键,编译运行WebService程序。若程序无误会显示WebService中的方法“GetData”。

     单击“生成”|“发布网站”命令,发布WebService网站。

     为WebService网站创建虚拟目录。创建步骤与<mx:HTTPService>实例中相同。

     在IIS中右击“Service.asmx”文件,选择“浏览”命令,测试WebService。

     21.5.3  编写Flex程序

    编写Flex程序的步骤如下所示。

    * 新建Flex工程。

     添加DataGrid组件。

    DataGrid组件用以显示来自WebService的返回数据。以下代码是DataGrid组件MXML代码。

    <mx:Panel title="获取SQL Server数据库职工数据">

        <!--定义数据列表,绑定来自数据库的数据-->

        <mx:DataGrid id="dgEmployee" width="397" dataProvider="{WS.GetData. lastResult}">

            <mx:columns>

                <mx:DataGridColumn headerText="用户编号" dataField="employeeId"/>                                                                <!--数据列-->

                <mx:DataGridColumn headerText="姓" dataField="firstName"/>                                                                      <!--数据列-->

                <mx:DataGridColumn headerText="名" dataField="lastName"/>                                                                       <!--数据列-->

            </mx:columns>

        </mx:DataGrid>

    </mx:Panel>

    DataGrid组件的dataProvider属性绑定于“WS.GetData.lastResult”,即<mx:WebService>组件GetData方法的结果集上。

     添加<mx:WebService>组件。以下代码是<mx:WebService>组件的MXML代码。

    <!--定义"WebService"组件,连接来自外部的WebService-->

    <mx:WebService id="WS" wsdl="http://localhost:88/GetDataFromSQLServer2000/ Service.asmx?WSDL"

        fault="Alert.show(event.fault.faultString, 'Error')" showBusyCursor ="true">

        <mx:operation name="GetData" resultFormat="object" />

    </mx:WebService>

    <mx:WebService>组件的wsdl属性指明WebService的地址,用户可根据实际设置修改。

     完成剩余代码。剩余代码包括应用程序初始化时调用WebService、引用Alert类等。

    以下代码是完整的MXML代码。

    <?xml version="1.0" encoding="utf-8"?>

    <mx:Applicationxmlns:mx="http://www.adobe.com/2006/mxml"layout="absolute"creationComplete="WS.GetData.send();">

        <mx:Script>

            <![CDATA[

            import mx.controls.Alert;                       //引用Alert类

        ]]>

        </mx:Script>

        <mx:Panel >

            <!--定义数据列表,绑定来自数据库的数据-->

            <mx:DataGrid id="dgEmployee" width="397" dataProvider="{WS.GetData. lastResult}">

                <mx:columns>

                    <mx:DataGridColumn headerText="用户编号" dataField= "employeeId"/>                                               <!--数据列-->

                    <mx:DataGridColumn headerText="姓" dataField="firstName"/>                                                              <!--数据列-->

                    <mx:DataGridColumn headerText="名" dataField="lastName"/>                                                               <!--数据列-->

                </mx:columns>

            </mx:DataGrid>

        </mx:Panel>

        <!--定义"WebService"组件,连接来自外部的WebService-->

        <mx:WebService id="WS" wsdl="http://localhost/GetDataFromSQLServer2000 /service.asmx?WSDL"

            fault="Alert.show(event.fault.faultString, 'Error')">

            <mx:operation name="GetData" resultFormat="object" />

        </mx:WebService>

    </mx:Application>

     按下Ctrl+F11快捷键,编译运行程序。运行效果如图21-17所示。

    图21-17  读取SQL Server 2000数据库数据效果图

    21.6  使用Fluorine网关与ASP.NET类交互

    上面章节中介绍了使用<mx:HTTPService>组件与ASP.NET网页进行交互。但这种方式的效率不如直接与ASP.NET类交互。Flex 3.0应用程序与ASP.NET类交互的难点在于,两种语言的数据类型几乎完全不同,不能正常交互。Fluorine网关的出现解决了这种转换问题。本章将详细介绍Fluorine网关的相关内容及使用方法。

     21.6.1  Fluorine简介

    Fluorine是一种开源的AMF(ActionScript Messaging Formatter)网关,专门负责Flex 3.0与.NET交互时的数据类型转换。Fluorine现支持ActionScript 2.0和ActionScript 3.0,所以Fluorine也可作为Flash与.NET交互时的AMF网关。Fluorine开发团队将Fluorine项目制作成Virsual Stdio中的模板,方便用户安装和配置。结合renaun团队开发的基于MXML的第三方组件RemoteObjectAMF0,用户可以有效、快速地与ASP.NET类进行交互。

    现Fluorine网关支持转换的类型如表21-2所示。

    表21-2  Fluorine网关支持转换的类型说明

       

       

    Strongly Typed Objects(强类型对象)

    使用“flash.net.registerClassAlias”类可以给自定义类取别名。若未注册别名,则在传递给服务器端时会被当作未名的对象。若注册别名,则在传递时能被正确地识别

    续表 

       

       

    Custom serialization(自定义串形格式化)

    使用“flash.util.IExternalizable”类可自定义数据如何格式化

    ArrayCollection(数组集)

    ActionScript中的ArrayCollection类型可被Fluorine格式化为“com.TheSilent Group.Fluorine.AMF3.ArrayCollection”类型,对应.NET中的ArrayList类型

    ByteArray(二进制数组)

    ActionScript中的ByteArray类型被Fluorine格式化为“com.TheSilentGroup. Fluorine.AMF3.ByteArray”类型,对应.NET中的Byte类型

     21.6.2  Fluorine的下载与安装

    Fluorine开源项目的主页为“http://fluorine.thesilentgroup.com/fluorine/index.html”。

    1.下载Fluorine

    下载步骤如下所示。

    * 在浏览器中输入网址后,打开官方主页,如图21-18所示。

    图21-18  Fluorine官方主页

     左边树形列表中展开“Download”结节,在右侧页面中单击“download”链接开始下载。

    2.安装Fluorine

    安装步骤如下所示。

    * 双击安装包“fluorine.exe”开始安装,弹出Fluorine安装第一步对话框,如图21-19所示。

     单击“Next”按钮,弹出选择安装路径对话框,如图21-20所示。

          

    图21-19  Fluorine安装第一步对话框                    图21-20  选择安装路径对话框

     在路径文本框中输入安装路径,单击“Next”按钮,弹出选择开始菜单名称对话框,如图21-21所示。

     单击“Next”按钮,弹出安装说明对话框,如图21-22所示。

         

    图21-21  选择开始菜单名称对话框                    图21-22  安装说明对话框

     确认安装无误后,单击“Install”按钮,完成安装。

     21.6.3  第三方组件RemoteObjectAMF0的使用方法

    RemoteObjectAMF0组件是一种基于MXML的第三方组件,用于连接AMF网关。下载地址为“http://renaun.com/blog/flex-components/remoteobjectamf0/”。

    使用RemoteObjectAMF0组件的步骤如下所示。

    * 将RemoteObjectAMF0组件的源文件放置在工程中。RemoteObjectAMF0组件的源文件是“src”文件夹下的“com”文件夹。可将“com”文件夹全部复制至工程根目录下。

     引用RemoteObjectAMF0组件所在的包。查看RemoteObjectAMF0组件的类定义时,可发现RemoteObjectAMF0组件的包为“com.renaun.rpc.*”。引用RemoteObjectAMF0组件的包的语法如下所示。

    xmls:名称=" RemoteObjectAMF0组件的包"

    以下代码在<mx:Application>组件中引用RemoteObjectAMF0组件的包,并定义名为“renaun”的名称空间。

    <mx:Application … xmls:renaun="com.renaun.rpc.*" >

     MXML文件中定义RemoteObjectAMF0组件。RemoteObjectAMF0组件的定义语法如下所示。

    <名称空间:RemoteObjectAMF0

        endpoint="AMF网关地址"

        id=" RemoteObjectAMF0组件id"

        source="指向调用的类">

        <名称空间:methods>

            <名称空间:method name="方法名" result="调用成功后的处理函数"/>

        </名称空间:methods>

    </名称空间:RemoteObjectAMF0>

    (25)       RemoteObjectAMF0组件的endpoint属性指明AMF网关地址。对于Fluorine网关,其地址为“Gateway.aspx”网页所处的地址。

    (26)       RemoteObjectAMF0组件的source属性指向类的名称空间。

    (27)       <名称空间:method>组件的name属性指向类中的方法,必须与类中的定义相同。

    (28)       <名称空间:method>组件的result事件处理返回的数据。使用方法与<mx:HTTPService>组件的result事件相同。

    以下代码定义了一个RemoteObjectAMF0组件与“FluorineExample.Services. GetSQL Server2000Data”名称空间下的类交互。

    <renaun:RemoteObjectAMF0

            endpoint="http://localhost/FluorineExample/Gateway.aspx"

            id="fs"

            source="FluorineExample.Services.GetSQLServer2000Data"

            showBusyCursor="true"

            >

            <renaun:methods>

                <renaun:method

                    name="GetData"

                    result="GetDataHandle(event)"

                />

                <renaun:method

                    name="InsertData"

                    result="InsertDataHandle(event)"

                />

            </renaun:methods>

        </renaun:RemoteObjectAMF0>

     调用RemoteObjectAMF0组件中的方法的语法如下所示。

    RemoteObjectAMF0组件变量名.方法名(参数1,参数2…);

    需要注意的是,参数的顺序必须与类中的定义相同。

    以下代码调用类中的InsertData方法。

    fs.InsertData("111","2222",txtee.text);

     21.6.4  Fluorine实例

    本实例将从SQL Server 2000数据库中取出通知信息显示在Flex端。在Flex应用程序中可插入新的通知到数据库。本实例涉及的主要技术包括:创建SQL Server 2000数据库、定义ASP.NET类、Flex 3.0通过Fluorine与ASP.NET类进行交互。

    使用Fluorine网关与ASP.NET类交互的步骤如下所示。

    * 创建数据库。数据库名为“School”,表名为“Notes”,表的列设计如表21-3所示。

    表21-3 “Notes”表的列设计

       

       

    是否为主码

    noteId

    Char(20)

    title

    Char(50)

     

    content

    Char(200)

     

    publisher

    Char(20)

     

    以下代码是创建“School”数据库的SQL语句。

    /*创建数据库School*/

    create database School

    on

    ( NAME = 'School_dat',

        FILENAME = 'E:/Flex基础教程/源程序/第7章/FluorineExample/School.mdf',

        SIZE = 10,

        MAXSIZE = 50,

        FILEGROWTH = 5 )

    LOG ON

    ( NAME = 'Company_log',

        FILENAME = 'E:/Flex基础教程/源程序/第7章/FluorineExample/School.ldf',

        SIZE = 5MB,

        MAXSIZE = 25MB,

        FILEGROWTH = 5MB )

    GO

    /*设置当前数据库为School*/

    use  School

    go

    /*创建表Notes*/

    create table Notes

    (

        noteId char(20) primary key,

        title char(50) ,

        content char(200),

        publisher char(20)

    )

    /*插入三组数据*/

    insert into Notes(noteId,title,content,publisher) values ('1','aaaaaa', 'aaaaaaaaaaaaaaaaaaaaa','a')

    insert into Notes(noteId,title,content,publisher) values ('2','bbbbbb', 'bbbbbbbbbbbbbbbbbbbbb','b')

    insert into Notes(noteId,title,content,publisher) values ('3','cccccc', 'ccccccccccccccccccccc','c')

    建立数据库的具体方法与WebService实例中相同,读者可参看WebService实例中的数据库设计。

     新建Fluorine项目。安装Fluorine后,Visual Stdio 2005中自动新增名为“Fluorine ASP.NET Web Application”的模板。创建工程的具体方法可参看WebService实例。

     设计ASP.NET类。在自动生成的代码文件夹“App_Code”下新建名为“GetSQLServer2000Data.cs”的类文件。GetSQLServer2000Data类中定义了两个方法:GetData和InsertData。GetData方法获得全部通知,并以DataSet类型返回。InsertData方法添加数据到数据库。以下代码定义了GetSQLServer2000Data类。

    //GetSQLServer2000Data.cs

    namespace FluorineExample.Services                  //类所在的名称空间,可自定义。

    {

        public class GetSQLServer2000Data

        {

            public GetSQLServer2000Data()               //默认构造函数

            {

            }

            public DataSet GetData()                    //获得数据库数据

            {

                SqlConnection conn = new SqlConnection();       //定义“SqlConnnection”类实例

                //数据库连接字符串

                conn.ConnectionString = "Data Source=.;Initial Catalog=School; Persist Security Info=True;User ID=sa;Password=sa";

                //定义“SqlCommand”实例,从“Notes”表中取数据

                SqlCommand command = new SqlCommand("select * from Notes", conn);

                conn.Open();//打开连接

                SqlDataAdapter da = new SqlDataAdapter();       //定义“SqlDataAdapter”类实例

                //将“command”值传递给“SqlDataAdapter”的“SelectCommand”属性

                da.SelectCommand = command;

                DataSet ds = new DataSet();                 //定义“DataSet”类实例

                da.Fill(ds, "tables");                      //取数据

                conn.Close();                               //关闭数据库

                return ds;

            }

            //插入数据函数

            public void InsertData(string title, string content, string publisher)

            {

                SqlConnection conn = new SqlConnection();   //定义“SqlConnnec tion”类实例

                //数据库连接字符串

                conn.ConnectionString = "Data Source=.;Initial Catalog=School; Persist Security Info=True;User ID=sa;Password=sa";

                SqlCommand command = new SqlCommand("select Max(noteId) from Notes", conn);

                conn.Open();                                //打开连接

                SqlDataAdapter da = new SqlDataAdapter();       //定义“SqlDataAdapter”类实例

                //将“command”值传递给“SqlDataAdapter”的“SelectCommand”属性

                da.SelectCommand = command;

                DataSet ds = new DataSet();                 //定义“DataSet”类实例

                da.Fill(ds, "tables");                      //取数据

                //新的id为最大noteId+1

                string newid=(Convert.ToInt32(ds.Tables["tables"].Rows[0][0]. ToString())+1).ToString();

                //SQL插入语句

                command = new SqlCommand("insert into Notes values('" + newid + "','" + title.Trim() + "','" + content.Trim() + "','" + publisher.Trim()+ "')",conn);

                command.ExecuteNonQuery();                  //执行SQL语句

                conn.Close();

            }

        }

    }

    (29)       “namespace FluorineExample.Services”指明类所处的名称空间。有了名称空间就能准确地定位类。

    (30)       用户应根据实际配置修改数据库字符串“Data Source=Initial Catalog=School;Persist Security Info=True;User ID=sa;Password=sa”。

    (31)       Fluorine网关会将.NET中的DataSet类型转换为Flex能够识别的Object类型,所以GetData方法可以使用DataSet类型返回数据。

    (32)       通知id由程序自动计算。计算方法是最大id加1。

     在VS 2005中按下Ctrl+F5快捷键编译运行程序。

     单击“生成”|“发布网站”命令,发布网站。

     为网站设置虚拟目录。

     在IIS中,右击“Gateway.aspx”文件,选择“浏览”命令,测量运行是否正常。“Gateway.aspx”文件运行时并无任何可见效果。

     将RemoteObjectAMF0组件源文件复制至工程根目录下。

     引用RemoteObjectAMF0组件的包,并定义名称空间。以下代码引用了RemoteObjectAMF0组件的包,并定义名称空间“renaun”。

    <mx:Application … xmls:renaun="com.raun.rpc.*">

     定义RemoteObjectAMF0组件。以下代码定义RemoteObjectAMF0组件指向GetSQLServer2000Data类。

    <!--第三方网关组件,用以连接AMF网关-->

        <renaun:RemoteObjectAMF0

            endpoint="http://localhost:88/FluorineExample/Gateway.aspx"

            id="fs"

            source="FluorineExample.Services.GetSQLServer2000Data"

            showBusyCursor="true"

            >

            <renaun:methods>

                <renaun:method

                    name="GetData"

                    result="GetDataHandle(event)"

                />

                <renaun:method

                    name="InsertData"

                    result="InsertDataHandle(event)"

                />

            </renaun:methods>

        </renaun:RemoteObjectAMF0>

    endpoint属性指向Fluorine网关的“Gateway.aspx”页。

    source属性指向类。使用类的名称空间能准确定位类。

    * 设计外观模型。本程序中的可视化组件包括DataGrid组件、Label组件、TextInput组件、Button组件等。

    以下代码是外观模型的MXML代码。

    <mx:Panel width="455" height="462" title="通知">

        <mx:DataGrid id="dg">

            <mx:columns>

                <mx:DataGridColumn headerText="编号" dataField="0"/>

                <mx:DataGridColumn headerText="标题" dataField="1"/>

                <mx:DataGridColumn headerText="内容" dataField="2"/>

                <mx:DataGridColumn headerText="发布者" dataField="3"/>

            </mx:columns>

        </mx:DataGrid>

        <mx:Canvas width="395" height="243">

            <mx:Label x="37" y="27" text="标题"/>

            <mx:TextInput x="80" y="25" id="txtTitle"/>

            <mx:Label x="37" y="155" text="发布者"/>

            <mx:TextInput x="80" y="153" id="txtPublisher"/>

            <mx:Label x="37" y="53" text="内容"/>

            <mx:TextArea x="80" y="55" width="278" height="90" id="txtContent"/>

            <mx:Buttonx="121"y="198"label="添加"id="btnInsert" click="fs. InsertData(txtTitle.text,txtContent.text,txtPublisher.text);"/>

        </mx:Canvas>

    </mx:Panel>

    文本框:   图21-23  Fluorine实例外观效果GetData方法返回的类型为DataSet。经Fluorine转换后,在Flex 3.0中以Object类型接收。由于DataSet类型为多表数据类型,所以在转换过程中列名丢失。用户可按顺序确定数据列,如dataFiled属性为“0”。外观模型效果如图21-23所示。

     编写GetData方法调用成功后的处理函数。

    函数的主要处理是将数据绑定至DataGrid组件的dataProvider属性上。

    以下代码定义了处理函数GetDataHandle。

    public function GetDataHandle(e:ResultEvent):void

    {

        //获得的数据绑定至DataGrid组件

        dg.dataProvider=e.result.tables.serverInfo.initialData as Array;

    }

    DataSet经Fluorine网关转换后较复杂。经调试发现数据存储于“e.result.tables. serverInfo.initialData”中。

     完成剩余代码。剩余代码包括InsertData方法调用成功后的处理函数、应用程序初始化处理、引用ResultEvent类等。

    以下代码是完整的MXML代码。

    <?xml version="1.0" encoding="utf-8"?>

    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"

        xmlns:renaun="com.renaun.rpc.*"

        layout="absolute" creationComplete="fs.GetData();" fontSize="13">

        <mx:Script>

            <![CDATA[

                import mx.collections.ArrayCollection;  //引用ArrayCollection类

                import mx.rpc.events.ResultEvent;       //引用ResultEvent类

                import mx.controls.Alert;               //引用Alert类

                //获得数据后的处理函数

                public function GetDataHandle(e:ResultEvent):void

                {

                    //获得的数据绑定至DataGrid组件

                    dg.dataProvider=e.result.tables.serverInfo.initialData as Array;

                }

                //插入数据成功后的处理函数

                public function InsertDataHandle(e:ResultEvent):void

                {

                    //提示插入成功

                    Alert.show("插入数据成功");

                    //重新获取数据

                    fs.GetData();

                }

            ]]>

        </mx:Script>

        <mx:Panel width="455" height="462" title="通知">

            <mx:DataGrid id="dg">

                <mx:columns>

                    <mx:DataGridColumn headerText="编号" dataField="0"/>

                    <mx:DataGridColumn headerText="标题" dataField="1"/>

                    <mx:DataGridColumn headerText="内容" dataField="2"/>

                    <mx:DataGridColumn headerText="发布者" dataField="3"/>

                </mx:columns>

            </mx:DataGrid>

            <mx:Canvas width="395" height="243">

                <mx:Label x="37" y="27" text="标题"/>

                <mx:TextInput x="80" y="25" id="txtTitle"/>

                <mx:Label x="37" y="155" text="发布者"/>

                <mx:TextInput x="80" y="153" id="txtPublisher"/>

                <mx:Label x="37" y="53" text="内容"/>

                <mx:TextArea x="80" y="55" width="278" height="90" id= "txtContent"/>

                <mx:Buttonx="121"y="198"label="添加"id="btnInsert" click="fs. InsertData(txtTitle.text,txtContent.text,txtPublisher.text);"/>

            </mx:Canvas>

        </mx:Panel>

        <!--第三方网关组件,用以连接AMF网关-->

        <renaun:RemoteObjectAMF0

            endpoint="http://localhost:88/FluorineExample/Gateway.aspx"

            id="fs"

            source="FluorineExample.Services.GetSQLServer2000Data"

            showBusyCursor="true"

            >

            <renaun:methods>

                <renaun:method

                    name="GetData"

                    result="GetDataHandle(event)"

                />

                <renaun:method

                    name="InsertData"

                    result="InsertDataHandle(event)"

                />

            </renaun:methods>

        </renaun:RemoteObjectAMF0>

    </mx:Application>

     按下Ctrl+F11编译运行程序。运行效果如图21-24所示。

    图21-24  Fluorine实例效果图

    21.7  小结

    本章详细地介绍了数据的传输与交互。数据传输按数据类型可分为内部数据传输、文件流方式传输、XML方式传输、其他方式传输。使用<mx:HTTPService>组件可与HTTP程序交互,如ASP、PHP、ASP.NET、JSP等。使用<mx:WebService>组件可与WebService交互。使用Fluorine网关可与.NET类交互。通过本章的学习,读者能够掌握Flex 3.0如何与其他程序交互。

    展开全文
  • 交互设计在任何的人工物的设计和制作过程里面都是可以避免的,区别只在于显意识和无意识。在完全依托虚拟形态存在的互联网产品,好的交互架构将会是产品制胜法宝。 交互设计本就该是互联互通环环相扣的 交互...
    交互设计在任何的人工物的设计和制作过程里面都是不可以避免的,区别只在于显意识和无意识。在完全依托虚拟形态存在的互联网产品,好的交互架构将会是产品制胜法宝。

    交互设计本就该是互联互通环环相扣的
    交互设计是设计人与物的对话(dialog),交互设计的目的包括:有用性、易用性和吸引性的设计和改善。近些年随着互联网化进程加快,特别是进入数字时代,交互设计的研究显得更加多元化,多学科各角度的剖析让交互设计理论的显得更加丰富。产品和用户体验日趋复杂、功能增多,新的人工物不断涌现,给用户造成的认知摩擦日益加剧的情况下,人们对交互设计的需求变得愈来愈显性,从而触发其作为单独的设计学科在理论和实践的呼声变得愈发迫切。一款好的产品,除了其商业价值,产品功能架构外。好的交互设计是产品为用户所接受至关重要的因素。交互设计在任何的人工物的设计和制作过程里面都是不可以避免的,区别只在于显意识和无意识。在完全依托虚拟形态存在的互联网产品,好的交互架构将会是产品制胜法宝。这里跟大家分享下我接受的交互设计几点启发。设计简洁清晰,自然易懂。

    好的交互设计可以让用户很轻松看到所需要信息
    一套好的交互设计必定是一目了然的,用户在接触APP的第一感觉是有可操作性并保持持续操作的欲望。人类在面对新的环境或者接触新的事物时会陷入思维困境,每个人都会有一套原生的理解事物的方式,而交互设计就是要用最简单的方式打破思维困境。在用户接触该事物的时候,保证给用户一个习惯、完全符合常态的事物形态。这样的形态要将用户期待的需求与你想要用户接受的部分相互结合。以微信为例,当前所有社交产品中,微信是最为广大用户所接受的。作为一款社交软件,微信刨除了所有与社交无关的模块。仅仅保留了最基础的导航栏,这里微信需要用户参与的就是社交,而用户意愿得到的本质也是进行社交。微信简化了一切可能影响用户社交行为的元素。

    信息的获取和传达的过程必须是简洁清晰,自然易懂
    设计的作用在于寻找功能和社会间的接点, 在功能足以说明一切的前提下,装饰是可 以节制的,如何把握节制的度是考验一个 设计师是否成熟的标尺。—— 田中一光
    所谓简洁清晰,也就是将信息最简化。少即是多,这是互联网时代的信条。作为产品的受体用户群,大家面对的不止是这一款软件。用户有太多要思考的信息,有太多要接触的事物。如果每一款产品都在为用户带来重复行为或者复杂的选择,用户会出现搜索恐惧恐惧。

    有时候给予更多其实对用户而言是一种伤害
    想象一下,如果你要买一双袜子,但是你处在一家服装百货超市,在没有售货员的情况下去搜索你要买的袜子。这简直是要命的!我们想一想名创优品在产品规划方面,帮用户筛选出最符合用户需求的商品并且简化设计与产品种类。保证用户从入店到出店在10min之内完成一站式购物,并且带来很好的体验。其实这也是交互设计应该学习的的一部分。所谓自然易懂,也即使用用户能够理解的语言。

    用户原版想要的是简单使用功能,最后却像得到一本天书一样
    用户获取信息的方式多样,并且对信息的理解程度也各有不同,所以信息传达要使用用户平时使用和理解的表达方式去传递信息,则更加可以被用户所接受。 产品面向用户不是专家之间的座谈会,永远不要指望用户花很大的时间与精力去研究你的产品。这样的行为完全是在挑战用户的耐性与容忍度。真正好的交互,是信息传达的极简化,交互设计要满足易用性原则。产品想被用户接受,产品本身就要简洁化,复杂的逻辑与不必要的流程都要隐藏。保证产品本身的易用性原则。

    选择适当的方式做好信息表述
    当前移动应用与网页信息表述种类大致依靠这几类:页面布局、文本交互、界面色彩、图像、图标、声音等。
    a.页面布局
    界面中的信息布局,会直接影响用户获取信息的效率。所以,一般界面的布局 因功能不同考虑的侧重点不同,并且会让用户有一种“区块感”,方便用户对信息的扫描性浏览。

    页面布局要遵循以下几点设计原则:
    1.界面布局尽量有秩序、排列整齐,防止过紧戒过松。要有明显的“区块感”,切忌混乱;
    2.布局要充分表现其功能性,对于每个区域所代表的功能应有所区别。如:标题区,工作区,提示/帮助区等;
    3.页面中最重要的信息所在的模块将是在屏幕中最明显的位置上。并且,应该是最大的。要让用户在最习惯的部分看到该选项;
    4.布局中的信息需要有明显的标志和简单介绍,比如标题栏和标题等;
    5.信息的位置保证一致性,让用户可以无需重新建立对页面信息分布的理解。

    b.文本交互
    交互文本指产品界面涉及交互操作中需要用户理解并反馈的所有的文字:包括 标题、按钮文字、链接文字、对话框提示、各种提示信息、帮劣等。
    这些文字直接影响用户在交互过程中对预期的理解,好的交互文本设计,可以提高用户 完成任务的效率。 然而对于文本的过分解读,将会极大的损伤用户的信任度,尤其是对于某些提示的过分解读就像是一则免责声明,降低用户的选择欲望。当然,过于缩小文本交互也是不对的,这样就会使得文本起不到应有的意义。

    文本交互要遵循以下设计原则
    1.表述的信息尽量口语化,少用尽量不用专业术语;
    2.表述语气柔和、礼貌,避免使用被动语态、否定句等;
    3.简洁、清楚的表达,文字较多要适当断句,尽量避免左右滚屏,折行等出现;
    4.对于同种操作的交互文本,操作行文字保持统一性。出现层级将会极大的提升用户的理解难度。同时会一定程度损伤设计美感;
    5.字体使用默认/标准的字体,大小以用户的视觉清晰分辨为主;
    文本交互一定要考虑其必要性,如果对于信息量较大的内容,文字可能无法描述,就要考虑从其它层面去实现。单纯的依靠文本本身反而会损害用户解读。

    c.界面色彩
    人眼一共大约能区分一千万种颜色,面对如此庞大的色素群,人类天生对于色觉有自己独特的感知,所以用户对界面中颜色的关注度非常的高。因此有效的使用色彩区分信息的级别、分类等,有助于用户对信息和操作产生关联,有效减少用户的记忆负担。

    界面色彩遵循以下几点设计原则
    1.根据不同的产品使用“场景”,选择其合适的颜色。如管理界面经常使用蓝色。
    对于特定的区域块要匹配用户比较接受的色调。(具体可参考情感化色调的相关知识)
    2.考虑颜色对用户的心理和文化的影响。比如黄色代表警告,绿色代表成功等。
    这里着重提示:一款产品的普适性一定要考虑颜色对于文化的影响。(具体可参考情感化色调的相关知识)
    3.避免界面中同时出现3种以上的颜色。
    4.颜色的对比度明显,如在深色的背景中使用浅色的文字。
    5.使用颜色指导用户关注到最重要的信息。这样的配色要根据页面的色调信息进行筛选排序。

    d.图像图标
    相对于单纯的文本,图像以及符号化的图标更加符合用户的认识习惯。往往表述一种信息,一张图片或者一个标识更能让同用户理解。适当的使用 图片不符号化的图标,会让用户很自然的建立起认知习惯。

    图像图标设计原则
    1.表意清晰、明确、有高度的概括性不指向性,让用户能够快速的联想到对应的功能和操作。
    2.同类在同一纬度的信息,在形式和色彩风格上尽量保持一致性;
    3.旨在于突出重要信息,用户可能产生理解偏差的情况下使用。避免滥用。
    4.尽量不交互文本结合使用。很多设计师会觉得已经做了图标但是担心用户不理解,所以加入文字描述。其实这样的解读完全是多余的,犯了过分解读用户行为的错误。用户在接触尝试阶段是行为驱动的,真正图标的实用价值会随着用户对产品的熟悉而被弱化。
    5.对应的图标选择已经被习惯定义的种类,保证图标与功能一致性。

    e.声音交互设计
    在网页的交互设计中,用于声音的信息表述方式相对视觉表述来说不是很多。一般声音仅应用于提示、提醒、帮助等信息表述。此类信息表述让用户通过听觉获取反馈,更加的直接有效。

    声音交互设计遵循的原则
    1.表述清晰、语气亲切,不生硬,有礼貌。
    2使用符合用户认知习惯的声音。如使用敲门声提示好友来访信息等。
    3使用不让用户反感(如:恐怖、恶心、烦躁)的声音。
    4在用户可预知的情况下发出声音。这里谈下最让人担忧的就是这点,有些语音功能就像恶作剧,尤其是我们看过的一些PPT,突然一个提示音简直让人崩溃。

    总结
    对于不同的信息表述方式,我们都要求设计师在表达信息的时候做到简洁清晰、自然易懂,尽量让用户觉得这是自然而然(这是产品设计进化的最终目标),而且信息一定要清晰明了,就像小溪流水一样自如,就像军人接受命令一样明确。这样才会让用户快速,准确,舒服的完成任务。希望以上这些使大家对于交互设计的学习会有帮助,同时建议大家多关注国外比较成熟的网站与移动应用的交互设计与产品本身的构造,这会对我们对于产品交互设计有更深层次的影响,最后期待看到大家设计出更加优秀的产品。

    本文来自UI中国,作者计无施
    展开全文
  • 上一篇文章 简述了Service的一些基础知识以及Service和Thread的简单区别,本文将着重讲解与Service交互的五种基本方式:广播交互、共享文件交互、Mssenger(信使)交互、自定义接口交互、AIDL交互。  1. 广播交互...
    上一篇文章 简述了Service的一些基础知识以及Service和Thread的简单区别,本文将着重讲解与Service交互的五种基本方式:广播交互、共享文件交互、Mssenger(信使)交互、自定义接口交互、AIDL交互。
    

           1. 广播交互

           提到Activity与Service的交互,可能狠多人首先想到的就是BroadCast——广播。在Android中,广播是系统提供的一种很好的交互方式。比如:在电池电量过低,开机完成等情况下,系统都会发出相应的系统广播,我们的应用程序只需要注册相应的广播接收器,就可以接收到这些系统的广播。同时,我们也可以定义自己的广播,这样在不同的Activity、Service以及应用程序之间,就可以通过广播来实现交互。我们通过模拟应用程序后台下载的情况来分析Service与Activity的交互方式。实现效果如图1.1:

    图1.1

            当我们点击StartService按钮之后,界面上的进度条将会每隔一秒加1。因为是模拟下载,因此下载动作我们在Service中通过一个Timer定时器来实现,在Timer中对一个整型数据i进行自加(i++),然后Client端获取Server端的i值并显示在界面上,从而达到模拟的目的。

            1.1. 实现原理

            Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。

            1.2. 实现步骤

            1.2.1 在Client端中通过startService()啟动Service。

    [java] view plaincopy
    1. if(v == startBtn){  
    2.     Log.i(TAG, "start button clicked...pid: "+Process.myPid());  
    3.     mIntent.setClass(BroadCastService.this, DownLoadService.class);  
    4.     startService(mIntent);  
    5. }  
              这里的mIntent = new Intent();Process.myPid()方法可以获取当前进程的ID号。
           1.2.2 DownLoadService接到启动的命令之后,执行onCreate()方法,并在其中开启timer计数模拟下载。

    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "DownLoadService.onCreate()...pid: "+Process.myPid());  
    5.     intent = new Intent("com.seven.broadcast");  
    6.     mTimer = new Timer();  
    7.     mTimer.schedule(new MyTimerTask(), 0 , TIME * 1000);  
    8. }  
              这里的intent是Server端向Client端传送数据用的,使用的action是”com.seven.broadcast”,Client端只有註册了相应action才能够接收到Server端的广播,并解析其中的内容。Process.myPid()是获取当前进程的ID。
            1.2.3 在Server端的timer计数其中发送广播,告知Client端目前下载进度。

    [java] view plaincopy
    1. class MyTimerTask extends TimerTask{  
    2.     @Override  
    3.     public void run() {  
    4.         if(i==100){  
    5.             i=0;  
    6.         }  
    7.         intent.putExtra("CurrentLoading", i);  
    8.         sendBroadcast(intent);  
    9.         i++;  
    10.         Log.e(TAG, "i= "+i);  
    11.     }  
    12. }  
              通过intent.putExtra(key,value);设置intent的值,然后通过sendBroadcast(intent)2方法,将广播发送出去。

           1.2.4 在Client端通过匿名内部类的方式实例化BroadcastReceiver并覆写其中的onReceive()方法。

    [java] view plaincopy
    1. BroadcastReceiver receiver = new BroadcastReceiver() {  
    2.     @Override  
    3.     public void onReceive(Context context, Intent intent) {  
    4.         if(MYACTION.equals(intent.getAction())){  
    5.             Log.i(TAG, "get the broadcast from DownLoadService...");  
    6.             curLoad = intent.getIntExtra("CurrentLoading", ERROR);  
    7.             mHandler.sendMessage(mHandler.obtainMessage());  
    8.         }  
    9.     }  
    10. };  
              在onReceive()方法中,判断是否为Server端发送的广播,如果是则对广播中携带的intent数据进行解包处理。这裡也可以单独写一个类继承自BroadcastReceiver,在其中覆写onReceive()方法,在Client端中实例化其对象,同样可以达到相应的效果,这样做可以为后面实现静态注册广播。
            1.2.5 更新主介面下载进度。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         Log.i(TAG, "current loading: "+curLoad);  
    6.         if(curLoad<0||curLoad>100){  
    7.             Log.e(TAG, "ERROR: "+curLoad);  
    8.             return;  
    9.         }  
    10.         mProgressBar.setProgress(curLoad);  
    11.         mTextView.setText(curLoad+"%");  
    12.     }  
    13. };  
                 这里对获取到的进度进行了一次判断,如果获取到的值没有异常,那么将会显示到界面,并更新进度条的进度,如果异常则返回。
             1.2.6 一定要对Broadcast进行注册和取消注册。只有注册之后相应的broadcast之后才能接收到广播注册方法有两种。
             动态注册/取消注册:

    [java] view plaincopy
    1. @Override  
    2. protected void onResume() {  
    3.     super.onResume();  
    4.     Log.i(TAG, "register the broadcast receiver...");  
    5.     IntentFilter filter = new IntentFilter();  
    6.     filter.addAction(MYACTION);  
    7.     registerReceiver(receiver, filter);  
    8. }  
    9. @Override  
    10. protected void onDestroy() {  
    11.     super.onDestroy();  
    12.     Log.i(TAG, "unregister the broadcast receiver...");  
    13.     unregisterReceiver(receiver);  
    14. }  
               动态註册可以随时註册随时取消。

            静态註册:

    [html] view plaincopy
    1. <receiver android:name="MyBroadcastReceiver">  
    2.     <intent-filter>  
    3.         <action android:name="com.seven.broadcast" />  
    4.     </intent-filter>  
    5. </receiver>  
               注:这里的MyBroadcastReceiver是一个继承自BroadcastReceiver的类。静态注册只要注册了一次那么只要该程序没有被卸载那么该广播将一直有效。

            最后贴出整个AndroidManifest.xml文件

    [html] view plaincopy
    1. <application android:icon="@drawable/icon" android:label="@string/app_name">  
    2.     <activity android:name=".BroadCastService"  
    3.               android:label="@string/app_name">  
    4.         <intent-filter>  
    5.             <action android:name="android.intent.action.MAIN" />  
    6.             <category android:name="android.intent.category.LAUNCHER" />  
    7.         </intent-filter>  
    8.     </activity>  
    9.     <service android:name="DownLoadService" android:process=":remote"/>  
    10. </application>  
              这里的android:process =”:remote”可以使该Service运行在单独进程中,从而可以模拟跨进程通信。

           1.3 小结

           通过广播的方式实现Activity与Service的交互操作简单且容易实现,可以胜任简单级的应用。但缺点也十分明显,发送广播受到系统制约。系统会优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟。同时在广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应。

            2. 共享文件交互

            这里提到的共享文件指的是Activity和Service使用同一个文件来达到传递数据的目的。我们使用SharedPreferences来实现共享,当然也可以使用其它IO方法实现,通过这种方式实现交互时需要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。实现效果如图2.1:

    图2.1

             2.1 实现原理

             Server端将当前下载进度写入共享文件中,Client端通过读取共享文件中的下载进度,并更新到主界面上。

             2.2 实现步骤

             2.2.1 在Client端通过startService()啟动Service。

    [java] view plaincopy
    1. if(startSerBtn==v){  
    2.     Log.i(TAG, "Start Button Clicked.");  
    3.     if(intent!=null){  
    4.     startService(intent);  
    5.     timer.schedule(new MyTimerTask(), 0, TIME * 1000);  
    6.     }  
    7. }  
              这里的intent = new Intent()2只是为了启动Server端。
            2.2.2 Server端收到启动intent之后执行onCreate()方法,并开启timer,模拟下载,以及初始化SharedPreferences对象preferences。

    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "DownLoadService.onCreate()...");  
    5.     preferences = getSharedPreferences("CurrentLoading_SharedPs"0);  
    6.     timer = new Timer();  
    7.     timer.schedule(new MyTimerTask(), 0, TIME*1000);  
    8. }  
              通过preferences=getSharedPreferences(String,MODE)2可以在/data/data/com.seven.servicetestdemo/shared_prefs文件夹下建立相应的xml文件。

           2.2.3 开始计数并将下载进度写入shared_prefs文件夹下的xml文件中,内容以键值对的方式保存。

    [java] view plaincopy
    1. class MyTimerTask extends TimerTask{  
    2.     @Override  
    3.     public void run() {  
    4.         setCurrentLoading();  
    5.         if(100==i){  
    6.             i=0;  
    7.         }  
    8.         i++;  
    9.     }         
    10. }     
    11. private void setCurrentLoading() {  
    12.     preferences.edit().putInt("CurrentLoading", i).commit();  
    13. }  
               对於SharedPreferences的使用需要注意一下几点:

            首先,使用sharedPreferences前需要获取文件引用。

            preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);

            其次,使用sharedpreferences写数据方式。

            preferences.edit().putInt("CurrentLoading", i).commit();

            最后,读取数据的方式。

            int couLoad = preferences.getInt("CurrentLoading", 0);

            2.2.4 Client端通过读取/data/data/com.seven.servicetestdemo/shared_prefs文件夹下的xml文件,并取得里面的键值对,从而获取到当前的下载进度,并更新到主界面上。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         int couLoad = preferences.getInt("CurrentLoading"0);  
    6.         mProgressBar.setProgress(couLoad);  
    7.         currentTv.setText(couLoad+"%");  
    8.     }  
    9.  };  

              2.3 小结

            因為方法简单,因此就不贴出AndroidManifest.xml文件了。对於这种方式实现Activity与Service的交互,可以说很方便,就像使用管道,一个往裡写,一个往外读。但这种方式也有缺陷,写入数据较为复杂以及数据量较大时,就有可能导致写入与读数据出不一致的错误。同时因为经过了一个中转站,这种操作将更耗时。

            3. Messenger交互(信使交互)

            Messenger翻译过来指的是信使,它引用了一个Handler对象,别人能够向它发送消息(使用mMessenger.send(Message msg)方法)。该类允许跨进程间基于Message通信,在服务端使用Handler创建一个 Messenger,客户端只要获得这个服务端的Messenger对象就可以与服务端通信了。也就是说我们可以把Messenger当做Client端与Server端的传话筒,这样就可以沟通交流了。实现效果如图3.1:

    图3.1

            3.1 实现原理

            在Server端与Client端之间通过一个Messenger对象来传递消息,该对象类似于信息中转站,所有信息通过该对象携带。

            3.2 Messenger的一般用法

            (1). 在Server端创建信使对象。

                   mMessenger = new Messenger(mHandler)

            (2). Client端使用bindService()绑定Server端。

            (3). Server端的onBind()方法返回一个binder对象。

                   return mMessenger.getBinder();

            (4). Client端使用返回的binder对象得到Server端信使。

    [java] view plaincopy
    1. public void onServiceConnected(ComponentName name, IBinder service) {    
    2.               rMessenger = new Messenger(service);        
    3.              ......  
    4.  }  
              这里虽然是new了一个Messenger,但我们查看它的实现

    [java] view plaincopy
    1. public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target);  }   
               发现它的mTarget是通过AIDL得到的,实际上就是远程创建的那个。

            (5). Client端可以使用这个Server端的信使对象向Server端发送消息。
                   rMessenger.send(msg);

            这样Server端的Handler对象就能收到消息了,然后可以在其handlerMessage(Message msg)方法中进行处理。经过这5个步骤之后只有Client端向Server端发送消息,这样的消息传递是单向的,那么如何实现消息的双向传递呢?

            首先需要在第5步做修改,在send(msg)前通过msm.replyTo = mMessenger将Client端自己的信使设置到消息中,这样Server端接收到消息时同时也得到了Client端的信使对象,然后Server端也可以通过使用得到的Client端的信使对象来项Client端发送消息 cMessenger = msg.replyTo2  cMessenger.send(message);

           这样即完成了从Server端向Client端发送消息的功能,这样Client端可以在自己的Handler对象的handlerMessage()方法中接收服务端发送来的message进行处理。

            3.3 实现步骤

            3.3.1 创建并初始化Server端的信使对象。

    [java] view plaincopy
    1. private Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         switch (msg.what) {  
    6.         case TEST:  
    7.             Log.e(TAG, "Get Message from MainActivity.");  
    8.             cMessenger = msg.replyTo;  
    9.             mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);  
    10.             break;  
    11.             default:  
    12.                 break;  
    13.             }  
    14.         }         
    15. };  
    16. //It's the messenger of server  
    17. private Messenger mMessenger = new Messenger(mHandler);  

              3.3.2 在Client端使用bindService()方法绑定Server端。

    [java] view plaincopy
    1. private void doBindService(){  
    2.         Log.i(TAG, "doBindService()...");  
    3.     mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);//if bind success return true  
    4.         Log.e(TAG, "Is bind: "+mIsBind);  
    5. }  

              3.3.3 在Server端的onBind()方法中返回一个binder对象。

    [java] view plaincopy
    1. @Override  
    2. public IBinder onBind(Intent intent) {  
    3.     Log.i(TAG, "MessengerService.onBind()...");  
    4.     return mMessenger.getBinder();  
    5. }  
              这裡的mMessenger就是Server端的信使对象。

            3.3.4 Client端使用ServiceConnected()方法来获取Server端的信使对象。

    [java] view plaincopy
    1. private ServiceConnection serConn = new ServiceConnection() {     
    2.     @Override  
    3.     public void onServiceDisconnected(ComponentName name) {  
    4.         Log.i(TAG, "onServiceDisconnected()...");  
    5.         rMessenger = null;  
    6.     }         
    7.     @Override  
    8.     public void onServiceConnected(ComponentName name, IBinder service) {  
    9.         Log.i(TAG, "onServiceConnected()...");  
    10.     rMessenger = new Messenger(service);//get the object of remote service  
    11.     mMessenger = new Messenger(mHandler);//initial the object of local service  
    12.     sendMessage();  
    13.     }  
    14. };  
               获取Server端的信使对象的同时,也初始化Client端的自己的信使对象,并且通过sendMessage()方法发送消息给Server端,表示可以开始下载了。

            3.3.5 Client端使用获取到的rMessenger来发送消息给Server端,同时将Client端的信使封装到消息中,一并发送给Server端。

    [java] view plaincopy
    1. private void sendMessage() {  
    2.     Message msg = Message.obtain(null, MessengerService.TEST);//MessengerService.TEST=0  
    3.     msg.replyTo = mMessenger;  
    4.     try {  
    5.         rMessenger.send(msg);  
    6.     } catch (RemoteException e) {  
    7.         e.printStackTrace();  
    8.     }  
    9. }  
               这里的MessengerService.TEST為Server端里的一个静态常量。Msg.replyTo=mMessenger;表示发送给Server端的信息里携带Client端的信使。

            3.3.6 Server端获取Client端发送的消息并得到Client端的信使对象。

    [java] view plaincopy
    1. private Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         switch (msg.what) {  
    6.         case TEST:  
    7.             Log.e(TAG, "Get Message from MainActivity.");  
    8.             cMessenger = msg.replyTo;//get the messenger of client  
    9.             mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);  
    10.             break;  
    11.         default:  
    12.             break;  
    13.         }  
    14.     }  
    15. };  
               在接收到Client端的信息之后,Server端开啟timer模拟下载,并接收Client端的信使对象。

            3.3.7 Server端向Client端发送数据。

    [java] view plaincopy
    1. class MyTimerTask extends TimerTask {  
    2.     @Override  
    3.     public void run() {  
    4.         if (i == 100) {  
    5.             i = 0;  
    6.         }  
    7.         try {  
    8.             //send the message to the client  
    9.         Message message = Message.obtain(null, MessengerService.TEST,i, 0);  
    10.             cMessenger.send(message);  
    11.         } catch (RemoteException e) {  
    12.                 e.printStackTrace();  
    13.         }  
    14.             i++;  
    15.     }  
    16. }  
              直接使用接收到的Client端的信使对象来发送当前下载进度给Client端。

            3.3.8 Client端接收来自Server端的数据。


    [java] view plaincopy
    1. private Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         switch (msg.what) {  
    6.         case MessengerService.TEST:  
    7.             Log.e(TAG, "Get Message From MessengerService. i= "+msg.arg1);  
    8.             int curLoad = msg.arg1;  
    9.             mTextView.setText(curLoad+"%");  
    10.             mProgressBar.setProgress(curLoad);  
    11.             break;  
    12.         default:  
    13.             break;  
    14.         }  
    15.     }  
    16. };  
               Client端的接收和Server端的接收狠类似。接收到Server端传过来的数据之后进行介面更新,以及下载进度更新。

            以下是AndroidManifest.xml文件:

    [html] view plaincopy
    1. <?xml version="1.0" encoding="utf-8"?>  
    2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    3.       package="com.seven.messengerservicedemo"  
    4.       android:versionCode="1"  
    5.       android:versionName="1.0">  
    6.     <uses-sdk android:minSdkVersion="10" />  
    7.     <application android:icon="@drawable/icon" android:label="@string/app_name">  
    8.         <activity android:name=".MainActivity"  
    9.                   android:label="@string/app_name">  
    10.             <intent-filter>  
    11.                 <action android:name="android.intent.action.MAIN" />  
    12.                 <category android:name="android.intent.category.LAUNCHER" />  
    13.             </intent-filter>  
    14.         </activity>  
    15.     <service android:name="MessengerService">  
    16.         <intent-filter>  
    17.     <action ndroid:name="com.seven.messagerservice.MessengerService" />  
    18.         </intent-filter>  
    19.     </service>  
    20. </application>  
    21. </manifest>  
               这里在Service的註册中加入了过滤动作,只有相匹配的action才能启动相应的Service。

            3.4 小结

            通过Messenger来实现Activity和Service的交互,稍微深入一点我们就可以知道,其实Messenger也是通过AIDL来实现的。对於前两种实现方式,Messenger方式总体上来讲也是比较容易理解的,这就和平时使用Handler和Thread通信一个道理。

            4. 自定义接口交互

            何谓自定义接口呢,其实就是我们自己通过接口的实现来达到Activity与Service交互的目的,我们通过在Activity和Service之间架设一座桥樑,从而达到数据交互的目的,而这种实现方式和AIDL非常类似(后文会说到)。实现效果如图4.1:

    图4.1

            4.1 实现原理

            自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server端用一个类继承自Binder并实现该接口,覆写了其中获取当前下载进度的方法。Client端通过ServiceConnection获取到该类的对象,从而能够使用该获取当前下载进度的方法,最终实现实时交互。

            4.2 实现步骤

            4.2.1 新建一个Interface,并在其中创建一个用于获取当前下载进度的的空方法getCurrentLoad()。

    [java] view plaincopy
    1. package com.seven.servicetestdemo;  
    2.   
    3. public interface ICountService {  
    4.     public int getCurrentLoad();  
    5. }  

            4.2.2 新建Server端DownService实现ICountService并在其中通过一个内部类ServiceBinder继承自Binder并实现ICoutService接口。

    [java] view plaincopy
    1. public class DownLoadService extends Service implements ICountService{  
    2. private ServiceBinder serviceBinder = new ServiceBinder();    
    3. public class ServiceBinder extends Binder implements ICountService{  
    4.     @Override  
    5.     public int getCurrentLoad() {  
    6.         Log.i(TAG, "ServiceBinder getCurrentLoad()... i=:"+i);  
    7.         return i;  
    8.     }     
    9. }  
    10. @Override  
    11. public int getCurrentLoad() {  
    12.     return 0;  
    13. }  
    14. }  
              在Server端中,实现获取下载进度的空方法getCurrentLoad();这是Eclipse自动生成的,重点不在这裡。我们需要在ServiceBinder类中覆写getCurrentLoad()方法,这裡我们返回当前的下载进度i。

           4.2.3 Client端使用bindService()绑定Server端。

    [java] view plaincopy
    1. if (startSerBtn == v) {  
    2.     Log.i(TAG, "Start Button Clicked.");  
    3.     bindService(intent, serConn, BIND_AUTO_CREATE);  
    4.     timer.schedule(new MyTimerTask(), 1000, TIME * 1000);//这里一定要延迟一下再开始获取数据,不然会报空指针异常  
    5. }  
               在Client端绑定Server端的同时,延迟1s开始获取下载进度。其中的intent = new Intent(“com.seven.test”)2com.seven.test该字符串要与在AndroidManifest.xml中申明的一致。

           4.2.4 Server端返回binder对象。

    [java] view plaincopy
    1. @Override  
    2. public IBinder onBind(Intent intent) {  
    3.     Log.i(TAG, "DownLoadService.onBind()...");  
    4.     return serviceBinder;  
    5. }  
               这里的serviceBinder因为继承了Binder因此也是Binder对象。

            4.2.5 Client端通过ServiceConnection来获取Server端的binder对象。

    [java] view plaincopy
    1. private ServiceConnection serConn = new ServiceConnection() {  
    2. @Override  
    3.     public void onServiceDisconnected(ComponentName name) {  
    4.         iCountService = null;  
    5.     }         
    6.     @Override  
    7.     public void onServiceConnected(ComponentName name, IBinder service) {  
    8.         Log.i(TAG, "onServiceConnected()...");  
    9.         iCountService = (ICountService)service;  
    10.     }  
    11. };  
              获取的过程是在bindService()过程中完成的,这里的iCountService是接口ICountService的对象,在这里得到实例化。

            4.2.6 在绑定完成之后,Server端会开启下载,在实际情况中Server端会开启独立线程用于下载,这里用i++来代替。

    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "DownLoadService.onCreate()...");  
    5.     timer = new Timer();  
    6.     timer.schedule(new MyTimerTask(), 0, TIME*1000);  
    7. }  
    8. class MyTimerTask extends TimerTask{  
    9.     @Override  
    10.     public void run() {  
    11.         if(100==i){  
    12.             i=0;  
    13.         }  
    14.         i++;  
    15.     }  
    16. }  
              bindService()方法执行之后会调用DownLoadService中的onCreate()方法,在其onCreate()方法中开启timer使得i++。

            4.2.7 Server端已经开启了下载,那么Client端需要及时获取下载进度并在主界面上更新。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         Log.i(TAG, "handleMessage...");  
    6.         int curLoad = iCountService.getCurrentLoad();  
    7.         mProgressBar.setProgress(curLoad);  
    8.         currentTv.setText(curLoad+"%");  
    9.     }  
    10.  };  
    11. class MyTimerTask extends TimerTask{  
    12.     @Override  
    13.     public void run() {  
    14.         mHandler.sendMessage(mHandler.obtainMessage());  
    15.     }  
    16. }  
               Client端的Timer在bindService()完成之后1秒再开始获取下载进度,获取方法是直接通过int curLoad = iCountService.getCurrentLoad();这里的getCurrentLoad()方法是DownLoadService内部类ServiceBinder中的方法。Client端将获取到的下载进度更新到介面上并更新进度条。

            4.3 小结

            通过上面的例子可以知道,这种方法简单实用,扩展性强,但其也有一些缺点,比如需要延迟一些再开始获取Server端的数据,从而无法完全实现从零开始同步更新。综其所述,通过自定义接口实现Activity与Service交互的方法还是比较实用的。适用於同进程中通信,不能进行跨进程通信。

            5. AIDL交互

            什么是AIDL?

            AIDL是Android Interface Definition Language的首字母缩写, 也就是Android接口定义语言。提及AIDL就不得不说下Android的服务,Android 支持两种服务类型的服务即本地服务和远程服务。

            本地服务无法供在设备上运行的其他应用程序访问,也就是说只能该应用程序内部调用,比如某些应用程序中的下载类服务,这些服务只能由内部调用。而对于远程服务,除了可以由本应用程序调用,还可以允许其他应用程序访问。远程服务一般通过AIDL来实现,可以进行进程间通信,这种服务也就是远程服务。

            本地服务与远程服务还是有一些重要的区别。具体来讲,如果服务完全只供同一进程中的组件使用(运行后台任务),客户端一边通过调用 Context.startService()来启动该服务。这种类型的服务为本地服务,它的一般用途是后台执行长耗时操作。而远程服务一般通过bindService()方法启动,主要为不同进程间通信。我们也将远程服务称为AIDL支持服务,因为客户端使用 AIDL 与服务通信。Android中对于远程服务有多种叫法:远程服务、AIDL服务、外部服务和RPC服务。

            5.1 AIDL实现流程图

    图5.1

            这属于代理/存根结构,通过这张AIDL的流程图,很容易发现Android实现IPC其实是在原来的C/S框架上加入了代理/存根结构。

            比如,你到自动取款机上去取款。那么你就是客户(Client),取款机就是你的代理(Proxy);你不会在乎钱具体放在那里,你只想将你的钱从取款机中取出来。你同银行之间的操作完全是取款机代理实现。你的取款请求通过取款机传到另一边,即银行的服务器(Server)。它也没有必要知道你在哪儿取钱,它所关心的是你的身份和你取款多少。当它确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。取款机不是直接同服务器连接的,他们之间还有一个“存根(Stub)”,取款机与存根通信,服务器与存根通信,从某种意义上说存根就是服务器的代理。实现效果如图5.2:

    图5.2

            5.3 实现原理

            AIDL属于Android的IPC机制,常用于跨进程通信,主要实现原理基于底层Binder机制。

            5.4 实现步骤

            5.4.1 建立工程。按照图5.3和图5.4建立AIDLServer端以及AIDLClient端。在AIDLServer端中只有一个服务程序,没有主界面,其主要功能就是负责下载。AIDLClient端从AIDLServer端获取当前下载进度(注:AIDLServer端和AIDLClient端是不同的两个APK,在模拟本例的时候,需要先在模拟器上安装AIDLServer编译出来的APK,安装方法可以直接在模拟器上运行一次,可以通过adb install your.apk 来安装)。

    图5.3

            AIDLServer端中新建了一个ICountService.aidl的文件,该文件内容如下:

    [plain] view plaincopy
    1. package com.seven.aidlserver;  
    2.   
    3. interface ICountService{  
    4.     int getCount();  
    5. }  

              aidl文件的书写规范如下:

            (1). Android支持String和CharSequence(以及Java的基本数据类型);

            (2). 如果需要在aidl中使用其它aidl接口类型,需要import,即使是在相同包结构下;

            (3). Android允许传递实现Parcelable接口的类,需要import;

            (4). Android支持集合接口类型List和Map,但是有一些限制,元素必须是基本型或者前面三种情况,不需要import集合接口类,但是需要对元素涉及到的类型import;

            (5). 非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。

    图5.4

             AIDLClient端需要将AIDLServer端的ICountService.aidl文件复製过去,这裡为了方便,新建了一个和Server端同名的包,并将ICountService.aidl放与其中。

             5.4.2 我们在Server端建立好ICoutService.aidl文件之后,Eclipse会在/gen/com.seven.aidlserver/目录下自动生成ICountService.java文件。该文件由Eclipse自动生成,请勿随便修改,后文我们需引用到的内容如下:

    [java] view plaincopy
    1. public static com.seven.aidlserver.ICountService asInterface(android.os.IBinder obj) {  
    2.     if ((obj == null)) {  
    3.         return null;  
    4.     }  
    5. android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);  
    6.     if (((iin != null) && (iin instanceof com.seven.aidlserver.ICountService))) {  
    7.         return ((com.seven.aidlserver.ICountService) iin);  
    8.     }  
    9.     return new com.seven.aidlserver.ICountService.Stub.Proxy(obj);  
    10. }  

              5.4.3 在Server端新建一个内部类继承自ICountService.Stub并覆写其中的getCount()方法,以及实例化该类的一个对象serviceBinder。

    [java] view plaincopy
    1. private AIDLServerBinder serviceBinder = new AIDLServerBinder();  
    2. class AIDLServerBinder extends ICountService.Stub{  
    3.     @Override  
    4.     public int getCount() throws RemoteException {  
    5.         return i;  
    6.     }  
    7. }  
             这里与前面提到的“通过接口实现交互”非常类似。

            5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder对象。

    [java] view plaincopy
    1. @Override  
    2. public IBinder onBind(Intent intent) {  
    3.     Log.i(TAG, "AIDLServer.onBind()...");  
    4.     return serviceBinder;  
    5. }  

             5.4.5 在Server端的onCreate()方法中,开启timer,模拟下载。在Client端通过bindService()绑定Server端的时候,会首先执行Server端的onCreate()方法。


    [java] view plaincopy
    1. @Override  
    2. public void onCreate() {  
    3.     super.onCreate();  
    4.     Log.i(TAG, "AIDLServer.onCreate()...");  
    5.     mTimer = new Timer();  
    6.     mTimer.schedule(new MyTimerTask(), 0,TIME * 1000);  
    7. }  
    8. class MyTimerTask extends TimerTask{  
    9.     @Override  
    10.     public void run() {  
    11.         if(i==100){  
    12.             i=0;  
    13.         }  
    14.         i++;  
    15.     }  
    16. }  

              5.4.6 Client端通过bindService()绑定Server端。


    [java] view plaincopy
    1. if(startBtn==v){  
    2.     Log.i(TAG, "start button click.");  
    3.     mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);  
    4.     mTimer.schedule(new MyTimerTask(), 1000 ,TIME * 1000);  
    5. }  
              这里的intent = new Intent(“com.seven.aidlserver”);这里跟Server端注册Service时过滤的要一致,也就是说只有发出相同的action才会启动该Service。同时开启了一个timer用于获取下载进度。
            5.4.7 Client端通过ServiceConnection来获取Server端的binder对象。

    [java] view plaincopy
    1. private ServiceConnection serConn = new ServiceConnection() {         
    2.     @Override  
    3.     public void onServiceDisconnected(ComponentName name) {  
    4.         iCountService = null;  
    5.     }         
    6.     @Override  
    7.     public void onServiceConnected(ComponentName name, IBinder service) {  
    8.         Log.i(TAG, "AIDLClient.onServiceConnected()...");  
    9.         iCountService = ICountService.Stub.asInterface(service);  
    10.     }  
    11. };  
              这里的iCountService对象实际上就是ICountService的对象在此实例化。

            5.4.8 获取当前下载进度并更新到界面上。

    [java] view plaincopy
    1. Handler mHandler = new Handler(){  
    2.     @Override  
    3.     public void handleMessage(Message msg) {  
    4.         super.handleMessage(msg);  
    5.         try {  
    6.             int count =  iCountService.getCount();  
    7.             mTextView.setText(count+"%");  
    8.             mProgressBar.setProgress(count);  
    9.         } catch (RemoteException e) {  
    10.             e.printStackTrace();  
    11.         }  
    12.     }  
    13. };  
              通过更新介面上的进度条,可以狠容易的后去当前下载进度。因為AIDLServer端只是一个继承自Service的服务,因此就不贴出其AndroidManifest.xml文件了。

            5.5 小结

            AIDL在Android中是进程间通信常用的方式,可能使用较為复杂,但效率高,扩展性好。同时很多系统服务就是以这种方式完成与应用程序通信的。

            本文通过五个例子,分别介绍了五种与Service交互的方法,这些方法有的简单,有的可能要复杂一些。在这里只是做为对Servie的一些总结。后文附上源码下载链接,不需要积分的哦。

            源码下载

            所有源码均在Ubuntu 10.04 Eclipse-Indigo下实验通过 模拟器采用的是2.3的镜像。

    展开全文
  • 本文信息交互是指办公和经营管理活动中所产生的公众或定向业务信息,以及在信息共享过程中,以业务规则形式衍生出的评论、评价、回复、转发等业务信息延续。
  • PHP和Nginx的交互方式

    千次阅读 2015-09-03 19:33:51
    nginx和php交互是通过fastcgi模块来实现的。fastcgi在nginx中是作为一个upstream实现的。可以使用如下的配置实现nginx和php的交互,从而把nginx接收到的请求转发给php。fastcgi_pass unix:/home/wangwei/...
  • 常见的Web实时消息交互方式和SignalR

    千次阅读 2017-05-16 15:08:11
    Web消息交互技术 1.1 常见技术 1.2 WebSocket介绍 1.3 WebSocket示例2. Signal 2.1 SignalR是什么 2.2 默认传输方式 2.3 指定传输方式 2.4 自动管理传输方式 2.5 通信模型 2.6 SignalR示例...
  • 某一天,我突然在想:App的交互方式有哪些?不同的交互方式有什么区别?每一种交互方式主要的运用场景是什么?如何运用不同的交互方式创造出好玩又爽的体验? 然而,我并没有找到有关于上述问题的系统化的回答,...
  • 信息架构设计是对信息进行结构、组织方式以及归类的设计,好让使用者与用户容易使用和理解的一项艺术与科学。(如:饭店装修) 目的: 让用户一眼就明白你的产品能做什么,大概怎么用。(如:底部导...
  • Service Activity三种交互方式

    万次阅读 2012-09-09 22:52:44
    service有两种类型:  本地服务(Local Service... 前者用于实现应用程序自己的一些耗时任务,比如查询升级信息,并占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。  后者可被其他
  • web前端后端数据的交互方式总结

    万次阅读 2016-05-04 01:41:29
    做web开发,很重要的一个环节就是前后台的数据的交互,数据从页面提交到contoller层,数据从controler层传送到jsp页面来显示。这2个过程中数据具体是如何来传送的,是本节讲解的内容。  首先说一下数据如何从后台...
  • 许多新的设备如雨后春笋般层出不穷,但总体来看,他们都有一些共同的特性,那就是具备高清晰的显示能力,屏幕比较大、有良好的视觉效果,支持友好的触摸交互方式,带有很多新式的传感器模块,如GPS,温度感应器、...
  • web前后台数据交互方式

    千次阅读 2016-05-08 22:33:43
    做web开发,很重要的一个环节就是前后台的数据的交互,数据从页面提交到contoller层,数据从controler层传送到jsp页面来显示。这2个过程中数据具体是如何来传送的,是本节讲解的内容。  首先说一下数据如何从...
  • 进程中的线程间内存共享,这是比较常用的通信方式和交互方式。 注:定义全局变量时最好使用volatile来定义,以防编译器对此变量进行优化。 Message消息机制 常用的Message通信的接口主要有两个:PostMessage和...
  •  本文将通过三大部分来讲解Android中程序与Service的交互方式,这里说的交互方式指的是如何与Service进行消息的传递,比如:从Service中获取信息,向Service发送信息等等。举个简单的例子,当我们通过手
  • web 前后台数据交互方式

    万次阅读 2012-05-25 15:41:23
    做web开发,很重要的一个环节就是前后台的数据的交互,数据从页面提交到contoller层,数据从controler层传送到jsp页面来显示。这2个过程中数据具体是如何来传送的,是本节讲解的内容。  首先说一下数据如何从后台...
  • 本文分享自天猫精灵 M 实验室,介绍 M 实验室关于手势识别交互能力的研究,包括在手势识别方面所做的一些业务和算法上的探索,以及手势识别未来的应用和对相关算法的一些展望。 一、概述 “手势是人类沟通的最自然...
  • 进程间交互的几种方式

    千次阅读 2016-04-11 21:27:30
    # 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。 # 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它...
  • 本文整理了设计师常逛的网站,这些资料信息网站包括交互设计、信息图、信息可视化,在线制图、数据可视化,本文大致的内容包括: 《灵感——可以参看如下网站来寻找灵感网站汇总》、 《信息图工具——可视化工具资源...
  • 说这些, 其实也是为了说明也因为WebView的前后变化太大了, 所以在低版本和版本上, WebView上传文件的方式都略有不同, 而且在安卓4.4的一些设备上难以保证所有机型都成功。导读->, Android H
  • 人机交互技术是通过识别人体包括手势、体势、表情等动作,甚至采用语音和生物电信号实现人与计算机的多通道融合的交互技术。Kinect作为一个划时代的产品,通过获取彩色图像、深度图像以及人体骨骼图像提供一项全新的...
  • HRI-人与机器人的智能交互)课程介绍与资料这是机器人系统设计与控制技术的后续课程,全部的课程内容包括四门课程(本科),如下:人机智能交互技术是人机融合的基础,课程介绍多种人机交互技术,包括传统和新兴的,...
  • Chapter5 交互设计测试与评估 第一章 为什么要开展测试与评估 用户测试:请目标用户使用产品来完成任务,观察并记录用户使用产品的整个过程. 用户测试的定义:也被称为产品可用性测试,指特定的用户在特定的使用...
  • 尼尔森十大交互设计原则

    千次阅读 2019-04-21 12:16:30
    因此,我们能把它上升为一种标准,而是应该当做一种经验来学习,然后跟现实中的设计结合来使用。接下来,作者通过一些具体的实例来跟大家深度解析尼尔森十大交互设计原则在设计中的用法~ 作者简介 雅各布·...
  • Linux用户态和内核态交互的几种方式

    千次阅读 2019-01-22 18:45:49
     在kernel module中调用printk是最简单的传递信息到用户空间的方法。   2/用户态-&gt;内核态  在linux中,用户对设备的操作往往被抽象为对文件的操作。利用这一特性,可以通过注册和实现伪字符设备...
  • 交互概览图

    千次阅读 2017-01-02 14:48:07
    2,交互概览图用于将一些零散的顺序图组织在一起,它采用了活动图的构造方式,利用了活动图的各种控制节点,并把活动图的每个活动结点替换为一个交互或者交互使用。每个交互或者交互使用都使用一个顺序图表示。
  • 我们将提到每个场景中所使用的交互方式,以及具体实现的方式。   Menu 场景中的交互  每个menu场景都包含了几个组件,其中我们需要重点关注的是MenuButton,VRInteractiveItem和Mesh Collider。 ...
  • Ajax 异步交互

    千次阅读 2020-09-07 08:21:13
    异步交互的区别在于同步交互需要等待结果,而异步交互不需要等待。 所谓异步交互,就是指发送一个请求,需要等待返回,随时可以再发送下一个请求。 同步交互与异步交互区别在于:同步需要等待结果,而异步需要...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 482,100
精华内容 192,840