Android Binder进程间通信工具AIDL使用示例深入分析

目录
  • 前言
  • AIDL
  • AIDL示例
    • 客户端
    • 运行日志
  • AIDL通信过程分析
    • bindService流程分析

前言

众所周知,Android进程间通信采用的是Binder机制。Binder是Android系统 独有的进程间通信方式,它是采用mmp函数将进程的用户空间与内核空间的一块内存区域进行映射,免去了一次数据拷贝,相比Linux上的传统IPC具有高效、安全的优点。本文结合AIDL与bindService函数,在Android体系的应用层和Framework层,对Binder通信进行深入剖析,以加深对Binder的了解。

AIDL

AIDL是Android接口描述语言,它也是一个工具,能帮助我们自动生成进程间通信的代码,省去了很多工作。既然它是一个工具,其实也不是必需的。笔者结合AIDL生成的代码进行剖析,分析它生成的代码是如何进程IPC通信的。

AIDL示例

服务端

创建PersonController.aidl文件:

在src目录包名com.devnn.libservice上右键点击创建AIDL文件并全名为PersonController,就会在aidl目录下创建同名的aidl文件。

// PersonController.aidl
package com.devnn.libservice;
import com.devnn.libservice.Person;
// Declare any non-default types here with import statements
interface PersonController {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    List<Person> getPersons();
    void addPerson(inout Person person);
}

形参上的inout修饰表示数据可以发送到服务端进程,而且服务端进程对数据的修改,也会同步到客户端进程。还有另外两个方式in和out,表示单向的传输。当使用in时,服务端对person对象的修改,客户端是无法感知的。当使用out时,客户端发送过去的字段是空的,返回的数据是服务端的。

创建Person.aidl文件:

// Person.aidl
package com.devnn.libservice;
// Declare any non-default types here with import statements
parcelable Person;

创建Person.java文件,Person类需要实现Parcelable接口:

package com.devnn.libservice
import android.os.Parcel
import android.os.Parcelable
class Person(var name: String?, var age: Int) : Parcelable {
    constructor(parcel: Parcel) : this(parcel.readString(), parcel.readInt()) {
    }
 	/**
     * 字段写入顺序和读取顺序要保持一致
     */
    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeInt(age)
    }
	/**
     * readFromParcel不是必需的,在aidl文件中函数参数类型是inout时需要。
     */
    fun readFromParcel(parcel: Parcel) {
        name = parcel.readString()
        age = parcel.readInt()
    }
	/**
     * 默认即可
     */
    override fun describeContents(): Int {
        return 0
    }
    companion object CREATOR : Parcelable.Creator<Person> {
        override fun createFromParcel(parcel: Parcel): Person {
            return Person(parcel)
        }
        override fun newArray(size: Int): Array<Person?> {
            return arrayOfNulls(size)
        }
    }
}

创建MyService类继承Service类:

package com.devnn.libservice
import android.app.Application
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import android.os.Build
import android.util.Log
import java.util.ArrayList
class MyService : Service() {
    override fun onCreate() {
        super.onCreate()
        Log.d("MyService", "onCreate")
		/**
         * 为了证明是在新进程中运行,将进程名打印出来
         * 在android 33设备上运行的。
         */
        if (Build.VERSION.SDK_INT >= 28) {
            val processName = Application.getProcessName()
            Log.d("MyService", "processName=$processName")
        }
    }
    override fun onBind(intent: Intent): IBinder? {
        Log.d("MyService", "onBind")
        return binder
    }
	private val binder: Binder = object : PersonController.Stub() {
          override fun getPersons(): List<Person> {
              Log.d("MyService", "getPersons")
              val list: MutableList<Person> = ArrayList()
              list.add(Person("张三", 20))
              list.add(Person("李四", 21))
              return list
          }
          override fun addPerson(person: Person) {
              Log.d("MyService", "addPerson")
              Log.d("MyService", "name=${person.name},age=${person.age}")
            }
        }
}

为了让MyService在独立进程运行,在Manifest声明时需要注明是新进程:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.devnn.libservice">
    <application>
        <service
            android:name=".MyService"
            android:process="com.devnn.libservice.MyService"></service>
    </application>
</manifest>

android:process=“com.devnn.libservice.MyService” 表示是独立进程,用冒号开头就示是子进程或叫私有进程,不用冒号开头表示是独立进程。注意进程名中间不能用冒号,比如这种就不行:com.devnn.xxx:MyService。

