码迷,mamicode.com
首页 > 移动开发 > 详细

详解 Android 通信【史上最全】

时间:2016-06-10 08:35:54      阅读:454      评论:0      收藏:0      [点我收藏+]

标签:

什么是通信?

通信 ,顾名思义,指的就是信息的传递或者交换

看完本文能收获什么?

按目录索引,你可以学习到
1. 组件间的通信,Activity,fragment,Service, Provider,Receiver
2. 进程间的通信,AIDL
3. 线程间的通信,Handler,AnsycTask,IntentService
4. 多个App间的通信
5. 使用大型开源框架完成组件通信,EventBus,otto
6. 网络通信基础篇:Google 课程–AnsycTask+HttpClient
7. 网络通信提高篇:开源框架Ansyc-Httpclient,okttp,Retrofit

建议阅读本文时遵循以下学习思路

1. 研究对象:Activity,fragment等组件

2. 信息存在形式:Intent,Bundle,静态变量,全局变量,还是点击事件,触摸事件的回调监听,或者文件形式(Sharepreference,SQLite,File , NetStream) ,本质就是信息源

3. 信息传递的形式:网路,回调监听,线程,Intent,全局Application

4. 相同形式的思路,不会出现第二次,请读者举一反三

5. 最后强调研究对象是单一的

Activity通信

Activity 和 Activity

1. 常规方式:Intent Bundle

通过Intent 启动另一个Activity时,有两种重载方式:

  • startActivity(new Intent(),new Bundle());
  • startActivityForResult(new Intent(),FLAG,new Bundle());

从参数列表就可以总结出来,有Intent,和Bundle,可以传递8种基本数据类型和可序列化的数据类型,比如字符串和字节数组。提到可序列化,就引发 Intent和Bundle 的局限性了:

  • Intent Bundle 无法传递“不可序列化”的数据,比如Bitmap,InputStream,解决办法有很多种,最简单的就是将“不可序列化”的对象,转换成字节数组,这里因为主要是讲解通信,所以不展开讲了。
  • Intent Bundle 能传递的数据大小在40K以内 。

PS : 很多人不理解为什么把Intent和Bundle放在一起谈,因为Intent 底层存储信息的原理也是通过Bundle存储!

2. 公有静态变量

比如 public static String flag=“中国”;

使用方式 比如 在其他Activity当中 FirstActivity.flag=“china”; 修改 静态变量的值

3. 基于物理形式:

比如 File,SQLite,Sharepreference 物理形式

4. 全局变量:

比如Application:Application是与Activity,Service齐名的组件,非常强大,它的特点是全局组件共用,单例形式存在,在其他组件中,我们只需要Context.getApplication()获得该对象的引用即可

Activity 和Fragment,Service,BrodcastReceiver

,首先都遵循,如何启动它们,就如何传递信息的原则:

1. Activity与Fragment

1. 通过构造函数传递 2.获取Fragment的实例对象

//CustFragment 是自定义的fragment,参数列表也可以自己定义咯,
 getSupportFragmentManager().beginTransaction()
             .add(new CustFragment(自定义的的参数列表),new String("参数"))

  //------------------method two-----------------------
  getSupportFragmentManager().findFragmentById(R.id.headlines_fragment);
  //------------------method three----------------------
   getSupportFragmentManager().findFragmentByTag("HeadLines");

聪明的读者可能会问Fragment如何与Activity通信类似的问题,这是个好问题,请注意我们的研究的原则是单一目标原则,在这节我研究的是Activity,你的疑惑在后面都会一一解答

2. Activity与Service

Activity启动Service的两种方式:


  //CustomService 是自定义Service,完成一些后台操作

  startService(new Intent(FirstActivity.this,CustomService.class));

  bindService(new Intent(FirstActivity.this,CustomService.class)), new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //当前启动的service 一些数据就会回调回这里,我们在Activity中操作这些数据即可
                get
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {

            }
        },flags);

从启动方式就可以看出,通过Bundle对象的形式存储,通过Intent传输,来完成Activity向Service传递数据的操作

3. Activity与BroadcastReceiver

启动广播的形式也有两种:

  //method one !!!-----------------------------------------------
  registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {

            }
        },new IntentFilter(),"",new Handler());


  //method two !!!-----------------------------------------------     
  registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {

            }
        },new IntentFilter());

关于method one 的第三个参数Handler很多人会很费解

参照registerReceiver中源码关于该Handler参数的解释:

Handler identifying the thread that will receive the Intent. If null, the main thread of the process will be used.
定义了一个用于接收Intent的子线程,如果不填或者默认为null,那么就会在主线程中完成接收Intent的操作

很明显,Activity与BroadcastReceiver通信时,用的也是Intent传递,Bundle存储

4. 通讯时的同步问题

这里的同步通讯问题,为下文Fragment通讯作铺垫,不是这个问题不重要,不值得引起你注意,只是我想把问题放在它最应该出现的位置。

以上只是基础的传递数据的形式,大部分都是静态的,现在有一种需求,用户操作Activity,发出了某些指令,比如按下,滑动,触摸等操作,如何完成这些信息传递呢?这就要求同步了。

同步传递消息也很简单,就是调用系统写好的回调接口

首先我们要知道,用户 点击,触摸 这些行为 也属于 通信的范畴—点击和触摸属于 信息源;
比如用户行为进行点击,那就实现 :

     new Button(mCotext).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new ImageView(mCotext).invalidate();
            }
        });

通过此招提示指定的ImageView:嘿!老兄,你该刷新了

又或者 当用户 进行触摸操作,我们需要实现放大缩小平移指定的区域:

  new RelativeLayout(mCotext).setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                //缩放
                v.setScaleX(1f);
                v.setScaleY(1f);
                //平移
                v.setTranslationX(1f);
                v.setTranslationY(1f); 
                v.setTranslationY(1f);
                //旋转
                v.setRotation(2f);
                v.setRotationX(2f);
                v.setRotationY(2f);

                v.invalidate();
                return true;
            }
        });

嘿,你看,当用户进行触摸操作,我们可以通过回调onTouchListenter来完成“触摸”这一操作

关于View重绘机制以及优化刷新UI的细节,不属于本文讨论范围。

Fragment

1. Fragment 与Activity通信

通过实例对象传递

同样的,在Fragment中 getActivity()可以获取到它相关联的 Activity实例,就可以轻松获取并且修改Activity的数据

2. Fragment 与 多个Fragment通信

首先,两个Fragment之间不可能直接通信(非正规因素除外),Google官方提出的解决办法是 通过相关联的Activity来完成两个Fragment的通信

只需要记住三步:

1. 定义一个接口:

在让Fragment关联Activity之前,可以在Fragment中定义一个接口,然后让宿主Activity来实现这个接口。接着,在Fragment中捕获这个接口,并且在onAttach()中 捕获Activity实例

//只需关注接口是如何定义的,以及onAttack中的实现
public class HeadlinesFragment extends ListFragment {
    //定义的接口引用
    OnHeadlineSelectedListener mCallback;

    // 自定义回调接口,宿主Activity必须要实现它
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        // 在这里只是为了确保Activity实现了我们定义的接口,如果没有实现,则抛出异常
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }

    ...
}

一旦Activity通过OnHeadlineSelectedListener 的实例mCallBack回调 onArticleSelected(),Fragment就可以传递信息 给Activity了

例如 下面是 ListFragment的一个回调方法,当用户点击了list 中的item,这个Fragment就会通过回调接口向宿主Activity传递事件

 @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // 向Activity传递事件信息
        mCallback.onArticleSelected(position);
    }

2. 在宿主Activity实现这个接口

怎么实现?很简单,参考下面代码:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // 用户从从 HeadlinesFragment选中了一个标题
        //响应用户的操作,做一些业务逻辑
    }
}

3. 向其他Fragment传递信息 (完成通信)

宿主Activity可以通过findFragmentById()向指定的Fragment传递信息,宿主Activity可以直接获取Fragment实例,回调Fragment的公有方法

例如:

宿主Activity 包含了一个Listfragment用来展示条目信息,当每个条目被点击的时候,我们希望ListFragment向另外一个DetailsFragment传递一个信息用来 展示不同的细节

 public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

     public void onArticleSelected(int position) {
        // 用户在 HeadlinesFragment中选中了一个item 

        //在activity中添加新的fragment
        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // If article 对象 可以复用, 我们就不需要创建两遍了

            // 回调articleFrag 更新
            articleFrag.updateArticleView(position);

        } else {
            // 创建 Fragment 并为其添加一个参数,用来指定应显示的文章
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);

            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // 将 fragment_container View 时中的内容替换为此 Fragment ,
            // 然后将该事务添加到返回堆栈,以便用户可以向后回滚
            transaction.replace(R.id.fragment_container, newFragment);
            int setTransition=TRANSIT_FRAGMENT_OPEN;
            transaction.setTransition(setTransition);
            transaction.addToBackStack(null);

            // 执行事务
            transaction.commit();
        }
    }
}

下面我写了一个实例来供大家理解:

各个类的联系图:

技术分享

效果如下:

技术分享

Service

Service 与Activity通信

主要是如何获得Service实例的问题
总结来说两步:

  1. 在Service定义内部类,继承Binder,封装Service作为内部类的属性,并且在onBind方法中返回内部类的实例对象
  2. 在Activity中实现ServiceConnection ,获取到Binder对象,再通过Binder获取Service
public class LocalService extends Service {
    // 传递给客户端的Binder
    private final IBinder mBinder = new LocalBinder();
    //构造Random对象
    private final Random mGenerator = new Random();