以上代码是在单独的libservice module中编写的,结构如下:

客户端

appmodule中创建客户端代码,命名为ClientActivity,只有三个按钮,id分别btn1btn2btn3,功能分别对应bindServicegetPersonsaddPersons,代码如下:

package com.devnn.demo
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.devnn.libservice.Person
import com.devnn.libservice.PersonController
import com.devnn.demo.databinding.ActivityClientBinding
class ClientActivity : AppCompatActivity() {
    private val binding by lazy {
        ActivityClientBinding.inflate(this.layoutInflater)
    }
    private lateinit var personController: PersonController
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        //bindService
        binding.btn1.setOnClickListener {
            val intent = Intent().apply {
                component =
                    ComponentName(this@ClientActivity.packageName, "com.devnn.libservice.MyService")
            }
            bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
        }
        //getPersons
        binding.btn2.setOnClickListener {
            personController?.let {
                val list = it.persons;
                list?.map {
                    Log.i("ClientActivity", "person:name=${it.name},age=${it.age}")
                }
            }
        }
        //addPerson
        binding.btn3.setOnClickListener {
            personController?.let {
                val person = Person("王五", 22)
                it.addPerson(person)
            }
        }
    }
    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.i("ClientActivity", "onServiceConnected")
            personController = PersonController.Stub.asInterface(service)
        }
        override fun onServiceDisconnected(name: ComponentName?) {
            Log.i("ClientActivity", "onServiceDisconnected")
        }
    }
}

运行界面如下:

客户端与服务端进程的aidl文件要保持一致,可以将服务端的aidl拷贝到客户端。如果客户端在单独module,并且依赖了service所在的module,也可以不用拷贝aidl。

运行日志

运行后,点击bindService按钮输出日志如下:

在Logcat查看进程列表也出现了2个:

点击getPersons按钮,输出日志如下:

可以看到,客户端进程可以正常获取服务端进程的数据。

点击addPerson按钮,输出日志如下:

可以看到,服务端进程可以正常接收客户端进程的发来的数据。

以上是AIDL使用的一个例子,下面分析AIDI通信的过程。

AIDL通信过程分析

项目build后就会自动在build目录下生成对应的Java代码:

PersonController.java代码如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 */
package com.devnn.libservice;
// Declare any non-default types here with import statements
public interface PersonController extends android.os.IInterface
{
  /** Default implementation for PersonController. */
  public static class Default implements com.devnn.libservice.PersonController
  {
    /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    @Override public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException
    {
      return null;
    }
    @Override public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException
    {
    }
    @Override
    public android.os.IBinder asBinder() {
      return null;
    }
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.devnn.libservice.PersonController
  {
    private static final java.lang.String DESCRIPTOR = "com.devnn.libservice.PersonController";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.devnn.libservice.PersonController interface,
     * generating a proxy if needed.
     */
    public static com.devnn.libservice.PersonController asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.devnn.libservice.PersonController))) {
        return ((com.devnn.libservice.PersonController)iin);
      }
      return new com.devnn.libservice.PersonController.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_getPersons:
        {
          data.enforceInterface(descriptor);
          java.util.List<com.devnn.libservice.Person> _result = this.getPersons();
          reply.writeNoException();
          reply.writeTypedList(_result);
          return true;
        }
        case TRANSACTION_addPerson:
        {
          data.enforceInterface(descriptor);
          com.devnn.libservice.Person _arg0;
          if ((0!=data.readInt())) {
            _arg0 = com.devnn.libservice.Person.CREATOR.createFromParcel(data);
          }
          else {
            _arg0 = null;
          }
          this.addPerson(_arg0);
          reply.writeNoException();
          if ((_arg0!=null)) {
            reply.writeInt(1);
            _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
          }
          else {
            reply.writeInt(0);
          }
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.devnn.libservice.PersonController
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      /**
           * Demonstrates some basic types that you can use as parameters
           * and return values in AIDL.
           */
      @Override public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.util.List<com.devnn.libservice.Person> _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          boolean _status = mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().getPersons();
          }
          _reply.readException();
          _result = _reply.createTypedArrayList(com.devnn.libservice.Person.CREATOR);
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      @Override public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          if ((person!=null)) {
            _data.writeInt(1);
            person.writeToParcel(_data, 0);
          }
          else {
            _data.writeInt(0);
          }
          boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            getDefaultImpl().addPerson(person);
            return;
          }
          _reply.readException();
          if ((0!=_reply.readInt())) {
            person.readFromParcel(_reply);
          }
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
      }
      public static com.devnn.libservice.PersonController sDefaultImpl;
    }
    static final int TRANSACTION_getPersons = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    public static boolean setDefaultImpl(com.devnn.libservice.PersonController impl) {
      // Only one user of this interface can use this function
      // at a time. This is a heuristic to detect if two different
      // users in the same process use this function.
      if (Stub.Proxy.sDefaultImpl != null) {
        throw new IllegalStateException("setDefaultImpl() called twice");
      }
      if (impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.devnn.libservice.PersonController getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  /**
       * Demonstrates some basic types that you can use as parameters
       * and return values in AIDL.
       */
  public java.util.List<com.devnn.libservice.Person> getPersons() throws android.os.RemoteException;
  public void addPerson(com.devnn.libservice.Person person) throws android.os.RemoteException;
}

仔细观察会发现,这个接口有个Stub静态抽象内部类,里面有一个asInterface方法、onTransact方法,Proxy静态内部类是我们要关注的。

客户端进程调用 PersonController.Stub.asInterface(service)这个代码实际上就返回了Proxy这个代理对象,当调用getPersons和addPerson方法时,也相当于调用Proxy代理类里的对应的方法。

addPerson方法举例,这个方法实际上把JavaBean对象转成了Pacel类型的对象,然后调用IBinder的transact方法开始进程间通信。

addPerson方法进程间通信调用的方法如下:

mRemote.transact(Stub.TRANSACTION_getPersons, _data, _reply, 0);

mRemote就是通过binderService获取到的服务端进程的IBinder对象。

第一个参数Stub.TRANSACTION_getPersons表示要调用的方法ID。

第二个参数_data就是要发送给服务端的数据

第三个参数_reply就是服务端返给客户端的数据。

第四个参数0表示是同步的,需要等待服务端返回数据,1表示异步不需要等待服务端返回数据。

整体来说Proxy这个类就是给客户端使用的代码。

Stub这个类是给服务端使用的代码。

注意:谁主动发起通信谁就是客户端,接收通信是服务端。有时候一个进程同时充当客户端和服务端,比如app与ams的通信,app既充当客户端也充当服务端。

Stub类的onTransact方法就是服务端被调用时的回调函数。

boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

第一个参数code表示调用的方法ID

第二个参数data表示调用方发来的数据

第三个参数reply表示服务端需要回传的数据

第四个参数flags表示同步还是异步。

注意这个onTransact方法是在binder服务端的binder线程池被调用的,也就是在子线程调用的。所以上面的MyService里的getPersonsaddPersons方法是在子线程里运行的,如果需要与主线程通信还得使用Handler。

经过以上分析,可以发现进程间通信实际上是拿到对方的IBinder引用后,通过调用IBinder的transact方法发送和接收Parcel类型数据进行通信。AIDL实际上是简化了Binder调用的过程,帮助我们自动生成了通信代码。我们也可以根据需要,自己编写相关代码通信。

那么客户端调用bindServcie方法是如何拿到服务端进程的IBinder对象呢?这部分涉及到android framewok层,这里把它大致流程分析一下。

bindService流程分析

调用bindService方法时,实际是调用ContextWrapper的bindService方法,Activity是继承于ContextWrapper。下面基于Android 10的源码,用流程图表示这个调用链。

整体来说,客户端进程需要与服务端进程通信,先要获取服务端的binder对象,这中间需要经过AMS(Activity Manager Service)服务作为中介。先向AMS发起请求(bindService,携带ServiceConnection对象),AMS再跟服务端进程通信,服务端进程把binder给到AMS,AMS再通过ServiceConnection的onServiceConnected回调把binder发送给客户端进程。客户端获取到binder就可以调用transact方法发送数据。

OK,关于AIDL和Binder进程间通信就介绍到这了。

到此这篇关于Android Binder进程间通信工具AIDL使用示例深入分析的文章就介绍到这了,更多相关Android Binder AIDL内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-11-13

浅谈Android IPC机制之Binder的工作机制

进程和线程的关系 按照操作系统中的描述,线程是CPU调度的最小单位,同时线程也是一种有限的系统资源.而进程一般是指一个执行单元,在pc端或者移动端上是指一个程序或者一个应用.一个进程中可以包含一个或者是多个线程.所以他们的关系应该是包含和被包含的关系. 跨进程的种类 在Android中跨进程通信的方式有很多种,Bundle,文件共享,AIDL,Messenger,ContentProvider,Socket,这些都能实现进程间之间的通信,当然,虽然都能够实现进程间通信,但是他们之间的实现原理或者

Android中的binder机制详解

前言 Binder做为Android中核心机制,对于理解Android系统是必不可少的,关于binder的文章也有很多,但是每次看总感觉看的不是很懂,到底什么才是binder机制?为什么要使用binder机制?binder机制又是怎样运行的呢?这些问题只是了解binder机制是不够的,需要从Android的整体系统出发来分析,在我找了很多资料后,真正的弄懂了binder机制,相信看完这篇文章大家也可以弄懂binder机制. 1.Binder是什么? 要理解binder,先要知道IPC,Inter

Android10 Binder原理概述深入解析

目录 IPC工具介绍 Pipe Sign message queue shared memory Socket AIDL HIDL IPC工具介绍 Binder作为Android 众多的IPC通讯手段之一,在Framework的数据传输中起到极为关键的作用.为什么Google需要重新创造Binder这么一个IPC工具,使用linux默认提供的Pipe.Socket.共享内存.信号.消息队列等IPC工具不行吗? 答案是 这些传统的linux IPC工具有一部分android也在使用,只是在某些场合

Android Binder的原理与使用

前言 Binder是安卓中实现IPC(进程间通信的)常用手段,四大组件之间的跨进程通信也是利用Binder实现的,Binder是学习四大组件工作原理的的一个重要基础. 好多文章都会深入C代码去介绍Binder的工作流程,没点水平真的难以理解,本文不会太深入底层去剖析原理,尽可能较为简单的让大家了解Binder是怎么工作的. Binder的使用 在介绍Binder原理之前,我们先来看看在安卓中怎么使用Binder来进程间通信. 在使用之前我们先来介绍Binder的几个方法: public fina

Android 图文详解Binder进程通信底层原理

之前了解到进程与多进程,涉及多进程不可避免的遇到了进程间通信,说到进程间通信,Binder 成了一道绕不过的坎.接下来咱们逐一了解.

Android&nbsp;Binder&nbsp;通信原理图文详解

目录 前言 1. Binder的作用 2. 进程与Binder驱动如何通信 3. ServiceManager进程的作用 Binder Client.Binder Server.ServiceManager关系 ServiceManager注册进Binder 4. 进程添加服务到ServiceManager的流程 其它进程找到SM 添加服务到ServiceManager BBinder作用 5. 进程从ServiceManager获取服务的流程 其它进程找到SM 从ServiceManager获

Android中关于Binder常见面试问题小结

目录 1.简单介绍下binder 2.Binder的定向制导,如何找到目标Binder,唤起进程或者线程? 3.Binder中的红黑树,为什么会有两棵binder_ref红黑树 4.Binder一次拷贝原理 5.Binder传输数据的大小限制? 6.系统服务与bindService等启动的服务的区别 7.Binder多线程 8.Android APP进程天生支持Binder通信的原理是什么? 9.同一个线程的请求必定是顺序执行,即使是异步请求(oneway) 1.简单介绍下binder bind

Android中Binder&nbsp;IPC机制介绍

目录 前言 一.Binder是什么? 二.为什么要使用Binder 三.IPC机制原理 传统IPC机制如何实现跨进程通信 Binder IPC机制原理 小结 前言 记得刚开始做Andorid那会,面试时最怕被问到Binder,就感觉战战兢兢不知道从什么地方说起,导致后来一直有一种恐惧感.当然现在没有这种感觉了,但是这块知识点一直模模糊糊的,最近在学Andorid framework课程,借此机会简单总结下其中Binder相关知识点. 一.Binder是什么? Binder是Android中一种进

Android中Handler消息传递机制

Handler 是用来干什么的? 1)执行计划任务,可以在预定的时间执行某些任务,可以模拟定时器 2)线程间通信.在Android的应用启动时,会创建一个主线程,主线程会创建一个消息队列来处理各种消息.当你创建子线程时,你可以在你的子线程中拿到父线程中创建的Handler 对象,就可以通过该对象向父线程的消息队列发送消息了.由于Android要求在UI线程中更新界面,因此,可以通过该方法在其它线程中更新界面. 出于性能优化考虑,Android的UI操作并不是线程安全的,这意味着如果有多个线程并发

Android中ListView使用示例介绍

目录 一.具体思路 1.创建Listview控件 3.写入 4.读取 5.创建对象,构造器,GETSET方法 二.具体实施 1.适配器 2.数据库 3.对象 4.等等等等 三.案例分享 activity_main.xml listitem.xml MyDatabaseHelper.java MainActivity.java Employee.java 简单ListView实例 数据库读取数据存入ListView 一.具体思路 1.创建Listview控件 2.创建子布局 创建数据库 主方法调用

Android中的xml解析介绍

目录 XML - 元素 XML - 解析 例子 总结 XML 代表可扩展标记语言.XML 是一种非常流行的格式,通常用于在 Internet 上共享数据.本章说明如何解析 XML 文件并从中提取必要的信息. Android 提供了三种类型的 XML 解析器,它们是DOM.SAX 和 XMLPullParser.其中android推荐XMLPullParser,因为它高效且易于使用.所以我们将使用 XMLPullParser 来解析 XML. 第一步是确定您感兴趣的 XML 数据中的字段.例如.在

Android中Activity组件实例介绍

目录 Activity 概述 启动 Activity 的两种情况 关闭 Activity 总结 Activity 概述 在 Android 应用中,提供了 4 大基本组件,分别是 Activity.Service.BroadcastReceiver 和 ContentProvider.而 Activity 是 Android 应用最常见的组件之一.Activity 的中文意思是活动.在 Android 中,Activity 代表手机或者平板电脑中的一屏,它提供了和用户交互的可视化界面.在一个 A