    /**
     * 这个类提供给客户端  ,因为Service总是运行在同一个进程中的
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            // 当客户端回调的时候,返回LoacalService实例
            return LocalService.this;
        }
    }

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

    /**交给客户端回调的方法 */
    public int getRandomNumber() {
      return mGenerator.nextInt(100);
    }
}
public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 绑定 LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 解绑 service
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /**button已经通过 android:onClick (attribute) 设置此方法响应用户click*/
    public void onButtonClick(View v) {
        if (mBound) {
            // 回调 LocalService的方法.
            //因为在主线程中刷新UI,可能会造成线程阻塞,这里只是为了测试
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /**定义通过bindService 回调的Binder */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
           //先通过Binder获得Service的内部类 LoacalBinder
            LocalBinder binder = (LocalBinder) service;
             // 现在可以获得service对象了
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}

除了这种回调的方式外

还有一种方式 是在Service中 发送广播,

比如 在Service中 开启了一个子线程执行任务,就在子线程的run()方法中去sendBroadcast(intent);
数据用Intent封装,传递形式用广播

AIDL完成进程间通信


关于进程和线程的细节改天详细说明,我们首先了解一下进程和线程的概念:

当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux
进程。默认情况下,同一应用的所有组件在相同的进程和线程(称为“主”线程)中运行。
如果某个应用组件启动且该应用已存在进程(因为存在该应用的其他组件),则该组件会在此进程内启动并使用相同的执行线程。
但是,我们也可以安排应用中的其他组件在单独的进程中运行,并为任何进程创建额外的线程。

各类组件元素的清单文件条目—:activity,servicer,eceiver 和 provider均支持 android:process 属性,此属性可以指定该组件应在哪个进程运行。我们可以设置此属性,使每个组件均在各自的进程中运行,或者使一些组件共享一个进程,而其他组件则不共享。 此外,我们还可以设置 android:process,使不同应用的组件在相同的进程中运行

以及了解一下 进程间通信的概念

Android 利用远程过程调用 (RPC) 提供了一种进程间通信 (IPC) 机制,通过这种机制,由 Activity
或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。
然后,返回值将沿相反方向传输回来。 Android 提供了执行这些 IPC 事务所需的全部代码,因此我们只需集中精力定义和实现 RPC
编程接口即可。

要执行 IPC,必须使用 bindService() 将应用绑定到服务上。

具体实现 可以 参考这个实例 和文末给出的官方文档

线程间通信

Handler 和AsyncTask都是用来完成子线程和主线程即UI线程通信的

都可以解决主线程 处理耗时操作,造成界面卡顿或者程序无响应ANR异常 这一类问题

Handler 是 一种机制【Handler+Message+Looper】,所有的数据通过Message携带,,所有的执行顺序按照队列的形式执行,Looper用来轮询判断消息队列,Handler用来接收和发送Message

AsyncTask 是一个单独的类,设计之初的目的只是为了 异步方式完成耗时操作的,顺便可以通知主线程刷新Ui,AsyncTask的内部机制则是维护了一个线程池,提升性能。

在这里提供另一种优雅的做法完成线程间的通信:

扩展 IntentService 类

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务值得一试。
但如需同时处理多个启动请求,则更适合使用该基类Service。

IntentService 执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样我们就永远不必担心多线程问题。
  • 在处理完所有启动请求后停止服务,因此我们不必调用 stopSelf()。
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。
    综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,我们还需要为服务提供小型构造函数。)

以下是 IntentService 的实现示例:

public class HelloIntentService extends IntentService {

  /**
   * 必须有构造函数 必须调用父 IntentService(String)带有name的构造函数来执行工作线程
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * IntentService 调用默认的工作线程启动服务
   * 当此方法结束,, IntentService 服务结束
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // 通常在这里会执行一些操作,比如下载文件
      //在这里只是sleep 5 s
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

看吧,我们只需要一个构造函数和一个 onHandleIntent() 实现即可。

对于Service 当然也有基础一点的做法,来完成多线程的操作,只不过代码量更多了:

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler 接收来自主线程的Message
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
         //执行任务,比如下载什么的,这里只是 让线程sleep 
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // 手动停止服务,来处理下一个线程
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    //启动线程.  注意我们在主线程中创建了一些子线程, 这些线程都没有加锁同步. 这些现场都是后台线程,所以不会阻塞UI线程
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Handler开始轮询遍历了
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // 每一次请求,都会通过handler发送Message
      // startID只是为了让我们知道正在进行的是哪一个线程,以便于我们停止服务
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // 不提供 binding, 所以返回空
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

很遗憾未完成的部分

  1. 多个App间的通信
  2. 使用大型开源框架完成组件通信,EventBus,otto
  3. 网络通信基础篇:Google 课程–AnsycTask+HttpClient
  4. 网络通信提高篇:开源框架Ansyc-Httpclient,okttp,Retrofit

本文参考并翻译

  1. Google 课程 Communicating with Other Fragments
  2. Google 解释 AIDL进程间通信
  3. Google 解释 Handler
  4. Google 解释 AsyncTask
  5. Google BroadcastReceiver API
  6. Google 课程 Interacting with Other Apps
  7. Google 解释 contentprovider
  8. Google BroadcastReceiver 课程
  9. Google Service 课程
  10. Google 解释 进程和线程

详解 Android 通信【史上最全】

标签:

原文地址:http://blog.csdn.net/chivalrousman/article/details/51623122

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!