详细介绍Android中回调函数机制

提示:在阅读本文章之前,请确保您对Touch事件的分发机制有一定的了解 在Android的学习过程中经常会听到或者见到"回调"这个词,那么什么是回调呢?所谓的回调函数就是:在A类中定义了一个方法,这个方法中用到了一个接口和该接口中的抽象方法,但是抽象方法没有具体的实现,需要B类去实现,B类实现该方法后,它本身不会去调用该方法,而是传递给A类,供A类去调用,这种机制就称为回调. 下面我们拿具体的Button的点击事件进行模拟分析: 首先,在View类中我们能找到setOnClickLis

Android中Binder详细学习心得

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列.该系列引用了<Android开发艺术探索>以及<深入理解Android 卷Ⅰ,Ⅱ,Ⅲ>中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师. 前言 上一次还不如不说去面试了呢,估计是挂了,数据结构与算

Android 中的注解详细介绍

注解是我们经常接触的技术,Java有注解,Android也有注解,本文将试图介绍Android中的注解,以及ButterKnife和Otto这些基于注解的库的一些工作原理. 归纳而言,Android中的注解大概有以下好处 提高我们的开发效率 更早的发现程序的问题或者错误 更好的增加代码的描述能力 更加利于我们的一些规范约束 提供解决问题的更优解 准备工作 默认情况下,Android中的注解包并没有包括在framework中,它独立成一个单独的包,通常我们需要引入这个包. dependencies

Android的Touch事件处理机制介绍

Android的Touch事件处理机制比较复杂,特别是在考虑了多点触摸以及事件拦截之后. Android的Touch事件处理分3个层面:Activity层,ViewGroup层,View层. 首先说一下Touch事件处理的几条基本规则. 如果在某个层级没有处理ACTION_DOWN事件,那么该层就再也收不到后续的Touch事件了直到下一次ACTION_DOWN事件. 说明: a.某个层级没有处理某个事件指的是它以及它的子View都没有处理该事件. b.这条规则不适用于Activity层(它是顶层

Android中的WebView详细介绍

Android中WebView的详细解释: 1. 概念: WebView(网络视图)能加载显示网页,可以将其视为一个浏览器.它使用了WebKit渲染引擎加载显示网页. 2. 使用方法: (1).实例化WebView组件: A.在Activity中实例化WebView组件.eg: 复制代码 代码如下: WebView webView = new WebView(this); B.调用WebView的loadUrl()方法,设置WevView要显示的网页.eg: 复制代码 代码如下: 互联网用:we