Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

AIDL理解解析

$
0
0

前言

为了防止遗忘这些知识点,写一篇博客加深自己的理解,方便忘记后再重新学习。

概述

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。
AIDL是用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。线程间通讯有多种方式,下面简单介绍下不同之间区别。

多种线程间通讯方式的不同:详情参考此博客

  • Bundle:四大组件间的进程间通信方式,简单易用,但传输的数据类型受限。
  • 文件共享: 不适合高并发场景,并且无法做到进程间的及时通信。
  • Messenger: 数据通过Message传输,只能传输Bundle支持的类型
  • ContentProvider:android 系统提供的。简单易用。但使用受限,只能根据特定规则访问数据。
  • AIDL:功能强大,支持实时通信,但使用稍微复杂。
  • Socket:网络数据交换的常用方式。不推荐使用。

关于AIDL语法

aidl的语法基本和java一样,仅有几点不同之处:

  • 文件类型:AIDL文件的后缀是 .aidl,而不是 .java。
  • 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下。(列:编写了两个文件,一个叫做 person.java ,另一个叫做PersonManager.aidl,它们都在 com.mumu.aidl包下 ,在 .aidl 文件里使用person对象我们就必须在 .aidl 文件里面写上 import com.mumu.aidl.person; )
  • 默认支持的数据类型包括:
    • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。(实测short不支持)
    • String 类型。
    • CharSequence类型。
    • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。
    • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。
    • parcelable序列化的数据类型:parcelable所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口;
  • 定向tag:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。注意,不要全都用 inout ,工程大了系统的开销就会大很多,排列整理参数的开销很大。详细Tag使用方式参考此博客

dome实例(使用的studio工具)

简单实现基本数据类型(如图一个远程相加运算)

这里写图片描述

服务端
  • 创建一个新的项目作为服务端
    1. 创建服务器端的aidl包
      这里写图片描述
    2. 创建的aidl包下aidl文件
    3. 实现aidl文件接口
package ready.mumu.service;

interface MyAidl {
     int addnum(int num1 , int num2);
}
  • 在java包下创建一个service并实现aidl接口
public class Myservice extends Service {

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

    private final MyAidl.Stub iBinder  = new MyAidl.Stub(){

        @Override
        public int addnum(int num1, int num2) throws RemoteException {
            Log.v("MUMU","收到输入的远程请求,收到的值是num1:"+num1+"  num2:"+num2);
            return num1 + num2;
        }
}
  • 注册表注册service
<service android:name=".Myservice" android:process=":remote">
            <intent-filter>
                <action android:name="ready.mumu.service.MyAidl"/>
            </intent-filter>
        </service>

这里说一下Android声明文件中的android:process属性可以为任意组件包括应用指定进程,如果我们需要让一个服务在一个远端进程中运行(而不是标准的它所在的apk的进程中运行),我们可以在声明文件中这个服务的标签中通过android:process属性为其指定一个进程。
“:remote”又是什么意思呢?“remote”不是关键,这个完全可以自己随意取名字,“:”冒号才是关键。
进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。而进程名不以“:”开头的进程属于全局进程,其他应用可以通过某些方式和它跑在同一个进程中。

客户端
  • 将服务端对应的aidl拷贝到客户端,要求aidl完全一致,所在的包名也完全一致
  • 创建客户端的activity运行界面,并实现按钮点击事件(xml布局文件就不写了,简单的三个输入框一个按钮)
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initUI();

        //软件启动就绑定服务
        bindService();
    }

    private void initUI() {
        et_num1 = (EditText) findViewById(R.id.et_num1);
        et_num2 = (EditText) findViewById(R.id.et_num2);
        et_res = (EditText) findViewById(R.id.et_res);
        bt_add = (Button) findViewById(R.id.bt_add);

        bt_add.setOnClickListener(this);
    }
  • 实现绑定服务方法
private void bindService() {
        //获取到服务端
        Intent intent = new Intent();
        //5.0之后必须显示intent启动 绑定服务 , ComponentName两个参数对应是服务包名和服务文件名(文件名必须是包名+文件名)
        intent.setComponent(new ComponentName("ready.mumu.service","ready.mumu.service.Myservice"));
        bindService(intent,conn, Context.BIND_AUTO_CREATE);
    }
  • 实现ServiceConnection(conn)绑定回调
MyAidl myaidl;

private ServiceConnection conn = new ServiceConnection() {

        //绑定上服务的时候执行
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //拿到远程的服务
            myaidl = MyAidl.Stub.asInterface(iBinder);
        }

        //当服务断开的时候执行
        @Override
        public void onServiceDisconnected(ComponentName componentName) {

            //回收资源
            myaidl = null;
        }
    };
  • 实现“远程计算”按钮onclick方法
@Override
    public void onClick(View view) {

        if(view == bt_add){
            int num1 = Integer.parseInt(et_num1.getText().toString());
            int num2 = Integer.parseInt(et_num2.getText().toString());

            try {
                //调用远程服务
                int res = myaidl.addnum(num1 , num2);
                et_res.setText(res+"");
            } catch (RemoteException e) {
                e.printStackTrace();
                et_res.setText("报错了");
            }
        }
  • 在activity销毁的onDestroy方法中解绑服务
@Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(conn);
    }

如此简单的小dome就完成了。

相对复杂的序列化实现dome(如图)

这里写图片描述
这里在上边dome的基础上又添加了一个“序列化调用”按钮,点击之后输入传入的自定义的序列化数据,下面的步骤是在上个dome基础之上添加的。

服务端

方便看结构,先来一张服务端的代码结构图
这里写图片描述

  • 创建一个java类myParcelable,定义数据类型、构造方法、get/set方法,实现Parcelable序列化(这个类的包名和aidl的包名要一致)
public class myParcelable implements Parcelable{

    String name;
    int age;
    String sex;


    //参数是一个Parcel,用它来存储与传输数据
    protected myParcelable(Parcel in) {
        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
        name = in.readString();
        age = in.readInt();
        sex = in.readString();
    }

    public static final Creator<myParcelable> CREATOR = new Creator<myParcelable>() {
        @Override
        public myParcelable createFromParcel(Parcel in) {
            return new myParcelable(in);
        }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //数据存储至Parcel
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(sex);
    }

    //方便数据清晰
    @Override
    public String toString() {
        return "myParcelable{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}
  • 在aidl包下创建一个aidl文件(myParcelable.aidl)用于定义parcelable对象(非默认支持数据类型必须通过AIDL文件定义才能被使用)。这个myParcelable.aidl和myParcelable.java的包名要一致,所以上个类创建时才说要与aidl包名一致。
// myParcelable.aidl
package ready.mumu.service;

    parcelable myParcelable;
  • 在原来的MyAidl.aidl中添加一个readText方法
// MyAidl.aidl
package ready.mumu.service;

//注意需要导入包
import  ready.mumu.service.myParcelable;

interface MyAidl {

     int addnum(int num1 , int num2);

     //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
     String readText(in myParcelable par);
}
客户端

结构附图
这里写图片描述

  • 将服务端的myParcelable.java和myParcelable.aidl拷贝过来,注意包名一致
  • 将服务端的MyAidl.ail复制到客户端,此文件要保持客户端和服务端一致。
  • 实现activity中“序列化调用”按钮onclick方法
if(view == bt_par){

            try {
                String msg = myaidl.readText(new myParcelable("张三",18,"男"));
                et_res.setText(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
                et_res.setText("序列化出错了");
            }

        }

如此,序列化数据的远程调用也就结束了。

结语

可能看起来会稍微混乱一点,实现一下就会发现其实aidl使用还是很简单的,最后留下dome地址,看dome可能会相对更容易理解一些。dome下载地址

作者:A189lin 发表于2016/11/7 19:53:44 原文链接
阅读:22 评论:0 查看评论

关于手机安全卫士开发详解

$
0
0

手机安全卫士

1  初始化界面的搭建

1.1  界面UI

界面的ui主要完成的是背景图片的显示,以及版本号的显示,其中版本号是需要动态获取显示的。

主要实现:由于布局的特点选择相对布局,在RelativeLayout中设置背景图片,用一个textview显示版本信息。

 

1.2  界面activity需要重写的方法

界面需要完成的功能是在初始化页面的同时完成与服务器上的版本比对,看是否有新的版本,如果存在应该提醒用户是否进行更新。在完成相应的逻辑后进入到程序的主界面。

根据这些功能,可以知道页面只需要重写oncreate()方法。

1.3  activity里重写的相应的方法

onCreate()方法:

需要完成于服务器上的版本比对,也就是需要获取服务器上的相关数据,由于Android不能在主线程里进行耗时的操作,所以开启子线程完成网络的请求。

第一步:在activity里定义一个CheckVersionTask实现runnable接口重写里面的run()方法来实现子线程的开启。

第二步:在oncreate()方法中new Thread(new CheckVersionTask()).start();开启子线程完成访问服务器更新版本的操作。

1.4  CheckVersionTask()方法的实现

在方法中访问服务器,比较是否有新的版本,如果有新的版本就使用handle机制给主线程发送消息让主线程完成更新ui提示用户更新,如果没有新的版本则让主线程完成跳转到下一界面的操作。

第一步:访问服务器

res资源文件夹下的values子文件夹下创建一个string.xml文件来保存访问的服务器的路径,这样做的好处是当服务器的地址发生变化时我们不需要在复杂的代码中去修改路径,只需要在这个配置文件里修改即可。

通过getResources().getString(R.string.resource);方法得到配置的路径,通过URL统一资源定位器包装路径,使它成为一个对应相应服务器的一个连接的对象,然后使用openconnction方法建立连接通过强制类型装换为http协议的连接方式(openconection支持有很多的连接协议,通过类型装换使他对应http的连接),得到连接后设置连接的时长,设置连接的方式,

(这里使用get方法,关于post方式及两种方式的区别在后面进行补充),然后得到程序的响应码,进行判断是否与服务器连接成功,如果连接成功则得到服务器返回的流数据,如果失败则返回给用户一个对应的错误码(也是通过handle机制交给主线程在界面上显示)。

第二步:处理服务器返回的数据

在成功连接得到服务器返回的数据后(json数据),由于数据的类型需要先把流转换为一个字符串,通过自定义的工具类的StreamTools.readStream(is);方法完成流的转换,在得到字符串后,服务器返回的是一个json的数据,对其进行解析得到相应的数据。通过解析得到的服务器上的最新的版本信息与本地的版本进行比较,如果不一致提醒更新。

补充一:关于流的转换

这里使用内存流进行转换,相关代码如下:

public class StreamTools {

public static String readStream(InputStream is) throws IOException{

ByteArrayOutputStream bos=new ByteArrayOutputStream();

int len=-1;

byte[] buffer=new byte[1024];

if((len=is.read(buffer))!=-1){

bos.write(buffer, 0, len);

}

is.close();

return bos.toString();

}

}

这里选择使用内存流是操作最方便效率最好的一种方式,关于流的读取是边读边取,定义的字节数组是每次搬运时的容器,字节数组的大小决定了每次搬运的多少,为了使不至于占用过多的内存空间,每次搬运时都不会直接把整个文件搬运过去,所以在拷贝一个文件时特别是一个大的文件时,一般都是读一个字节数组容量过去然后取出来,并不会全部放在内存中,由于整个原因字节数组也不会定义的特别大(会消耗内存空间),也就是说每次读都需要有一个容器去接收,容器可以是一个流对应的本地文件,也可以是一个stringbufferstringbuild甚至是一个集合(一般不会用,因为集合拼接的问题的存在)(这些容器是可以自动扩展的相应的区别与原理在后文进行具体分析)。关于优化的缓冲流具体的原理差不多,只是每次搬运的更多。

显然在这里转换的不会是一个大的文件,(请求的是一些版本的信息)而且最好是能一次性读取,所以这里不用前面说的方法读取(后面会介绍相应的各种流的读取),而是采用内存本身作为容器,先把流的数据通过字节数组一点点搬运到内存中,当搬运完时直接一次性取出(bos.tostring),这样效率会高很多,而且由于文件本身的大小不会占用过多的内存。

补充二:json的解析

这里得json数据是很简单的结构,只有一层的json对象,没有夹杂json数组,所以在解析时很简单清晰;

得到一个jsonobject对象绑定对应的要解析的字符串,然后通过getString(“key”)方法取得对应的信息。(关于json解析的详细不在这里说明)

解析的核心代码如下:

String result = StreamTools.readStream(is);

// 解析得到的json字符串

JSONObject json = new JSONObject(result);

String version = json.getString("version");

String description = json.getString("description");

downloadPath = json.getString("apkurl");

补充三:得到版本信息的方法

PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);

String version=packageInfo.versionName;

return version;

通过包管理器(Google为我们包装的框架)在介绍Android的结构时介绍。

第三步:通过handle机制给主线程发消息

通过message封装消息,通过handle发送。

Message msg = Message.obtain();取得message对象,也可以通过new 来创建,不过这里推荐使用前面的方法

msg.what = SUCCESS_UPDATE;标志是什么消息,方便handle进行处理,后面的是自定义的常量

msg.obj = description;发送的内容,object类型,可以接受任何内容

handler.sendMessage(msg);发送给主线程的handle处理

1.5  handle处理

关于handle机制的详细原理不在这里进行介绍,只介绍本程序的相应实现。

版本一致不更新,返回的各种错误码的,只需要在界面给一个toast提醒然后进入主界面,版本不一致提醒更新则需要先弹出一个对话框提醒用户,然后根据不同的点击,设置不同的逻辑判断,执行不同的方法。

第一步:页面的跳转,跳转到menu页面

(关于页面的跳转方式:分为显示意图和隐式意图)

这里使用显示意图(一般调用系统的一些activity时使用隐式意图,如后面使用的自动安装更新)

由于在很多地方都需要使用到这个页面的跳转,所以抽取成一个方法。创建意图,设置跳转的页面,开启意图的跳转,结束页面。

具体代码如下:

private void loadMainUi() {

Intent intent = new Intent(Activity_splash.this, Activity_menu.class);

startActivity(intent);

finish(); }

第二步:创建一个对话框提醒用户是否更新

通过AlertDialog.Builder builder = new Builder(context);方法来创建一个对话框,通过setXXX方法设置对话框的显示的信息。完成信息设置后设置对话框的两个点击事件:builder.setNegativeButtonbuilder.setPositiveButton

这两个方法里有2个参数,

Parameters:

text The text to display in the negative button

listener The DialogInterface.OnClickListener to use.

第一个参数text代表对话框点击按钮的名称,第二个参数是一个点击事件的监听器,注意是Dialoginterface包下的,最好加上包名。

第三步:两个点击事件的处理

builder.setNegativeButton(取消更新)中只需要调用方法进入menu界面即可,重点在builder.setPositiveButton(更新)的处理。

在确定更新时,需要完成两步,第一是用多线程断点下载下载服务器返回的指定路径里的apk安装包(在下载完成后完成自动更新),第二步,在界面上创建一个下载的进度条显示下载的状态。

1.6  多线程断点下载

本程序使用的是xutils开源框架完成多线程断点下载,关于多线程断点下载的源码实现与开源框架的包装设计的原理WHHHH

在使用开源框架前把相应的jar包导入到项目中,在使用时只需要创建对象,开启下载两步即可。

第一步:创建对象

HttpUtils httpUtils = new HttpUtils();

创建httpUtils对象,拿到这个对象去开启下载,在开启下载中有下载中,下载成功,下载完成三个回调事件,分别在3个事件里做对应的逻辑处理即可完成想要的交互与效果。

第二步:开启下载

在开启下载前(这里是用sd卡存储)先判断sd卡是否可用。

httpUtils.download(downloadPath,file.getAbsolutePath(),new RequestCallBack<File>() {});

3个参数分别是,下载的路径,下载完成后文件存放的路径,请求下载响应的事件,里面需要重写3个方法,分别是下载中,下载失败,下载完成;

在下载中显示下载的进度,在下载成功后通过隐式意图开启系统的自动更换apk的页面完成apk的替换。

1.7  apk的替换

Apk的替换实在多线程断点下载的onsuceess回调方法里完成的,方法的代码:

public void onSuccess(ResponseInfo<File> fileinfo) {

// 下载成功回调的方法

// 下载成功应该替换掉apk,这里使用的是隐式意图的方法来替换

pd.dismiss();//关闭下载进度条对话框

Intent intent = new Intent();

intent.setAction("android.intent.action.VIEW");

intent.addCategory("android.intent.category.DEFAULT");

intent.setDataAndType(Uri.fromFile(fileinfo.result),"application/vnd.android.package-archive");

// 如果activity已经运行到了task,再次跳转不会再运行这个activity

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

startActivity(intent);}

上面的代码中有两点特殊的地方,第一点是setdataandtype方法里的data信息是由下载成功返回的responseinfo信息里提供的,第二点是设置了不同的activitytask栈里运行模式(和activity4种开启模式有所不同,WHHHH)。

1.8  页面细节的处理

第一:初始化页面的显示时间(无论网络请求的快慢,都使页面显示一个固定的时长)

第二:无论网络连接是否成功以及下载的操作是否成功都应该在提示信息后进入主界面

2.  主页面menu页面的搭建

在完成初始化界面后,程序进入主界面菜单,在menu界面里显示各个功能模块的入口,显示一个比较好看绚丽的页面动画效果。

2.1  界面UI

界面的ui主要实现里标题框的布局以及各个模块入口的布局。其中标题框里面有一个旋转的图片做成动画效果,有一个标签可以在其中动态的显示文字,功能模块的入口布局使用网格布局,gridview。界面的ui主要是这3个部分完成。

2.2  图片旋转动画的实现(属性动画)

使用ObjectAnimator类完成。分为3步:创建实例,设置动画的显示信息,开启动画。

第一步:创建动画对象实例

ObjectAnimator oa = ObjectAnimator.ofFloat(iv_menu_heima, "rotationY",

45, 90, 135, 180, 225, 270, 315);

3个参数分别为,关联的控件(也就是操作的控件),旋转的中心轴,后面一串为一个可变参数的数组参数,记录的是每次旋转的角度。

第二步:设置动画的信息

主要是:设置动画执行一次的时长:oa.setDuration(3000);

动画重复的次数:oa.setRepeatCount(ObjectAnimator.INFINITE);

动画旋转的模式:oa.setRepeatMode(ObjectAnimator.RESTART);

第三步:开启动画

使用start方法开启。

2.3  动态文本的显示

动态文本的实现使用的是自定义的textview,它继承textview重写了里面的相应的一些方法,以欺骗Android系统的一些处理,从而达到想要的动态效果

第一步:页面的参数

除了宽高距离颜色这些普遍的信息以外,主要在页面中设置了两个特殊的属性,一个是focusableInTouchMode,设置这个属性为true,意思是页面获取焦点的事件是开启的,当页面获得焦点后就可以动态显示文本,然后除了设置文本一行显示以外,还要设置ellipsize属性,这样多余的文字不会以省略号的形式显示。

然而即使设置了这些属性还是不能动态显示,因为textview不像button那样通过点击事件可以获取焦点(当然这里可以设置属性使它是可以点击的),显然根据需求,是一直的动态显示,也就是textview是一直获取焦点的,所以需要重写方法欺骗系统这个textview是一直获取焦点的。

第二步:重写方法,欺骗系统页面一直在获取焦点的状态。

这个通过自定义的textview继承textview重写里面的isfocused方法实现。把该方法的返回值始终返回true,注意在重写时重写父类的所有构造方法.

2.4  gridview的布局

Gridviewlistview类似,不同的地方是可以通过设置显示每行显示的模块数量,(通过numcolumns设置列的信息)通过适配器和inflator打气筒完成布局的显示。

第一步:适配器的设置

通过设置适配器显示每个条目,定义一个自己的适配器继承baseAdapter实现里面的4个方法,这里我们需要关注的是其中的两个方法:getviewgetcount

Getcount方法是返回页面总共需要显示的条目,一般与数组集合一起使用。

Getview方法返回每次打气好的view视图,返回视图,补充:无论是当重新new一个adapter适配器还是通过调用notifyDataSetChanged();方法刷新视图,都是重新执行一次getview方法,不同的是通过notifyDataSetChanged();方法刷新记录了上一次视图显示的信息,这样可以记录到用户正在操作的数据的位置。

对于类似的这些布局都是采用了使用适配器然后定义布局文件然后打气的一类操作,不同的是界面的布局不同,大气文件里的处理的逻辑不同。

3.  设置界面setting页面的搭建

在主界面中除了各功能模块的入口还有设置页面的实现,主要设置完成软件是否在启动时自动检测更新等功能,为方便起见应用程序都采用sharedperfrence来存储数据,关于Android中的几种存储操作在后面总结归纳(文件存储,首选项,sqilte数据库,内容提供者,sd卡存储等方式)

在页面布局中为了使界面交互性更好代码复用性更优使用了自定义的imageview以及状态选择器selector

3.1  自定义的imageview

实现点击切换图片的功能,继承imageview在里面增加改变图片的方法,当每次点击事件发生时调用该方法,通过该方法实现图片的切换。点击事件的触发在整个相对布局上完成,把布局设置为可点击的,然后通过设置状态选择器实现点击击中时整个布局背景颜色改变实现好的交互性。

3.2  状态选择器

新建一个drawable文件夹(名称不可变),在文件夹下新建相应的xml文件,在文件中配置相应的状态信息。

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

<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item

android:drawable="@drawable/last_pressed" android:state_pressed="true">

</item>

    <item android:drawable="@drawable/last_normal"></item>

</selector>

在布局文件的background属性里配置该文件就可以实现。

4.  第一个功能模块:安全模块

4.1  密码设置

密码设置分为两部分,第一部分是第一次点击时也就是用户没有设置过密码时,这种情况弹出第一个自定义的对话框去提醒用户设置密码,如果用户设置好了密码或设置过了密码(也就是密码有过保存数据)就进入验证密码的对话框去验证,验证通过分两种情况进入页面,如果用户是第一次登录设置密码则进入第一个页面开始设置,如果不是则直接进入后面的界面。

 

4.1.1  设置密码的自定义对话框

 

在用户点击进入第一个安全模块的时候触发,通过AlertDialog.Builder创建对话框,然后通过打气筒打好一个自定义的view(类似于适配器里的操作),然后通过setview方法设置对话框的显示为这个view的布局,实现布局里的两个控件的点击监听事件,就完成了对话框的基本逻辑,不过有一点要注意,完成的事件是自定义的事件,如何去实现对话框的关闭涉及到作用域的问题,因为关闭的操作在点击事件中完成,也就是在一个方法的内部类中,而对话框的开启则是在点击事件发生前就应该开启,且对话框对象的关联是通过开启事件的操作完成(当调用show方法的时候会返回一个dialog对象,通过返回的这个对象就得到定义的对话框的实例,这样才可以进行关闭操作)。这样需要定义一个变量使方法中的和方法的内部类中的都可以访问到,最好的方式是在外部类中定义一个成员变量,因为在方法中定义变量最好是在定义的时候就赋值(如果不赋值在内部类中使用则需要定义为final,而final的值是不可改变的,这样就是一个矛盾),显然不符合需求,在内部类中定义外部方法又访问不到,所以在这里使用外部类的成员变量的定义方式(不然会有bug)。

密码确认的自定义对话框逻辑类似。

 

4.1.2  自定义对话框的优化

 

4.2  第一个activity页面:stepFirst

主要完成显示一些信息主要是布局代码的完成,里面涉及到一个style的设置,可以去帮助优化布局,使代码复用性高,且维护性好,当有很多页面使用类似的布局设置时应该去提取一个style

res/values/styles.xml文件里去添加style节点抽取布局。具体实现代码:

<style name="text_title">

    <item name="android:layout_width">match_parent</item>

    <item name="android:layout_height">48dp</item>

    <item name="android:background">#99CCFF</item>

    <item name="android:gravity">center_vertical</item>

    <item name="android:textColor">#ffffff</item>

    <item name="android:textSize">18sp</item>

</style>

4.2  第二个activity页面:step02

页面主要完成绑定sim卡的逻辑,当用户点击了绑定sim卡时应该把sim的信息进行保存,这样当手机被盗换卡时能够比较出sim信息的不同然后通知用户。如果用户没有设置绑定则提醒用户必须绑定才能进入下一步。

为了使页面的交互性更好,使用了屏幕的事件处理,判断用户手指在屏幕上的操作,然后通过Android提供的GestureDetector方法去处理这些事件,在该方法中重写一些方法的逻辑就可以完成用户对应事件的捕获与处理。而且在activity的切换效果中使用了自定义的动画效果,使画面的切换更美观。

 

4.2.1  屏幕事件的处理

因为在多个activity中都需要这样的效果所以在代码重构时把这逻辑抽取到一个activity里面,这个activity继承了activity,增加了一些公有的逻辑和方法,如果页面需要使用这些效果直接继承这个抽取过得activity即可,这样的操作大大提高了页面的维护性以及代码的复用性。关于代码的重构不在这里进行介绍。

关于屏幕事件的处理,采用重写ontouchevevt方法去处理得到的屏幕事件由于并没有采用过ontouch的事件的监听,所以不会因为优先级的关系影响到该方法的执行,并不会被截断。Android提供了很方便的api去方便开发者处理屏幕的事件:GestureDetector

在实例化该对象时需要创建一个监听器,在该内部类里对应多种类型的事件处理,根据需要重写onfling方法,在里面完成手指怎样滑动会执行什么操作。

对于页面的切换,使用了自定义的动画效果,由于作为公共的父类,在其中的方法中并不提供具体的方法实现体。所以只给出方法的规范,定义出相应的抽象的方法,由子类去重写实现。

4.2.2  自定义的界面切换动画的实现

关于切换的动作是由子类去实现的,在finishstartxxx方法后(也就是跳转代码完成后)写overridePendingTransition(R.anim.anim_stepin, R.anim.anim_stepout);方法去实现自定义动画的效果,方法中的两个参数分别代表前一个页面的退出效果和后一个页面的进入的动画的效果,(通过xml文件去定义这些动画效果,有一定的格式,必须放在anim文件夹下(自定义的文件夹,名称不能改变));

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

<translate xmlns:android="http://schemas.android.com/apk/res/android"

    android:duration="300"

    android:fromXDelta="100%p"

    android:fromYDelta="0"

    android:toXDelta="0"

    android:toYDelta="0" >

</translate>

 

4.2.3  保存sim卡的信息

通过首选项保存sim卡的信息,当用户锁定sim卡时就保存该信息,通过的到系统的telephony service来得到teleponymanager,通过teleponymanager中的getsimserialnumber方法得到数据通过首选项保存。(下次开启重启是会比较sim信息看是否有换卡的操作,如果有就给安全号码发短信)

4.3  第三个activity页面:step03

3个页面完成安全号码的存储,由于是安全操作的关键部分,如果用户没有填写内容则不能进入下一个页面,(这里只需做一个判断即可)。用还可以选择通讯录里的号码来作为安全号码。安全号码的保存也是通过首选项来保存。

4.3.1  得到开启的页面返回的数据

通过点击页面上的安全号码按钮可以进入到联系人页面,通过startActivityForResult方式开启意图,到达contactactivity的页面(该页面完成联系人信息的展示),在该页面完成选择后,通过重写onactivityresult方法可以得到开启的那个页面里的选择的信息的内容。

(通过意图intent,以及bundle在底层完成数据的封装和传输)。再将得到的信息显示在编辑框里即可。

4.3.2  contactactivity里对于联系人信息的显示

通过内容提供者来得到联系人数据库里的数据,(Android4大组件之一,如果需要访问不同应用程序间的数据需要用到)

两句关键的代码:

ContentResolver resolver = context.getContentResolver();

        // 查询raw_contact

Uri uri = Uri.parse("content://com.android.contacts/raw_contacts");

第一句代码得到解析工具的对象,第二句得到需要解析的资源的信息。(也就是要去解析哪一个资源)

然后利用面向对象的思想就可以直接拿着这个工具去采取查询操作,在对数据库进行持查询等操作前,先要清楚操作的表的结构。

利用sqlite的工具打开导出的数据库中联系人表,分析其中的结构(分析过程在小结中提供)可以知道要完成联系人数据的读取涉及到多张表,先得到id信息在去另外的表中遍历查询。

拿到的信息存储在cursor游标中,对游标进行遍历读取,得到每一个id,查询每个id对应的信息(这些信息依然保存在一个cursor中),遍历第二层的cursor得到具体的信息保存在集合中,再将保存有信息的集合数据通过适配器显示在界面上(listview)。

通过listview的条目点击事件返回选择的数据,这样就拿到了用户点击选择的联系人的信息。

 

4.4  第四个activity页面:step04

主要是页面的布局和利用首选项对配置信息的存储,方法类似。

 

4.5  第五个activity页面:最后配置信息完全显示

显示首选项中保存的相关的配置信息,提供一个可以点击文本可以返回到第一个step01界面进行重新的信息配置。然后显示相应的指令信息,当手机被盗后,利用安全号码给对应号码发送指定的指令让手机完成指定的操作(后面介绍)。

4.6  如何完成对被盗手机的远程操作

可以发送指定的短信完成:播放音乐,通过短信返回手机所在位置,锁屏,远程清理数据。

实现的原理:通过检测sim卡改变的信息来完成是否换卡,如果sim有改变,则给安全号码发送一条手机可能被盗的短信,这样安全手机也就获得了换卡后的号码,由于android手机的换卡手机需要重新启动,利用广播,当重新开机后就完成sim卡的对比。

写一个broadcast,在清单文件里配置意图过滤器监听开启启动的动作,当监听到事件的发生时,执行onreceiver里的方法(完成sim的对比),如果不相符合,给安全号码发送短信。

关键代码:

获取sim信息的api

TelephonyManager tm=getSystemService(TELEPHONY_SERVICE);

tm.getSimSerialNumber;

发送短信的api

SmsManager.getDefault().sendTextMessage();

 

4.6.1  解析短信

安全号码接受到了短信也就得到了相应的号码,给手机回复相应的短信,通过解析判断短信的内容选择对应的逻辑执行。

写一个brodcast,在清单文件里配置意图过滤器,把该意图过滤器的优先级设置为最高,当有短信发进来时,先在onreceive里进行判断,如果是指定的特定短信或者是垃圾信息就进行拦截,不让用户收到。(短信是有序广播,先由优先级高的处理,处理完了再交给低优先级的)

Object[] objects = (Object[]) intent.getExtras().get("pdus");

SmsMessage message = SmsMessage.createFromPdu((byte[]) obj);

String body = message.getMessageBody();

 

4.6.2  播放音乐

res/目录下新建一个raw文件夹,存放需要播放的音乐,利用mediaplayerapi播放音乐。

MediaPlayer player=MediaPlayer.create(context, R.raw.zjwz);

player.start();

 

4.6.3  返回手机的位置

由于中国所用坐标系是不正确的(火星坐标系,国家对坐标进行了加偏),返回的坐标需要进行进一步的处理才能得到真实的坐标,先得到手机返回的坐标。

由于位置是变化着的,就需要得到能够更新的数据,所以通过服务在后台进行位置信息的获取,当用户的位置发生了较大移动时就返回新的位置信息。

当短信中解析到该指令信息时开启一个服务,通过服务监测位置信息的改变,在把得到的信息通过一定的方法转换为真实的位置坐标,把坐标点通过短信发送给真实的手机。

服务的写法:

先得到locationManager,通过个体systemservice(locationservice)得到。

关键的代码:

lm.requestLocationUpdates("gps", 0, 0, locationListener);

locationListener是一个接口,写一个实现类去实现这个接口里某实现的方法,这个类的作用是监听位置信息。

重写里面的onLocationChanged方法,当位置信息改变时,执行该方法里的逻辑,通过短信发送位置信息。(关于火星坐标的转换在小结里叙述)

 

 

4.6.4  清楚数据和锁屏

通过系统的服务去获得超级管理员的权限(获得后还需要用户在手机设置里去开启该权限)

DevicePolicyManager dpm= (DevicePolicyManager)context.getSystemService(Context.DEVICE_POLICY_SERVICE);

配置超级管理员信息,要写一个特定的广播,系统通过该广播判断该程序是否配置了超级管理员的权限已经有哪些操作的权限。也就是当你要执行上面的语句时,会有相应的广播响应,如果没有配置该广播则会出错。

该广播继承DeviceAdminReceiver,在清单文件里完成一系列的文件信息的配置。(固定的写法)

<receiver

            android:name="com.itheima.mobilesoft.receiver.MyAdmin"

            android:description="@string/sample_device_admin_description"

            android:label="@string/sample_device_admin"

            android:permission="android.permission.BIND_DEVICE_ADMIN" >

            <meta-data

                android:name="android.app.device_admin"

                android:resource="@xml/device_admin_sample" />

            <intent-filter>

                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />

            </intent-filter>

</receiver>

<string name="sample_device_admin">黑马手机安全卫士</string>

<device-admin xmlns:android="http://schemas.android.com/apk/res/android">

  <uses-policies>

    <limit-password />      

    <watch-login />         

    <reset-password />      

    <force-lock />          

    <wipe-data />           

    <expire-password />     

    <encrypted-storage />

    <disable-camera />      

  </uses-policies>

</device-admin>

在执行超级管理权限的相应代码时,会解析这些xml文件,看该程序的超级管理权限拥有哪些,只能执行权限里又得方法。该api里有修改密码,清除数据,锁屏的方法。

 

到此为止第一个功能模块手机防盗的功能结构大致介绍完了,下面介绍第二个功能模块。

 

 

 

 

 

5.   第二个功能模块:骚扰拦截

 (由于时间关系,在后期慢慢补上完善)

 

作者:wanghonghao168 发表于2016/11/7 20:45:06 原文链接
阅读:3 评论:0 查看评论

Android之DataBinding初体验(一)

$
0
0

DataBinding是谷歌推出的一个官方的数据绑定框架,所以我们有必要学下怎么使用它。如果你英文足够好就可以去官网看。
https://developer.android.com/topic/libraries/data-binding/index.html


准备工作:
在 该Moudle下的build.gradle ,添加 :

dataBinding{
        enabled true
    }

这里写图片描述

然后 ,rebuild project下;

然后我们再创建一个简单的javabean,Student类:

/**
 * Created by Administrator on 2016/11/7.
 */

public class Student {
    private String name;
    private int age;


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

修改 activity_mian.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <data>
      <variable
          name="student"
          type="com.example.edu.databindingsimple.Student"/>
  </data>
<LinearLayout
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.edu.databindingsimple.MainActivity">

    <!--显示学生姓名的textview-->
    <TextView
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{student.name}" />

    <!--显示学生年龄的textview-->
    <TextView
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{student.age}" />
</LinearLayout>
</layout>

必须是< layout >这个为根节点,< data >节点里的name 属性的值可以任取,必须跟下面的你使用的相同。type是你的类型,一般就是你的类名。@{这里面就是你text的值了},我的理解这就是数据绑定吧。不需要去java代码中去setText()了

MainActivity代码:

import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.edu.databindingsimple.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
   private  ActivityMainBinding mainBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainBinding= DataBindingUtil. setContentView(this,R.layout.activity_main);//这样就绑定了
        Student student=new Student("我不是程序员",22);
       //  mainBinding.setStudent(student);//赋值
         mainBinding.setVariable(BR.student,student);//这两种方法都行
    }
}

接下来运行:

运行会报错,因为我们的年龄是int型的,而text属性接受的是string,所以要把main_activity那个改下;

  <!--显示学生年龄的textview-->
    <TextView
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(student.age)}" />

接下来再运行下:

这里写图片描述

是不是很简单,代码也变少了很多,没有findViewById操作,没有setText操作。

我们也可以为我们的控件绑定事件,接下来就演示下:

修改后的activity_main

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <data>
      <variable
          name="student"
          type="com.example.edu.databindingsimple.Student"/>
     <variable
         name="myclick"
         type="com.example.edu.databindingsimple.MainActivity"/>
  </data>
<LinearLayout
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.edu.databindingsimple.MainActivity">

    <!--显示学生姓名的textview-->
    <TextView
        android:onClick="@{myclick.myClick}"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{student.name}" />

    <!--显示学生年龄的textview-->
    <TextView
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(student.age)}" />
</LinearLayout>
</layout>

修改后的MainActivity:

import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.example.edu.databindingsimple.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
   private  ActivityMainBinding mainBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainBinding= DataBindingUtil. setContentView(this,R.layout.activity_main);//这样就绑定了
        Student student=new Student("我不是程序员",22);
        //mainBinding.setStudent(student);
        mainBinding.setVariable(BR.student,student);//这两种方法都行
        mainBinding.setMyclick(this);
    }
    //这个是绑定到第一个textview 控件中
    public void myClick(View view){
        Toast.makeText(this,((TextView)view).getText(),Toast.LENGTH_SHORT).show();
    }
}

运行结果:

这里写图片描述

接下来我们在写另外一种方式,也可以绑定方法,

修改后的activity_main

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <data>
      <variable
          name="student"
          type="com.example.edu.databindingsimple.Student"/>
     <variable
         name="myclick"
         type="com.example.edu.databindingsimple.MainActivity"/>
  </data>
<LinearLayout
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.edu.databindingsimple.MainActivity">

    <!--显示学生姓名的textview-->
    <TextView
        android:onClick="@{myclick.myClick}"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{student.name}" />

    <!--显示学生年龄的textview-->
    <TextView
        android:onClick="@{()->myclick.myClick(student)}"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(student.age)}" />
</LinearLayout>
</layout>

修改后的activity_main


import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.example.edu.databindingsimple.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
   private  ActivityMainBinding mainBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainBinding= DataBindingUtil. setContentView(this,R.layout.activity_main);//这样就绑定了
        Student student=new Student("我不是程序员",22);
        //mainBinding.setStudent(student);
        mainBinding.setVariable(BR.student,student);//这两种方法都行
        mainBinding.setMyclick(this);
    }
    //这个是绑定到第一个textview 控件中
    public void myClick(View view){
        Toast.makeText(this,((TextView)view).getText(),Toast.LENGTH_SHORT).show();
    }
    //这个是绑定到第二个textview 控件中
    public void myClick(Student student){
        Toast.makeText(this,"你好啊",Toast.LENGTH_SHORT).show();
    }

}

运行效果:

这里写图片描述

可以看到两种方式都行,效果都是差不多的。我估计应该可以绑定任何方法到控件中都行。大家可以试试,

修改后的activity_main

import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.example.edu.databindingsimple.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
   private  ActivityMainBinding mainBinding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mainBinding= DataBindingUtil. setContentView(this,R.layout.activity_main);//这样就绑定了
        Student student=new Student("我不是程序员",22);
        //mainBinding.setStudent(student);
        mainBinding.setVariable(BR.student,student);//这两种方法都行
        mainBinding.setMyclick(this);
    }
    //这个是绑定到第一个textview 控件中
    public void myClick(View view){
        Toast.makeText(this,((TextView)view).getText(),Toast.LENGTH_SHORT).show();
    }
    //这个是绑定到第二个textview 控件中
    public void myClick(Student student){
        Toast.makeText(this,"你好啊",Toast.LENGTH_SHORT).show();
    }
    //这个是绑定到第三个textview 控件中
    public void myClick(){
        Toast.makeText(this,"HelloWorld",Toast.LENGTH_SHORT).show();
    }
}

修改后的activity_main

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >
  <data>
      <variable
          name="student"
          type="com.example.edu.databindingsimple.Student"/>
     <variable
         name="myclick"
         type="com.example.edu.databindingsimple.MainActivity"/>
  </data>
<LinearLayout
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.edu.databindingsimple.MainActivity">

    <!--显示学生姓名的textview-->
    <TextView
        android:onClick="@{myclick.myClick}"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{student.name}" />

    <!--显示学生年龄的textview-->
    <TextView
        android:onClick="@{()->myclick.myClick(student)}"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(student.age)}" />

    <TextView
        android:onClick="@{()->myclick.myClick()}"
        android:gravity="center_horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@{String.valueOf(student.age)}" />
</LinearLayout>
</layout>

运行效果:

这里写图片描述

好了,最基础最基础的学完了,还有很多高级特性,高级特性我就不讲了,我讲的不是很清楚。高级部分我也只是会用部分而已,

写博客目的只是记录自己学习的过程

作者:song_shui_lin 发表于2016/11/7 21:21:32 原文链接
阅读:4 评论:0 查看评论

Android NFC_P2P 开发

$
0
0
# Android NFC开发 #

----------

**我参照了NFC实战详解(赵波的一本书),没有NFC基本知识了解的推荐去看这本书的前3章,1-2小时就看完了**


**NFC的介绍啥的,我也不说了,一百度一大片,我就只上我对NFC以及代码的理解了,对了(我用的Mark编辑的,可能会有点符号,请勿见怪- -)**

----------

## 这里要说的是采用setNdefPushMessage的方式,手动发送一个NFC信息,属于NFC设备对NFC设备(p2p)的方式 ##
**PS:在这里我要吐槽一下,对于这个p2p方式的NFC数据传送,也是没谁了,要求手机的NFC传送点和点对上才能和平发送,就是手机后背有个NFC点,碰上,屏幕上才会弹出 "触摸发送" 选项,估计是塑料壳的原因吧,金属壳的不知道会不会更容易些,不吐了,上货!**


----------
**步骤1. 检查手机是否有NFC功能①,以及NFC和NFC_Beam功能②是否开启;**

**①:有NFC功能才能进行NFC的相关操作**

**②:NFC_Beam指的是设备和设备之间传输必须要开的(本人是这么理解的),不开仅仅可以设备扫卡(比如公交卡等NFC卡)**

**步骤2. 发送消息setNdefPushMessage**

**步骤3. 拦截(姑且称之为拦截)消息并处理**

**步骤4. 在MainFest文件中添加相应权限和activity-action**


----------


## 下面上代码,具体细节在注释中都能看见 ##

**对了,对NFC进行操作需要NfcAdapter这个类,是Android自带的**

**步骤1. 我定义了一个方法,进行了NFC是否开启的判断**

private void checkNFCFunction() {
    //  获取NFCAdapter(这个是全局的,参数是一个Context)
    mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
    //  mNfcAdapter为Null表示机器没有NFC功能
    if (mNfcAdapter == null) {
        //  提示      (这里是自定义的Log日志类,没有定义过可以用Log.I进行替换或者直接吐司,我也会上传代码)
        DebugUtils.LogI("没有NFC功能", getClass());
    } else {
        //  表示NFC功能未打开,就要弹出Dialog提示用户打开了
        if (!mNfcAdapter.isEnabled()) {
            Dialog dialog = null;
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("警告!");
            builder.setMessage("NFC功能未开启,是否前往开启(不开启将无法继续)");
            builder.setPositiveButton("开启", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    //  ACTION_WIRELESS_SETTINGS(即跳入NFC功能开启界面)
                    Intent setnfc = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
                    startActivity(setnfc);

                }
            }).setNegativeButton("不开启", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    finish();
                }
            });
            dialog = builder.create();
            dialog.setCancelable(false);
            dialog.setCanceledOnTouchOutside(false);
            setDialogWidth(dialog).show();
            return;
        //  NFC功能开启之后 判断NFC_Beam功能是否开启
        } else if (!mNfcAdapter.isNdefPushEnabled()) {
            Dialog dialog = null;
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("警告!");
            builder.setMessage("NFC_Beam功能未开启,是否前往开启(不开启将无法继续)");
            builder.setPositiveButton("开启", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    //  同理 ACTION_NFCSHARING_SETTINGS跳入NFC_Beam设置界面
                    Intent setnfc = new Intent(Settings.ACTION_NFCSHARING_SETTINGS);
                    startActivity(setnfc);

                }
            }).setNegativeButton("不开启", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    dialog.dismiss();
                    finish();
                }
            });
            dialog = builder.create();
            dialog.setCancelable(false);
            dialog.show();
            return;
        }
    }

}

注意: 在Activity被加载的时候 需要首先调用这个方法,就是说需要放在onCreate中,当然 在onStart之前就可以

步骤2. 创建信息并且发送,NFC消息需要通过NdefMessage来装(就类似四大组件之间的消息需要用Intent来装一样)需要用到NdefMessage类,Android自带的- -,还需要一个BobNdefMessage类,不自带,一百度一大把,我这里也上传- -在下面

mButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //  步骤1 : 创建NDEF Msg (三个参数,源码在,请自查)
            NdefMessage message = BobNdefMessage.getNdefMsg_from_RTD_TEXT("我是要发送的消息", false, false);
            //  步骤2 : 发送信息
            mNfcAdapter.setNdefPushMessage(message, ActNfc2.this);
        }
    });

**步骤3. 接收消息,并处理!处理!处理!这个才是重点,弱弱的说一下,有些地方都没搞懂- -也没去搞- -当然,没搞懂的地方我会标注/(ㄒoㄒ)/~~的**
    **接收..步骤1. 需要重写这个方法,具体原因不是很清楚,反正是能接收到数据到Intent- -**

@Override
    protected void onNewIntent(Intent intent) {
        setIntent(intent);
    }

**接收..步骤2. 重写onResume() 在接收完数据的时候会回调这个方法,然后数据就通过onNewIntent传过来了,然后就能解析了/(ㄒoㄒ)/~~**

@Override
    	protected void onResume() {
    	    super.onResume();
    	    //   消息判别(判别是不是NDEF类型的消息)
    	    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
    	        //  处理接受到的数据(同样定义了方法)
    	        resolveIntent(getIntent());
    	    }
    	}

**重点来了,我们来处理,其实步骤就那样,一层一层的把数据取出来 intent中取出Message,Message中取出Record,Record中取出数据- -包的有够到紧的-_-**

    **首先,我们从intent中取出Message**

private void resolveIntent(Intent intent) {
        String action = intent.getAction();
		//	程序的严谨性,再判断一遍
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {

            NdefMessage[] messages = null;
			//	通过EXTRA_NDEF_MESSAGES_Key就能得到NFC发过来的Message数组了  -_-  就是数据转移- -
            Parcelable[] rawMsgs = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            if (rawMsgs != null) {
                messages = new NdefMessage[rawMsgs.length];
                for (int i = 0; i < rawMsgs.length; i++) {
                    messages[i] = (NdefMessage) rawMsgs[i];
                }
            } else {
                //  未知TagType
                byte[] empty = new byte[]{};
                NdefRecord record = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
                NdefMessage msg = new NdefMessage(new NdefRecord[]{record});
                messages = new NdefMessage[]{msg};
            }
            //  从Message中取出Record(又定义了一个方法,)
            progressNDEFMsg(messages);
        } else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {
            DebugUtils.LogI(Tag_ASSIST + "ACTION_TECH_DISCOVERED", getClass());
        } else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
            DebugUtils.LogI(Tag_ASSIST + "ACTION_TAG_DISCOVERED", getClass());
        } else {
            DebugUtils.LogI(Tag_ASSIST + "UnKnown TAG", getClass());
            finish();
            return;
        }

    }

**从Message中取出Record**
private void progressNDEFMsg(NdefMessage[] messages) {
		//	非空
        if (messages == null || messages.length == 0) {
            return;
        }
        for (int i = 0; i < messages.length; i++) {
            int length = messages[i].getRecords().length;
            NdefRecord[] records = messages[i].getRecords();
            //  几个记录(书上这里有点问题,我改了改- -具体可以看书)
            for (NdefRecord record : records) {
				//	首先需要确定是文字
                if (isTextRecord(record)) {
					//	然后 取出最后的数据
                    parseRTD_TEXTRecord(record);
                }
            }
        }
    }

**取出最后的数据**

**注:Preconditions是一个格式验证的类,要是不懂可以用if_else替代,需要Jar包,如果不想百度 我也会在后面提供下载**

private void parseRTD_TEXTRecord(NdefRecord record) {
        //  记录格式验证
        Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
        //  记录类型验证
        Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT));

        String payloadStr = "";
        byte[] payload = record.getPayload();
        Byte statusByte = record.getPayload()[0];
		//	这一块我就不懂了 start-----
        String textEncoding = ((statusByte & 0200) == 0) ? "UTF-8" : "UTF-16";
        //  0x80=0200 ,获取状态字节编码
        //  获取语言码长度
        int languageCodeLength = statusByte & 0077;//   & 0x3F=0077(bit 5 to 0)
		//	不懂 end -----
        String languageCode = new String(payload, 1, languageCodeLength, Charset.forName("UTF-8"));
        try {
            payloadStr = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);
        } catch (UnsupportedEncodingException e) {
            DebugUtils.LogI("异常信息" + e.getMessage(), getClass());
        }
    }


**最后的languageCode就是在另一个手机上得到的信息了,可以是Json等任何类型的String 转成实体啦,图片啦,都可以- - 后面附上部分工具类以及Jar包地址**

**哦哦 还有几个方法**

**判断是不是文字**


private boolean isTextUri(NdefRecord record) {
        if (NdefRecord.TNF_WELL_KNOWN == record.getTnf()) {
            if (Arrays.equals(NdefRecord.RTD_TEXT, record.getType())) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
## 工具类 ##

**Log日志类--DebugUtils**

/**
    	* 提供打印日志的功能
    	*/
	public class DebugUtils {
	    private static boolean logIsOpen = true;     //  是否开启Log日志打印
	
    	public static void LogI(Object obj, Class clazz) {
    	    if (logIsOpen) {
    	        Log.i("RedWolf", "["+clazz.getSimpleName() + "]- " + obj);
    	    }
    	}
	}


**BobNdefMessage 直接拷贝的- -**


public class BobNdefMessage {

    /**
     * @About:create a TNF_WELL_KNOW NDEF record as RTD_URI
     * @param uriFiledStr , The rest of the URI, or the entire URI (if
     *            identifier code is 0x00).
     * @param identifierCode = prefixes(URI identifier code), 0x01=http://www.
     *            ,0x02=https://www. , 0x03=http://
     * @param flagAddAAR, true means add AAR
     * @return NdefMessage
     * @Ref: NFCForum-TS-RTD_URI_1.0
     * @By SkySeraph-2013
     */
    public static NdefMessage getNdefMsg_from_RTD_URI(String uriFiledStr, byte identifierCode,
                                                      boolean flagAddAAR) {
        byte[] uriField = uriFiledStr.getBytes(Charset.forName("US-ASCII"));
        byte[] payLoad = new byte[uriField.length + 1]; // add 1 for the URI
        // Prefix
        payLoad[0] = identifierCode; // 0x01 = prefixes http://www. to the URI
        // appends URI to payload
        System.arraycopy(uriField, 0, payLoad, 1, uriField.length);

        // Method1:
        NdefRecord rtdUriRecord1 = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI,
                new byte[0], payLoad);

        // Method2:only in API 14
        String prefix = URI_PREFIX_MAP.get(identifierCode);
        NdefRecord rtdUriRecord2 = NdefRecord.createUri(prefix + uriFiledStr);

        // Method3:only in API 14
        NdefRecord rtdUriRecord3 = NdefRecord.createUri(Uri.parse(prefix + uriFiledStr));

        if (flagAddAAR) {
            // note: returns AAR for different app (nfcreadtag)
            return new NdefMessage(new NdefRecord[] {
                    rtdUriRecord1, NdefRecord.createApplicationRecord("skyseraph.nfc_demo")
            }); // packageName
        } else {
            return new NdefMessage(new NdefRecord[] {
                    rtdUriRecord1
            });
        }
    }

    /**
     * @About:create a TNF_WELL_KNOW NDEF record as RTD_TEXT
     * @param text , the really text data
     * @param encodeInUtf8 , false means TEXT encoded by UTF-8
     * @param flagAddAAR , true means add AAR
     * @return NdefMessage
     * @Ref: NFCForum-TS-RTD_Text_1.0
     * @By SkySeraph-2013
     */
    public static NdefMessage getNdefMsg_from_RTD_TEXT(String text, boolean encodeInUtf8,
                                                       boolean flagAddAAR) {

        Locale locale = new Locale("en", "US"); // a new Locale is created with
        // US English.
        byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
        // boolean encodeInUtf8 = false;
        Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
        int utfBit = encodeInUtf8 ? 0 : (1 << 7);
        char status = (char)(utfBit + langBytes.length);
        // String text = "This is an RTD_TEXT exp";
        byte[] textBytes = text.getBytes(utfEncoding);
        byte[] data = new byte[1 + langBytes.length + textBytes.length];
        data[0] = (byte)status;
        System.arraycopy(langBytes, 0, data, 1, langBytes.length);
        System.arraycopy(textBytes, 0, data, 1 + langBytes.length, textBytes.length);
        NdefRecord textRecord = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT,
                new byte[0], data);

        if (flagAddAAR) {
            // note: returns AAR for different app (nfcreadtag)
            return new NdefMessage(new NdefRecord[] {
                    textRecord, NdefRecord.createApplicationRecord("skyseraph.nfc_demo")
            });
        } else {
            return new NdefMessage(new NdefRecord[] {
                    textRecord
            });
        }
    }

    /**
     * @About: create a TNF_ABSOLUTE_URI NDEF record
     * @param absoluteUri ,the absolute Uri
     * @param flagAddAAR , true means add AAR
     * @return NdefMessage
     * @Note: TNF_ABSOLUTE_URI indicates the absolute form of a URI that follows
     *        the absolute-URI rule defined by RFC 3986
     * @Note: Recommend that you use the RTD_URI type instead of
     *        TNF_ABSOLUTE_URI, because it is more efficient
     * @By SkySeraph-2013
     */
    public static NdefMessage getNdefMsg_from_ABSOLUTE_URI(String absoluteUri, boolean flagAddAAR) {
        // String absoluteUri = "http://developer.android.com/index.html";
        byte[] absoluteUriBytes = absoluteUri.getBytes(Charset.forName("US-ASCII"));
        NdefRecord uriRecord = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI, new byte[0],
                new byte[0], absoluteUriBytes);
        if (flagAddAAR) {
            // note: returns AAR for different app (nfcreadtag)
            return new NdefMessage(new NdefRecord[] {
                    uriRecord, NdefRecord.createApplicationRecord("skyseraph.nfc_demo")
            });
        } else {
            return new NdefMessage(new NdefRecord[] {
                    uriRecord
            });
        }
    }

    /**
     * @About:create a TNF_MIME_MEDIA NDEF record
     * @param payLoad,the MIME data
     * @param mimeType,the MIME Type
     * @param flagAddAAR, true means add AAR
     * @return NdefMessage
     * @By SkySeraph-2013
     */
    @SuppressLint("NewApi")
    public static NdefMessage getNdefMsg_from_MIME_MEDIA(String payLoad, String mimeType,
                                                         boolean flagAddAAR) {
        byte[] payLoadBytes = payLoad.getBytes(Charset.forName("US-ASCII"));
        // String mimeType = "application/skyseraph.nfc_demo";

        // method1:Creating the NdefRecord manually
        NdefRecord mimeRecord1 = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
                mimeType.getBytes(Charset.forName("US-ASCII")), new byte[0], payLoadBytes);
        // the identfier of the record is given as 0, since it will be the first
        // record in the NdefMessage

        // method2:Using the createMime() method, in API-16
        NdefRecord mimeRecord2 = NdefRecord.createMime(mimeType, payLoadBytes);

        if (flagAddAAR) {
            // note: returns AAR for different app (nfcreadtag)
            return new NdefMessage(new NdefRecord[] {
                    mimeRecord1, NdefRecord.createApplicationRecord("skyseraph.nfc_demo")
            });
        } else {
            return new NdefMessage(new NdefRecord[] {
                    mimeRecord1
            });
        }
    }

    /**
     * @About:create a TNF_EXTERNAL_TYPE NDEF record
     * @param payLoad,the EXTERNAL data
     * @param flagAddAAR, true means add AAR
     * @return NdefMessage
     * @By SkySeraph-2013
     */
    @SuppressLint("NewApi")
    public static NdefMessage getNdefMsg_from_EXTERNAL_TYPE(String payLoad, boolean flagAddAAR) {
        byte[] payLoadBytes = payLoad.getBytes();
        String domain = "skyseraph.nfc_demo"; // usually your app's package name
        String type = "externalType";
        String externalType = domain + ":" + type;

        // method1:Creating the NdefRecord manually
        NdefRecord exteralRecord1 = new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE,
                externalType.getBytes(), new byte[0], payLoadBytes);

        // method2:Using the createExternal() method, in API-16
        NdefRecord exteralRecord2 = NdefRecord.createExternal(domain, type, payLoadBytes);

        if (flagAddAAR) {
            // note: returns AAR for different app (nfcreadtag)
            return new NdefMessage(new NdefRecord[] {
                    exteralRecord1, NdefRecord.createApplicationRecord("skyseraph.nfc_demo")
            });
        } else {
            return new NdefMessage(new NdefRecord[] {
                    exteralRecord1
            });
        }
    }

    /**
     * checkSystemVersion()
     */
    private boolean flagVersion = false;

    private void checkSystemVersion() {
        // TODO Auto-generated method stub
        // if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)

        String systemModel;
        String releaseVersion;
        String sdkVersion;
        systemModel = android.os.Build.MODEL;
        sdkVersion = android.os.Build.VERSION.SDK;
        releaseVersion = android.os.Build.VERSION.RELEASE;
        if (Integer.parseInt(sdkVersion) > 15) {
            flagVersion = true;
        } else {
            flagVersion = false;
        }
    }

    /**
     * NFC Forum "URI Record Type Definition" This is a mapping of
     * "URI Identifier Codes" to URI string prefixes, per section 3.2.2 of the
     * NFC Forum URI Record Type Definition document.
     */
    private static final BiMap<Byte, String> URI_PREFIX_MAP = ImmutableBiMap
            .<Byte, String> builder().put((byte)0x00, "").put((byte)0x01, "http://www.")
            .put((byte)0x02, "https://www.").put((byte)0x03, "http://").put((byte)0x04, "https://")
            .put((byte)0x05, "tel:").put((byte)0x06, "mailto:")
            .put((byte)0x07, "ftp://anonymous:anonymous@").put((byte)0x08, "ftp://ftp.")
            .put((byte)0x09, "ftps://").put((byte)0x0A, "sftp://").put((byte)0x0B, "smb://")
            .put((byte)0x0C, "nfs://").put((byte)0x0D, "ftp://").put((byte)0x0E, "dav://")
            .put((byte)0x0F, "news:").put((byte)0x10, "telnet://").put((byte)0x11, "imap:")
            .put((byte)0x12, "rtsp://").put((byte)0x13, "urn:").put((byte)0x14, "pop:")
            .put((byte)0x15, "sip:").put((byte)0x16, "sips:").put((byte)0x17, "tftp:")
            .put((byte)0x18, "btspp://").put((byte)0x19, "btl2cap://").put((byte)0x1A, "btgoep://")
            .put((byte)0x1B, "tcpobex://").put((byte)0x1C, "irdaobex://")
            .put((byte)0x1D, "file://").put((byte)0x1E, "urn:epc:id:")
            .put((byte)0x1F, "urn:epc:tag:").put((byte)0x20, "urn:epc:pat:")
            .put((byte)0x21, "urn:epc:raw:").put((byte)0x22, "urn:epc:")
            .put((byte)0x23, "urn:nfc:").build();
	}


**最后附上Jar包地址,顺便带上触碰自动发送信息的源码,供大家参考(PS:- -Jar包地址明日加上- -可先自行百度- -)**


/**
 * 触屏发送订单信息
 */
//  NfcAdapter.CreateNdefMessageCallBack 接口 应该是在两个Nfc设备接触的时候 被调用的..
public class ActNfc3 extends BaseActivity implements BaseInterface, NfcAdapter.CreateNdefMessageCallback {
    //  NfcAdapter
    private NfcAdapter mNfcAdapter;
    //  EditText 发送
    private EditText etSend;
    //  TextView 接收
    private TextView tvReceive;

    private Context mContext;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_nfc3);
        mContext = this;
        checkNFCFunction();
        initViews();
        initDatas();
        initEvent();
    }

    @Override
    public void initViews() {
        etSend = (EditText) findViewById(R.id.et_send);
        tvReceive = (TextView) findViewById(R.id.tv_receive);
    }

    @Override
    public void initDatas() {
        mNfcAdapter.setNdefPushMessageCallback(this, this);
    }

    @Override
    public void initEvent() {

    }


    private void checkNFCFunction() {
        mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
        //  机器不支持Nfc功能
        if (mNfcAdapter == null) {
            ToastUtils.showToastFroce(mContext, "抱歉,本机不支持NFC功能");
            return;
        } else {
            //  检查机器NFC是否开启
            if (!mNfcAdapter.isEnabled()) {
                //  机器Nfc未开启 提示用户开启 这里采用对话框的方式<PS:这个开启了  可以进行对NFC的信息读写>
                AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
                builder.setTitle("警告").setMessage("本机NFC功能未开启,是否开启(不开启将无法继续)").setNegativeButton("开启", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent setNfc = new Intent(Settings.ACTION_NFC_SETTINGS);
                        startActivity(setNfc);
                    }
                }).setPositiveButton("不开启", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                }).setCancelable(false).create().show();
                return;
            } else {
                //  NFC 已开启  检查NFC_Beam是否开启  只有这个开启了  才能进行p2p的传输
                if (!mNfcAdapter.isNdefPushEnabled()) {
                    // NFC_Beam未开启  点击开启
                    new AlertDialog.Builder(mContext).setTitle("警告!").setMessage("NFC_Beam功能未开启,是否开启(不开启将无法继续)").setNegativeButton("开启", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            Intent setNfc = new Intent(Settings.ACTION_NFCSHARING_SETTINGS);
                            startActivity(setNfc);
                        }
                    }).setPositiveButton("不开启", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            finish();
                        }
                    }).setCancelable(false).create().show();
                    return;
                }
            }
        }
    }

    //  在Nfc设备相互接触的时候 调用这个方法  进行消息的发送
    @Override
    public NdefMessage createNdefMessage(NfcEvent event) {
        DebugUtils.LogI("createNdefMessage", getClass());
        //  1.创建NdefMessage
        NdefMessage message = BobNdefMessage.getNdefMsg_from_RTD_TEXT(etSend.getText().toString(), false, false);
        return message;
    }

    @Override
    protected void onNewIntent(Intent intent) {
        //  接收到消息的第一步
        setIntent(intent);
    }

    @Override
    protected void onResume() {
        super.onResume();
        //  需要从Intent中读出信息
        //  消息判别
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
            resolveIntent(getIntent());
        }
    }

    private void resolveIntent(Intent intent) {
        String action = intent.getAction();
        if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
            NdefMessage[] messages = null;
            Parcelable[] rawMsg = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);
            if (rawMsg != null) {
                messages = new NdefMessage[rawMsg.length];
                for (int i = 0; i < messages.length; i++) {
                    messages[i] = (NdefMessage) rawMsg[i];
                }
            } else {
                //  未知Action
                byte[] empty = new byte[]{};
                NdefRecord ndefRecord = new NdefRecord(NdefRecord.TNF_UNKNOWN, empty, empty, empty);
                NdefMessage ndefMessage = new NdefMessage(ndefRecord);
                messages = new NdefMessage[]{ndefMessage};
            }
            //  将Message中的Record解析出来
            progressNdefMessage(messages);
        } else if (NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {

        } else if (NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)) {

        } else {
            return;
        }
    }

    private void progressNdefMessage(NdefMessage[] messages) {
        if (messages == null || messages.length == 0) {
            DebugUtils.LogI("progressNdefMessage-message==null", getClass());
            return;
        }
        for (int i = 0; i < messages.length; i++) {
            NdefRecord records[] = messages[i].getRecords();
            for (NdefRecord record : records) {
                DebugUtils.LogI("progressNdefMessage - record", getClass());
                if (isTextUri(record)) {
                    parseTextUri(record);
                }
            }
        }
    }

    private void parseTextUri(NdefRecord record) {
        DebugUtils.LogI("isTextUri", getClass());
        //  记录格式验证
        Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
        //  记录类型验证
        Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT));
        //  读出所有的PayLoad
        String payLoadStr = "";
        byte[] payloads = record.getPayload();
        byte statusByte = payloads[0];
        //  得到编码方式
        String textEncoding = ((statusByte & 0200) == 0) ? "UTF-8" : "UTF-16";
        //  获取语言码的长度
        int languageCodeLength = statusByte & 0077;
        String languageCode = new String(payloads, 1, languageCodeLength, Charset.forName("UTF-8"));
        //  payloadStr = new String(payload, languageCodeLength + 1, payload.length - languageCodeLength - 1, textEncoding);

        //  真正的解析??
        payLoadStr = new String(payloads, languageCodeLength + 1, payloads.length - languageCodeLength - 1, Charset.forName(textEncoding));
        //  解析完毕,加载
        tvReceive.setText("接收完毕!-->" + payLoadStr);
    }

    private boolean isTextUri(NdefRecord record) {
        if (NdefRecord.TNF_WELL_KNOWN == record.getTnf()) {
            if (Arrays.equals(NdefRecord.RTD_TEXT, record.getType())) {
                return true;
            } else {
                return false;
            }
        } else {
            return false;
        }
    }
	}


Android NFC开发实战详解 赵波

http://download.csdn.net/detail/redwolfchao/9663033   

http://download.csdn.net/detail/redwolfchao/9663054



作者:RedWolfChao 发表于2016/11/7 21:26:53 原文链接
阅读:6 评论:0 查看评论

u-boot-2016.09移植(3)-u-boot-spl.bin

$
0
0

从本节开始,就正式进入移植过程,首先进行u-boot-spl.bin的移植。

一、因为第一步要移植u-boot-spl.bin,所以我们需要先在头文件里添加宏CONFIG_SPL

u-boot-2016.09$ vim include/configs/tq210.h 

#define CONFIG_SPL

二、时钟初始化
由上节分析,由于我们没有定义宏CONFIG_SKIP_LOWLEVEL_INIT,所以进入board/samsung/tq210/lowlevel_init.S进行硬件初始化。

首先添加S5PV210的时钟相关寄存器:

u-boot-2016.09$ vim arch/arm/mach-s5pv210/include/mach/clock.h

这里写图片描述

由于现在的操作只需要在 u-boot-spl.bin 中进行,因此这里使用 CONFIG_SPL_BUILD 宏来控制,当编译u-boot-spl.bin 才会将这些初始化代码编译进 u-boot-spl.bin,而编译 u-boot.bin 时就不会。修改lowlevel_init.S如下:
这里写图片描述

并在tq210.c中加入clock头文件并实现clock_init(这里也如lowlevel_init.S使用CONFIG_SPL_BUILD 来控制编译顺序):

这里写图片描述

这里写图片描述

在clock_init的实现函数中,使用了struct s5pv210_clock const clock = (struct s5pv210_clock )samsung_get_base_clock(),其中samsung_get_base_clock,在arch/arm/mach-s5pv210/include/mach/cpu.h中实现:
这里写图片描述
将其修改为
这里写图片描述

三、DDR初始化

u-boot-2016.09$ vim board/samsung/tq210/lowlevel_init.S 

这里写图片描述

添加DMC信息,参考《嵌入式Linux学习笔记(基于s5pv210、tq210)》,这里就不详细贴出来了。

在tq210.c中添加dmc的头文件并实现ddr_init。

#include <asm/arch/dmc.h>

这里写图片描述

在arch/arm/mach-s5pv210/include/mach/cpu.h中添加宏

#define S5PV210_DMC0_BASE       0xF0000000
#define S5PV210_DMC1_BASE       0xF1400000   

SAMSUNG_BASE(dmc0, DMC0_BASE)
SAMSUNG_BASE(dmc1, DMC1_BASE)

S5PV210_DMC0_BASE与S5PV210_DMC1_BASE在第一节我们已经定义了,如果没有定义,这里自己定义

接着执行到_main函数,
这里写图片描述

由于u-boot-spl.bin文件比较小,使用SRAM提供栈内存就好,所以可以将栈指针屏蔽掉,修改为:这里写图片描述
五、拷贝BL2到SDRAM
在tq210.c中实现拷贝函数,拷贝完成后,直接跳转到DDR的起始地址执行 BL2。
这里写图片描述
CONFIG_SYS_SDRAM_BASE 在 u-boot-2014.04/include/configs/smdkv210.h 中定义,我们将其修改为我们实际 DDR 的起始地址0x20000000,CopySDMMCtoMem实现从SD的34扇区复制BL2到到DDR的起始地址0x20000000。

注:
因为BL1最大只能到16K,而每个扇区的大小为512B,16K正好是32个扇区,所以为了安全起见,我们将BL2的起始扇区设置为34。这个值并不固定,只要比u-boot-spl.bin的大小大就可以了。

四、编译

u-boot-2016.09$ make tq210_defconfig
u-boot-2016.09$ make spl/u-boot-spl.bin

发现会出现No rule to make target ‘checkarmreloc’, needed by ‘all’,修改arch/arm/mach-s5pv210/Kconfig,添加SPL选项
这里写图片描述
修改configs/tq210_defconfig,添加CONFIG_SPL=y

再次编译,发现出现mkexynosspl:Command not find。分析scripts/Makefile.spl
这里写图片描述
如果定义了CONFIG_SUMSANG,就会添加目标(obj)/$(BOARD)-spl.bin,展开即为spl/tq210-spl.bin,这里需要一个添加头文件的工具。
我这里的工具是使用的国嵌的mkv210,将其复制到tool下,将名字改为mktq210spl,修改
scripts/Makefile.spl
这里写图片描述
再次编译,发现已经产生了我们需要的spl/tq210-spl.bin。

五、烧录与测试
单独的.bin文件启动看不到现象,这里我在board/samsung/tq210/lowlevel_init.S中添加了蜂鸣器鸣叫的片段。

    .globl lowlevel_init
lowlevel_init:
mov r9, lr

#ifdef CONFIG_SPL_BUILD 

    bl clock_init      /* 时钟初始化 */

    bl beep_asm_init    
    bl delay
    bl beep_asm_off

    bl ddr_init        /* DDR初始化 */

    bl uart_asm_init
#endif

mov pc, r9

/*
     * beep_asm_init: 初始化蜂鸣器,并让其发声 
     */
    beep_asm_init: 
        ldr r0, = 0xE02000A0
        ldr r1, = 0x00000010
        str r1, [r0]

        ldr r0, = 0xE02000A4
            ldr r1, = 0x2
        str r1, [r0]

        mov pc, lr

    /*
     * delay: 延时
     */   
    delay:
        mov r0, #0x2000000

    delay_loop:
        sub r0, r0, #1
        cmp r0, #0
        bne delay_loop

        mov pc, lr

    /*
     * beep_asm_off: 关闭蜂鸣器
     */
    beep_asm_off:
        ldr r0, = 0xE02000A4
        ldr r1, = 0x0
        str r1, [r0]

        mov pc, lr

为了偷懒,我直接使用了网上搜索的TQ210的裸机LED程序,编译得到led.bin。

烧录测试:
使用DD_for_win7.exe将tq210-spl.bin与led.bin分别烧入1和34扇区
这里写图片描述
这里写图片描述
然后将SD卡插入开发板,打开电源,发现蜂鸣器鸣叫一声,并且LED不停闪烁,表示我们tq210-spl.bin移植成功。

注:
DD_for_win7.exe有添加头字节选项,如果我们使用tq210-spl.bin就不需要选择这个选项,如果使用u-boot-spl.bin就必须将这个选项勾上。
作者:keyue123 发表于2016/11/7 21:39:39 原文链接
阅读:0 评论:0 查看评论

Android常用提示框(dialog和popuwindow)

$
0
0

一、UI显示
日常生活中,我们经常会看到应用中那些提示框,有在正中的,有在底部的,这篇文章我来学习一下这两类提示框UI,主要是dialog和popuwindow。
首先上图:
1、dialog
这里写图片描述
2、popuwindow
这里写图片描述

二、代码的编写(dialog)
dialog
1、首先我们来构思一下所要显示的样式,基本上是分三个部分,①标题②内容③取消确认键。这里我使用的是layout_weight,是为了控制显示比例,易于适配屏幕。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/view_custom_alter_dialog_title"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:gravity="center"
            android:text="@string/view_load_gif_dialog_title"
            android:textSize="@dimen/view_load_gif_title_text_size" />

        <TextView
            android:id="@+id/view_custom_alter_dialog_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:text="@string/view_load_gif_dialog_message"
            android:layout_weight="2"
            android:gravity="center"
            android:layout_marginLeft="@dimen/view_load_gif_content_margin_left_right"
            android:layout_marginRight="@dimen/view_load_gif_content_margin_left_right"
            android:layout_marginBottom="@dimen/view_load_gif_content_margin_bottom"
            android:textSize="@dimen/view_load_gif_content_text_size"
            android:lineSpacingExtra="@dimen/view_load_gif_line_space_extra"
            />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/activity_main_bottom_controller_bg" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1.5"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/view_custom_alter_dialog_cancel"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:textColor="@color/app_main_color"
                android:text="@string/view_random_code_dialog_cancel"
                android:textSize="@dimen/view_load_gif_cancel_sure_textsize" />

            <View
                android:layout_width="1dp"
                android:layout_height="match_parent"
                android:background="@color/activity_main_bottom_controller_bg" />

            <TextView
                android:id="@+id/view_custom_alter_dialog_sure"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                android:gravity="center"
                android:textColor="@color/app_main_color"
                android:text="@string/view_random_code_dialog_sure"
                android:textSize="@dimen/view_load_gif_cancel_sure_textsize" />
        </LinearLayout>

    </LinearLayout>
</RelativeLayout>

2、这里我们就已经有一个基本的布局了,然后是自定义这个布局。创建一个类来继承Dialog,实现重写构造函数,然后绑定一些控件。
这里我使用的是按照获取到的手机的屏幕,来控制这个dialog的显示比例,这样可以适配的更加方便。

public class CustomAlertDialog extends Dialog {

    protected View mView;

    //ui
    protected TextView mTextTitle,mTextContent,mTextCancel,mTextSure;

    public CustomAlertDialog(Context context) {
        this(context,0);
    }

    public CustomAlertDialog(Context context, int themeResId) {
        super(context, themeResId);
        mView = LayoutInflater.from(context).inflate(R.layout.view_custom_alert_dialog,null);
        setContentView(mView);
        initView();
        //setting size
        Window dialogWindow = this.getWindow();
        WindowManager m = getWindow().getWindowManager();
        Display d = m.getDefaultDisplay(); // 获取屏幕宽、高用
        WindowManager.LayoutParams p = dialogWindow.getAttributes(); // 获取对话框当前的参数值
        //手机横竖屏时候
        if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
            p.height = (int) (d.getHeight() * 0.25); // 高度设置为屏幕的
            p.width = (int) (d.getWidth() * 0.7); // 宽度设置为屏幕的
        }else if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
            p.height = (int) (d.getHeight() * 0.3); // 高度设置为屏幕的
            p.width = (int) (d.getWidth() * 0.4); // 宽度设置为屏幕的
        }
        dialogWindow.setAttributes(p);
    }

    private void initView() {
        try{
            mTextTitle = (TextView) mView.findViewById(R.id.view_custom_alter_dialog_title);
            mTextContent = (TextView) mView.findViewById(R.id.view_custom_alter_dialog_content);
            mTextCancel = (TextView) mView.findViewById(R.id.view_custom_alter_dialog_cancel);
            mTextSure = (TextView) mView.findViewById(R.id.view_custom_alter_dialog_sure);
        }catch(Exception _e){
            ExceptionHandler.onException(_e);
        }
    }


    public void setText(String _title,String _content,String _cancel,String _sure){
        try{
            if (null != _title)
                mTextTitle.setText(_title);
            if (null != _content)
                mTextContent.setText(_content);
            if (null != _cancel)
                mTextCancel.setText(_cancel);
            if (null != _sure)
                mTextSure.setText(_sure);
        }catch(Exception _e){
            ExceptionHandler.onException(_e);
        }
    }

    public void setLeftOnclick(View.OnClickListener _onclick){
        try{
            mTextCancel.setOnClickListener(_onclick);
        }catch(Exception _e){
            ExceptionHandler.onException(_e);
        }
    }

    public void setRightOnclick(View.OnClickListener _onclick){
        try{
            mTextSure.setOnClickListener(_onclick);
        }catch(Exception _e){
            ExceptionHandler.onException(_e);
        }
    }

}

3、然后在代码中使用这个控件,这里我们注意一下,还要传入hemeResId的值,因为如果不传入,效果就不会那么好。

public void reDownloadResourceDialog(){
        try{
            final CustomAlertDialog _customAlterDialog = new CustomAlertDialog(this,R.style.random_code_dialog);
            _customAlterDialog.show();
            _customAlterDialog.setLeftOnclick(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    _customAlterDialog.dismiss();
                }
            });
            _customAlterDialog.setRightOnclick(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    _customAlterDialog.dismiss();
                    ActivityActionList.this.reDownloadResources();
                }
            });
        }catch(Exception _e){
            ExceptionHandler.onException(_e);
        }
    }

4、使用themeResId的样式,styles.xml文件中

<!--view_random_dialog-->
    <style name="random_code_dialog" parent="@android:style/Theme.Dialog">
        <item name="android:background">@drawable/dialog_random_code_shape</item>
        <item name="android:windowIsFloating">true</item>
        <item name="android:windowIsTranslucent">false</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>
    <!--view_random_dialog end-->

外加background的样式,主要是周围显示的圆角,dialog_random_code_shape.xml文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <corners android:radius="10dp" />

    <solid android:color="@color/common_white" />
</shape>

这里dialog的自定义先告一段落。

三、代码的编写(popuwindow)
popuwindow
1、我们也还是先定义一个布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="@dimen/activity_exercise_plan_xml_padding">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="@dimen/activity_exercise_plan_xml_height"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:background="@drawable/plan_more_show_shape"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="@string/activity_quit_plan_xml_show"
                android:textSize="@dimen/activity_exercise_plan_xml_show_sp" />

        </LinearLayout>

        <View
            android:layout_width="match_parent"
            android:layout_height="@dimen/activity_exercise_plan_xml_splitor"
            android:background="@color/activity_main_bottom_controller_bg" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1.5"
            android:background="@drawable/plan_more_quit_shape"
            android:orientation="vertical">

            <TextView
                android:id="@+id/exercise_plan_more_quit"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="@string/activity_quit_plan_xml_quit"
                android:textColor="@android:color/holo_red_light"
                android:textSize="@dimen/activity_exercise_plan_xml_text_sp" />

        </LinearLayout>

        <Space
            android:layout_width="match_parent"
            android:layout_height="@dimen/activity_exercise_plan_xml_space" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1.5"
            android:background="@drawable/plan_more_cancel_shape"
            android:orientation="vertical">

            <TextView
                android:id="@+id/exercise_plan_more_cancel"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:text="@string/activity_quit_plan_xml_cancel"
                android:textColor="@android:color/holo_blue_light"
                android:textSize="@dimen/activity_exercise_plan_xml_text_sp" />

        </LinearLayout>

    </LinearLayout>
</LinearLayout>

布局的样式:
这里写图片描述

2、有了布局的样式之后,就开始为布局添加绑定。
① mPopWindowMore.showAtLocation(_view, Gravity.BOTTOM, 0, 0);是设置此popuwindow要显示的位置,这里显示在底部
②setBackgroundAlpha()的方法来使得显示popuwindow的时候,会有变暗的效果,当popuwindow消失的时候,又会恢复。bgAlpha取值为0.0-1.0
③使用mPopWindowMore.setOnDismissListener()来监听popuwindow的消失

public void showPopwindowQuitPlan(View _view) {
        View _viewPopwindow = null;
        TextView _quitTextView,_cancelTextView;
        try {
            _viewPopwindow = LayoutInflater.from(this).inflate(R.layout.view_exercise_plan_more,null);
            _quitTextView = (TextView) _viewPopwindow.findViewById(R.id.exercise_plan_more_quit);
            _cancelTextView = (TextView) _viewPopwindow.findViewById(R.id.exercise_plan_more_cancel);
            if (null != _viewPopwindow){
                mPopWindowMore = new PopupWindow(_viewPopwindow,
                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
                mPopWindowMore.setTouchable(true);
                mPopWindowMore.showAtLocation(_view, Gravity.BOTTOM, 0, 0);
                mPopWindowMore.setOnDismissListener(new PopupWindow.OnDismissListener() {
                    @Override
                    public void onDismiss() {
                        setBackgroundAlpha(1f);
                    }
                });
            }

            if (null != _quitTextView){
                _quitTextView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //确认点击
                        }
                });
            }
            if (null != _cancelTextView){
                _cancelTextView.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        //取消点击
                    }
                });
            }

        }catch (Exception _e){
            ExceptionHandler.onException(_e);
        }
    }

设置背景颜色的方法

public void setBackgroundAlpha(float bgAlpha) {
        WindowManager.LayoutParams lp = getWindow().getAttributes();
        lp.alpha = bgAlpha; //0.0-1.0
        getWindow().setAttributes(lp);
    }
作者:llayjun 发表于2016/11/7 21:41:49 原文链接
阅读:0 评论:0 查看评论

ubuntu上最使用jni最简单易懂的例子

$
0
0

第一步:爆结果照,让你有坚持下去的信心



二、NDK解释

NDK全称:Native Development Kit。 
NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。 
NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

三、NDK环境搭建

1、下载安装NDK-r10e并且配置好环境,地址http://developer.android.com/sdk/ndk/index.html,如果不清楚配置,可以先看我这篇博客 Android安全与逆向之在ubuntu上面搭建NDK环境http://blog.csdn.net/u011068702/article/details/53039584

2、打开Eclipse,新建一个Android工程(我的取名为FirstJni),在工程目录FirstJni下新建jni文件夹,该文件夹就用来保存NDK需要编译的文件代码等。
3、开始新建并配置一个Builder

(a)Project->Properties->Builders->New,新建一个FirstBuilder。 
(b)在弹出的【Choose configuration type】对话框,选择【Program】,点击【OK】: 
(c)在弹出的【Edit Configuration】对话框中,配置选项卡【Main】。 
在“Name“中输入新builders的名称(这个名字可以任意取)。

在“Location”中输入nkd-build.cmd的路径(这个是下载完ndkr10e后解压后的路径,这个建议放在根目录下面,路径不能有空格和中文)。根据各自的ndk路径设置,也可以点击“Browser File System…”来选取这个路径。 
在“Working Diretcoty”中输入TestNdk位置(也可以点击“Browse Workspace”来选取TestNdk目录)。如图1


第一个箭头是ndk目录下面的ndk-build.cmd

第二个箭头是选自己的项目

(d)继续在这个【Edit Configuration】对话框中,配置选项卡【Refresh】。如图2 
勾选“Refresh resources upon completion”, 
勾选“The entire workspace”, 
勾选“Recuresively include sub-folders”。


图2

(e)继续在【Edit Configuration】对话框中,配置选项卡【Build options】。 
勾选“After a “Clean””,(勾选这个操作后,如果你想编译ndk的时候,只需要clean一下项目 就开始交叉编译
勾选“During manual builds”, 
勾选“During auto builds”, 
勾选“Specify working set of relevant resources”。如图3

图3

点击“Specify Resources…”勾选FirstJni工程,点击”finish“。 点击“OK“,完成配置。 如图4


编译环境以及成功搭建完

四、demo的简单实现

 1.在FirstJni工程中新建一个JniClient.java
package com.example.firstjni;

public class JniClient {
	    static public native String AddStr(String strA, String strB);
	    static public native int AddInt(int a, int b);
}

2.生成 .h 的c++头文件

(1)用cmd命令定位到JniClient.class 所在目录,输入“javac JniClient.java“后回车,生成JniClinet.class文件(如果是用的Eclipse建的工程,在TestNdk\bin\classes\com\ndk\test目录下就已经有JniClinet.class文件了)。com.example.firstjni

(2)将JniClinet.class拷贝到FirstJni\bin\classes\com\example\firstjni目录,将cmd命令定位到FirstJni\bin\classes目录,输入”javah com.example.firstjni.JniClient“后回车,在FirstJni\bin\classes目录下就生成了C++头文件com_example_firstjni_JniClient.h

图片讲解如下:


com_example_firstjni_JniClient.h的文件内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_firstjni_JniClient */

#ifndef _Included_com_example_firstjni_JniClient
#define _Included_com_example_firstjni_JniClient
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_firstjni_JniClient
 * Method:    AddStr
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_firstjni_JniClient_AddStr
  (JNIEnv *, jclass, jstring, jstring);

/*
 * Class:     com_example_firstjni_JniClient
 * Method:    AddInt
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_example_firstjni_JniClient_AddInt
  (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif
3.在jni目录下新建一个Android.mk文件,其内容如下(关于mk文件需要注意,很重要,还有c和c++文件的mk文件还不一样,此处是调用c语言的mk文件,至于其他的怎么调用,这个自己去百度吧,在此就不多说了)

# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := FirstJni
LOCAL_SRC_FILES := com_example_firstjni_JniClient.c

include $(BUILD_SHARED_LIBRARY)

4. 将刚刚手动生成的com_example_firstjni_JniClient.h拷贝到FirstJni工程的jni目录下,

然后新建一个com_example_firstjni_JniClient.c文件完成头文件中函数的实现,其内容如下(本来想写两个方法的,现在只讲解第一个方法,返回一个字符串“HelloWorld from JNI ”,另一个方法是一个a+b的运算,方法写到这里,感兴趣的可以自己去研究):

com_example_firstjni_JniClient.c

#include "com_example_firstjni_JniClient.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef __cplusplus
extern "C"
{
#endif
/*
 * Class:     com_ndk_test_JniClient
 * Method:    AddStr
 * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_firstjni_JniClient_AddStr
  (JNIEnv *env, jclass arg, jstring instringA, jstring instringB)
{
    jstring str = (*env)->NewStringUTF(env, "I am chenyu, This is my first ubuntu jni!!!!");
    return str;
}

/*
* Class:     com_ndk_test_JniClient
* Method:    AddInt
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_firstjni_JniClient_AddInt
  (JNIEnv *env, jclass arg, jint a, jint b)
{
    return a + b;
}

#ifdef __cplusplus
}
#endif

此刻,当编辑com_ndk_test_JniClient.c并保存后,project下的—clean  一下工程,然后再去ndk-build项目,就可以生存响应的so文件


5.在MainActivity.java中完成对JniClient.java中函数的调用(首先静态加载动态链接so库):

package com.example.firstjni;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

	 public String string1 = "I am chenyu ";
	 public String string2 = "this is my first ubuntu jni";
	 public int a = 3;
	 public int b = 5;
	 static {
		 System.loadLibrary("FirstJni");
	 }

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
        TextView  tv = new TextView(this);
        tv.setText(JniClient.AddStr(string1, string2)+"3 + 5 = " + JniClient.AddInt(a, b));
        setContentView(tv);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle action bar item clicks here. The action bar will
		// automatically handle clicks on the Home/Up button, so long
		// as you specify a parent activity in AndroidManifest.xml.
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			return true;
		}
		return super.onOptionsItemSelected(item);
	}
}

6.运行FirstJni工程,就出现了上面的爆图。

五、demo已经上传,需要的猛搓下面的地址

ubuntu上面使用jni例子  http://download.csdn.net/detail/u011068702/9675559   











作者:u011068702 发表于2016/11/7 21:43:17 原文链接
阅读:34 评论:0 查看评论

《React-Native系列》React-Native实战系列博客汇总

$
0
0

从2016年7月份开始,坚持写ReactNative系列博客,记录工作中遇到的点滴。

今天把博客汇如下:

  1. 《React-Native系列》1、初探React-Native
  2. 《React-Native系列》2、RN与native交互与数据传递
  3. 《React-Native系列》3、RN与native交互之Callback、Promise
  4. 《React-Native系列》4、表单界面代码编写
  5. 《React-Native系列》5、RN实现弹出选择界面与动画效果
  6. 《React-Native系列》6、Navigator语法介绍及经典应用
  7. 《React-Native系列》7、bundle文件的加载和维护
  8. 《React-Native系列》8、RN如何打离线包
  9. 《React-Native系列》9、 Networking之fetch
  10. 《React-Native系列》10、 RN组件之Text和TextInput以及注意要点
  11. 《React-Native系列》11、 图解RN布局之FlexBox,三分钟上手写RN界面
  12. 《React-Native系列》12、 API模块之PixelRatio和Dimensions
  13. 《React-Native系列》13、 组件封装之Dialog(iOS和Android通用)
  14. 《React-Native系列》14、 RN学习之NodeJS
  15. 《React-Native系列》15、 RN之可触摸组件
  16. 《React-Native系列》16、 RN组件之ListView
  17. 《React-Native系列》17、 RN中this所引起的undefined is not an object错误
  18. 《React-Native系列》18、 RN之定时器Timer
  19. 《React-Native系列》19、 ListView组件之上拉刷新(iOS和Android通用)
  20. 《React-Native系列》20、 RN数据流之Flux概览
  21. 《React-Native系列》21、 解决RN在Android下不支持gif问题
  22. 《React-Native系列》22、 Flux框架Demo详解
  23. 《React-Native系列》23、 js实现下拉刷新效果(Android和iOS通用)
  24. 《React-Native系列》24、 结合Demo学习Redux框架
  25. 《React-Native系列》25、 详解Redux的connect方法
  26. 《React-Native系列》26、 ReactNative实现图片上传功能
  27. 《React-Native系列》27、 Redux的异步数据流
  28. 《React-Native系列》28、 RN之AsyncStorage
  29. 《React-Native系列》29、 RN组件之WebView
  30. 《React-Native系列》30、 RN组件间通信
  31. 《React-Native系列》31、 Fetch发送POST请求的坑与解决方案
  32. 《React-Native系列》32、 基于Fetch封装HTTPUtil工具类
  33. 《React-Native系列》33、 键盘遮挡问题处理
  34. 《React-Native系列》34、 ReactNative的那些坑
  35. 《React-Native系列》35、 RN在Android下支持gif的另一种方案
  36. 《React-Native系列》36、 ReactNative地图组件
  37. 《React-Native系列》37、 ReactNative百度地图开源组件使用
  38. 《React-Native系列》38、 ReactNative混合组件封装
  39. 《React-Native系列》39、 ReactNative之键盘Keyboard
  40. 《React-Native系列》40、 ReactNative之bundle文件瘦身
  41. 《React-Native系列》41、刨根问底Picker组件

作者:hsbirenjie 发表于2016/11/7 21:46:13 原文链接
阅读:2 评论:0 查看评论

基础篇章:React Native之Flexbox的讲解(Height and Width)

$
0
0

今天在讲解Flexbox之前,我们先讲解一下高度和宽度的问题。因为Height and Width的问题很简单,就不单独写一篇文章了。顺带说一下即可。

Height and Width

一个组件的高度和宽度,决定了它在屏幕上显示的大小。

固定尺寸

最简单的设置组件的尺寸的方法就是通过添加一个固定的宽度和高度。所有尺寸大小在React Native没有单位的,代表着独立的像素密度。

官网例子

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class FixedDimensionsBasics extends Component {
  render() {
    return (
      <View>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 100, height: 100, backgroundColor: 'skyblue'}} />
        <View style={{width: 150, height: 150, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('AwesomeProject', () => FixedDimensionsBasics);


效果图:

这样设置尺寸大小的方式,比较适合于要求不同的屏幕上显示相同大小的View或者组件。写固定的尺寸大小,死值。

弹性宽高

我们可以在组件样式中使用flex让组件根据可用空间动态的收缩和扩展。通常情况下我们可以使用flex: 1,告诉某个组件来填充剩余的所有的空间,如果是多个组件的话,则是所有的这些组件去平分父容器中的剩余的所有空间。。如果这些并列的子组件的flex值不一样,则谁的值更大,谁占据剩余空间的比例就更大(跟我们android中weight的用法差不多)。

官网例子

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class FlexDimensionsBasics extends Component {
  render() {
    return (
      // Try removing the `flex: 1` on the parent View.
      // The parent will not have dimensions, so the children can't expand.
      // What if you add `height: 300` instead of `flex: 1`?
      <View style={{flex: 1}}>
        <View style={{flex: 1, backgroundColor: 'powderblue'}} />
        <View style={{flex: 2, backgroundColor: 'skyblue'}} />
        <View style={{flex: 3, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('AwesomeProject', () => FlexDimensionsBasics);

效果图:

关于高度和宽度就讲这些吧,其实内容都是翻译与官网的docs,地址:
https://facebook.github.io/react-native/docs/height-and-width.html#height-and-width

Flexbox

一个组件可以使用Flexbox指定其子组件或元素之间的布局。Flexbox旨在为不同的屏幕上提供一致的布局。

通常情况下,我们结合使用flexDirection、alignItems和 justifyContent三个样式属性就已经能够实现我们所需的布局。

注意:Flexbox在React Native的工作原理和使用方式与css在web上的方式基本一样,当然也有一些例外:比如flexDirection的默认值是column而不是row,alignItems的默认值是stretch而不是flex-start,以及flex只能指定一个数字值。

Flex Direction

向一个组件的样式中添加Flex Direction可以决定一个布局的主轴。子元素应该沿着水平方向(row)排列,还是沿着竖直方向(column)排列呢?默认值是竖直(column)方向。

官网例子

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class FlexDirectionBasics extends Component {
  render() {
    return (
      // Try setting `flexDirection` to `column`.
      <View style={{flex: 1, flexDirection: 'row'}}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('AwesomeProject', () => FlexDirectionBasics);

效果图:

Justify Content

向组件的样式中添加Justify Content可以决定其子元素沿着主轴的排列方式。子元素应该分布在主轴的开始端,还是中间,最后,还是均匀分布?可用的选项有:flex-start、center、flex-end、space-around以及space-between。

  • flex-start:弹性盒子元素将与行起始位置对齐。该行的第一个子元素的主起始位置的边界将与该行的主起始位置的边界对齐,同时所有后续的伸缩盒项目与其前一个项目对齐。
  • flex-end:弹性盒子元素将与行结束位置对齐。该行的第一个子元素的主结束位置的边界将与该行的主结束位置的边界对齐,同时所有后续的伸缩盒项目与其前一个项目对齐。
  • center:弹性盒子元素将与行中间位置对齐。该行的子元素将相互对齐并在行中居中对齐,同时第一个元素与行的主起始位置的边距等同与最后一个元素与行的主结束位置的边距(如果剩余空间是负数,则保持两端相等长度的溢出)。
  • space-between:弹性盒子元素会均匀分布在行里。如果最左边的剩余空间是负数,或该行只有一个子元素,则该值等效于’flex-start’。在其它情况下,第一个元素的边界与行的主起始位置的边界对齐,同时最后一个元素的边界与行的主结束位置的边距对齐,而剩余的伸缩盒项目则平均分布,并确保两两之间的空白空间相等。
  • space-around:弹性盒子元素会均匀分布在行里,两端保留子元素与子元素之间间距大小的一半。如果最左边的剩余空间是负数,或该行只有一个伸缩盒项目,则该值等效于’center’。在其它情况下,伸缩盒项目则平均分布,并确保两两之间的空白空间相等,同时第一个元素前的空间以及最后一个元素后的空间为其他空白空间的一半。

官网例子

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class JustifyContentBasics extends Component {
  render() {
    return (
      // Try setting `justifyContent` to `center`.
      // Try setting `flexDirection` to `row`.
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'space-between',
      }}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('AwesomeProject', () => JustifyContentBasics);

效果图:

Align Items

向组件的样式(style)中添加alignItems可以决定其子元素沿着次轴(就是与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式。子元素是应该靠近次轴的开始端还是中间,还是末端,亦或是拉伸来填补呢?可用选项有:flex-start、center、flex-end以及stretch。

  • flex-start:弹性盒子元素的次轴起始位置的边界紧靠该行的次轴起始边界。
  • flex-end:弹性盒子元素的次轴起始位置的边界紧靠住该行的次轴结束边界。
  • center:弹性盒子元素在该行的次轴)上居中放置。(如果该行的尺寸小于弹性盒子元素的尺寸,则会向两个方向溢出相同的长度)。
  • stretch:如果指定次轴大小的属性值为’auto’,则其值会使项目的边距盒的尺寸尽可能接近所在行的尺寸,但同时会遵照’min/max-width/height’属性的限制。

注意:要使stretch选项生效的话,子元素在次轴方向上不能有固定的尺寸。在下面的例子中:只有将子元素样式中的width: 50去掉之后,alignItems: ‘stretch’才能生效。

官网例子

import React, { Component } from 'react';
import { AppRegistry, View } from 'react-native';

class AlignItemsBasics extends Component {
  render() {
    return (
      // Try setting `alignItems` to 'flex-start'
      // Try setting `justifyContent` to `flex-end`.
      // Try setting `flexDirection` to `row`.
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'center',
        alignItems: 'center',
      }}>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
      </View>
    );
  }
};

AppRegistry.registerComponent('AwesomeProject', () => AlignItemsBasics);

效果图:

文章翻译并参考于Flexbox官方文档,地址:
https://facebook.github.io/react-native/docs/flexbox.html

好了,到这里关于Flexbox的讲解就讲到这里了,关于Flexbox运用,上面的例子只是冰山一角,要想真正熟练掌握,还得靠自己亲自动手去写,去实践,才能够真正来理解各个属性的意思。赶紧动手去实践吧。

欢迎关注微信公众号:非著名程序员(smart_android),每天每周定时推送原创技术文章。所有技术文章, 均会在微信订阅号首发,关注微信公众号可以及时获得技术文章推送。

原文地址:http://godcoder.me

作者:loongggdroid 发表于2016/11/8 19:59:43 原文链接
阅读:33 评论:0 查看评论

Unity基础包 刚体FPS RigidbodyFirstPersonController 脚本研究

$
0
0

版本:unity 5.3.4  语言:C#

 

今天又研究了一个脚本。

 

刚体的第一人称,不过这个脚本没有像之前的FPS脚本一样,加那么多另外的脚本,唯一一个就是MouseLook,这个脚本我们之前分析过了,就不再赘述了。


所以整个看下来都是一个比较完整的FPS模型,个人喜欢用这个刚体实现,因为以后用其他什么力都比较方便。

 

下面上代码:

// 刚体FPS移动主脚本,用刚体和胶囊组件代替了Character Controller组件
// 提醒一下,这个脚本有两百多行,稍微有点混乱的,建议先看看都有些什么属性留个印象,然后按照Start、Update、FixedUpdate一步步按照逻辑执行顺寻看过去会轻松很多
[RequireComponent(typeof (Rigidbody))]
[RequireComponent(typeof (CapsuleCollider))]
public class RigidbodyFirstPersonController : MonoBehaviour
{
    // 内部类,移动设置
    [Serializable]
    public class MovementSettings
    {
        public float ForwardSpeed = 8.0f;   // 向前走的最大速度
        public float BackwardSpeed = 4.0f;  // 后退的最大速度
        public float StrafeSpeed = 4.0f;    // 倾斜走的最大速度
        public float RunMultiplier = 2.0f;   // 奔跑速度跟行走速度的比例

	    public KeyCode RunKey = KeyCode.LeftShift;  //跑步的按键

        public float JumpForce = 30f;   //起跳的力

        public AnimationCurve SlopeCurveModifier = new AnimationCurve(new Keyframe(-90.0f, 1.0f), new Keyframe(0.0f, 1.0f), new Keyframe(90.0f, 0.0f));  //斜面移动的调节曲线,默认是0到90,从1减到0
        [HideInInspector] public float CurrentTargetSpeed = 8f; //当前的目标速度

#if !MOBILE_INPUT
        private bool m_Running; //当前是否在跑步状态,只有在非手机平台上有效
#endif
        // 更新目标的速度。
        public void UpdateDesiredTargetSpeed(Vector2 input)
        {
	        if (input == Vector2.zero) return;  //没有输入不处理

			if (input.x > 0 || input.x < 0)     //x轴(即水平方向)有输入,速度为倾斜速度
			{
				CurrentTargetSpeed = StrafeSpeed;
			}
			if (input.y < 0)    //y小于0,速度为后退速度
			{
				CurrentTargetSpeed = BackwardSpeed;
			}
			if (input.y > 0)    //y大于0,前进速度,这个写在最后,使其优先级最高,即如果即倾斜又前进,则为前进速度
			{
				CurrentTargetSpeed = ForwardSpeed;
			}
#if !MOBILE_INPUT
	        if (Input.GetKey(RunKey))   //奔跑状态下,速度默认乘以2
	        {
		        CurrentTargetSpeed *= RunMultiplier;
		        m_Running = true;
	        }
	        else
	        {
		        m_Running = false;
	        }
#endif
        }

#if !MOBILE_INPUT
        public bool Running
        {
            get { return m_Running; }
        }
#endif
    }

    // 内部类,高级设置
    [Serializable]
    public class AdvancedSettings
    {
        public float groundCheckDistance = 0.01f; //判断当前是否着陆离地面的距离(设置为0.01f应该是比较好的)
        public float stickToGroundHelperDistance = 0.5f; // stops the character //停止角色运动的距离
        public float slowDownRate = 20f; //当没有输入时,控制器缓慢停下时的比例
        public bool airControl; //当角色在空中时,用户是否能控制角色
        [Tooltip("set it to 0.1 or more if you get stuck in wall")] //鼠标悬停在下面属性上会显示该提示,当然中文是不行的
        public float shellOffset; //减小半径用于减少墙面对卡住角色的影响(值为0.1f是比较好的)
    }

    // 这边开始是主类的代码
    public Camera cam;  //当前的相机,组件组织结构跟非刚体的PFS脚本是一样的,分为角色层和镜头层
    public MovementSettings movementSettings = new MovementSettings();  //内部类,移动设置
    public MouseLook mouseLook = new MouseLook();   //我们的老朋友,鼠标控制角色和镜头旋转
    public AdvancedSettings advancedSettings = new AdvancedSettings();  //内部类,高级设置

    private Rigidbody m_RigidBody;  //角色的刚体
    private CapsuleCollider m_Capsule;  //胶囊碰撞体
    private float m_YRotation;  //y轴的旋转,这个变量好像并没有什么用
    private Vector3 m_GroundContactNormal;      //与地面接触的法线向量
    private bool m_Jump, m_PreviouslyGrounded, m_Jumping, m_IsGrounded; //当前跳跃、着陆等的一些状态bool变量

    // 获取当前的速度
    public Vector3 Velocity
    {
        get { return m_RigidBody.velocity; }
    }

    // 当前的状态是否着陆了
    public bool Grounded
    {
        get { return m_IsGrounded; }
    }

    // 是否在跳跃中
    public bool Jumping
    {
        get { return m_Jumping; }
    }

    // 是否在奔跑状态
    public bool Running
    {
        get
        {
#if !MOBILE_INPUT  //只有在非手机平台上才能奔跑
			return movementSettings.Running;
#else
	        return false;
#endif
        }
    }

    // 初始化
    private void Start()
    {
        m_RigidBody = GetComponent<Rigidbody>();
        m_Capsule = GetComponent<CapsuleCollider>();
        mouseLook.Init (transform, cam.transform);  //给入角色层transform和相机transform
    }

    // 每帧更新
    private void Update()
    {
        RotateView();   //旋转角色和镜头

        if (CrossPlatformInputManager.GetButtonDown("Jump") && !m_Jump)
        {
            m_Jump = true;
        }
    }

    // 固定更新
    private void FixedUpdate()
    {
        GroundCheck();  //判断当前是否在陆地上
        Vector2 input = GetInput(); //获取输入

        // input有输入,并且可以控制的情况,处理速度
        if ((Mathf.Abs(input.x) > float.Epsilon || Mathf.Abs(input.y) > float.Epsilon) && (advancedSettings.airControl || m_IsGrounded))
        {
            // 总是以Camera的正方向作为前进方向
            Vector3 desiredMove = cam.transform.forward*input.y + cam.transform.right*input.x;
            desiredMove = Vector3.ProjectOnPlane(desiredMove, m_GroundContactNormal).normalized;    //将速度投射到法线的斜面,并将其单位化

            desiredMove.x = desiredMove.x*movementSettings.CurrentTargetSpeed;  //计算各轴的速度
            desiredMove.z = desiredMove.z*movementSettings.CurrentTargetSpeed;
            desiredMove.y = desiredMove.y*movementSettings.CurrentTargetSpeed;

            if (m_RigidBody.velocity.sqrMagnitude <
                (movementSettings.CurrentTargetSpeed*movementSettings.CurrentTargetSpeed))  //当前速度小于目标速度,则加个冲力,我最初的实现是直接该刚体的速度,导致损失了其他的力,比如重力、炸弹爆炸对角色的冲力
            {
                m_RigidBody.AddForce(desiredMove*SlopeMultiplier(), ForceMode.Impulse); //斜率跟冲力做计算
            }
        }

        if (m_IsGrounded)
        {
            m_RigidBody.drag = 5f;  //在地上把空气阻力设置为5f

            // 要跳跃了,空气阻力设置为0,着陆状态到跳跃状态的一个中间状态
            if (m_Jump) 
            {
                m_RigidBody.drag = 0f;
                m_RigidBody.velocity = new Vector3(m_RigidBody.velocity.x, 0f, m_RigidBody.velocity.z); //保存x、z轴速度
                m_RigidBody.AddForce(new Vector3(0f, movementSettings.JumpForce, 0f), ForceMode.Impulse);   //在y轴上使用一个冲力
                m_Jumping = true;   //正式进入jump状态
            }

            // 没有速度,不在跳跃,并且刚体的速度小于1,则使刚体休眠,节约cpu运算
            if (!m_Jumping && Mathf.Abs(input.x) < float.Epsilon && Mathf.Abs(input.y) < float.Epsilon && m_RigidBody.velocity.magnitude < 1f)
            {
                m_RigidBody.Sleep();
            }
        }
        else
        {
            m_RigidBody.drag = 0f;  //空中,空气阻力设置为0,否则跳得很矮
            if (m_PreviouslyGrounded && !m_Jumping)
            {
                StickToGroundHelper();  //跳跃完成后把刚体粘到地上
            }
        }
        m_Jump = false;
    }

    // 斜率跟冲力做计算
    private float SlopeMultiplier()
    {
        float angle = Vector3.Angle(m_GroundContactNormal, Vector3.up); //计算地面斜面法线和z轴正方向的角度
        return movementSettings.SlopeCurveModifier.Evaluate(angle); //根据斜率曲线,计算出当前应该给与物体冲力的比例,斜面斜率到90的时候,即使按了方向键也没有冲力
    }

    // 粘到地上助手
    private void StickToGroundHelper()
    {
        RaycastHit hitInfo;
        if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo,
                                ((m_Capsule.height/2f) - m_Capsule.radius) +
                                advancedSettings.stickToGroundHelperDistance, ~0, QueryTriggerInteraction.Ignore))   //丢球到地上
        {
            if (Mathf.Abs(Vector3.Angle(hitInfo.normal, Vector3.up)) < 85f)
            {
                m_RigidBody.velocity = Vector3.ProjectOnPlane(m_RigidBody.velocity, hitInfo.normal);    //刚体的速度映射到斜面上
            }
        }
    }

    // 获取输入
    private Vector2 GetInput()
    {
            
        Vector2 input = new Vector2
            {
                x = CrossPlatformInputManager.GetAxis("Horizontal"),
                y = CrossPlatformInputManager.GetAxis("Vertical")
            };
		movementSettings.UpdateDesiredTargetSpeed(input);
        return input;
    }

    // 旋转镜头和角色
    private void RotateView()
    {
        // 当游戏暂停时忽略鼠标移动的影响
        if (Mathf.Abs(Time.timeScale) < float.Epsilon) return;
        /* 这边引用宏哥1995的总结:
            * 1.timeScale不影响Update和LateUpdate,会影响FixedUpdate
            * 2.timeScale不影响Time.realtimeSinceStartup,会影响Time.timeSinceLevelLoad和Time.time
            * 3.timeScale不影响Time.fixedDeltaTime和Time.unscaleDeltaTime,会影响Time.deltaTime
            */

        // 在旋转之前获取一下旋转角度
        float oldYRotation = transform.eulerAngles.y;

        mouseLook.LookRotation (transform, cam.transform);  //MouseLook处理角色镜头旋转

        // 只有在方向键可以控制角色时,才进入判断,其他情况反正xz平面速度为0嘛
        if (m_IsGrounded || advancedSettings.airControl)
        {
            // 旋转刚体的速度,以符合新的角色旋转角度
            Quaternion velRotation = Quaternion.AngleAxis(transform.eulerAngles.y - oldYRotation, Vector3.up);  //获取旋转角度差
            m_RigidBody.velocity = velRotation*m_RigidBody.velocity;    //相乘即旋转增加该角度,这个之前计算出欧拉角相乘是一样的(严格来说是用一个欧拉角来计算出一个Quaternion对象)
        }
    }

    // 用一个球从胶囊中间丢下,看看是否碰撞陆地,来判断是否在陆地上,这跟非刚体的FPS脚本是一样的
    private void GroundCheck()
    {
        m_PreviouslyGrounded = m_IsGrounded;
        RaycastHit hitInfo;
        if (Physics.SphereCast(transform.position, m_Capsule.radius * (1.0f - advancedSettings.shellOffset), Vector3.down, out hitInfo,
                                ((m_Capsule.height/2f) - m_Capsule.radius) + advancedSettings.groundCheckDistance, ~0, QueryTriggerInteraction.Ignore))  //有一些不同的是这边用到了几个高级属性,用于减少球的半径,增加投球的距离,使其倾向于判断胶囊底部的碰撞,而非侧边的碰撞
        {
            m_IsGrounded = true;
            m_GroundContactNormal = hitInfo.normal; //获取法线
        }
        else
        {
            m_IsGrounded = false;
            m_GroundContactNormal = Vector3.up;
        }

        // 前个固定一帧的状态是不在地上,现在在地上的情况,跳跃状态结束
        if (!m_PreviouslyGrounded && m_IsGrounded && m_Jumping)
        {
            m_Jumping = false;
        }
    }
}

嗯嗯,今天分析了4个脚本,感觉前途还很遥远。

 

下边是我自己的一些心情,没兴趣的读者玩家们可以跳过了。

 

很多时候描绘自己的梦想很轻松,自己想要成为什么,但实际上做起来又是另一会事,总会抱怨自己的环境有那些欠缺,但就是不肯自己动手做些什么,拖沓的病、以及大量的计划也让我应接不暇,然而这些东西也只能自己在博文中发一发,说自己未来一定有一个团队的,但是……

 

刚刚看了一圈独立游戏的开发者,发现那些游戏我大部分都玩过,说来惭愧,有些我自己都奉为神作的作品自己一分钱也没有花。

 

多数作品是一些团队完成的,分工明确、目标一致,资金上可能缺一点,不过挺过来的相对都是成功的。有几句话挺触动我的,说是团队的核心成员基本上是最要好的朋友,有不同的技能,但有相同的梦想。

 

想起一个用虚幻引擎做鹿的一个游戏的小青年,初中?反正10几岁吧,就跟着自己的同学组建了一个三人的团队,令人惊叹。

 

感慨自己没有这样一起怀有相同梦想人的同时,认真思考,可能最终只能一个人来做。

 

不过美术、音乐、策划都是相当需要时间的一件事情,自己除了程序以外又什么也没接触过。

 

我自己的创作作品的计划一拖再拖,美其名曰没有时间,现在还有很多前置的计划,不过谁知道是不是内心的恐惧抓住了我,让我不敢开始。我经历过太多太多的失败了,从来也不是那些成功者的一员,或许将来会好一些吧。

 

每次借口说自己没认真做,但是认真做了就能成功吗?我的眼光、能力和性格始终摆在那里,不进不退。我觉得最现实的就是磨练好自己unity技术,拿个还算不错的工资,能够自己生活就行了,过得平凡一些。

 

有时候确实会疑惑,为什么没有超人的能力,还成天想着那些不切实际的梦想。如果没有会不会幸福很多。


作者:u012632851 发表于2016/11/8 20:08:36 原文链接
阅读:43 评论:1 查看评论

html5 webview对象

$
0
0

Webview模块管理应用窗口界面,实现多窗口的逻辑控制管理操作。通过plus.webview可获取应用界面管理对象。
方法:

all: 获取所有Webview窗口
close: 关闭Webview窗口
create: 创建新的Webview窗口
currentWebview: 获取当前窗口的WebviewObject对象
getWebviewById: 查找指定标识的WebviewObject窗口
getLaunchWebview: 获取应用首页WebviewObject窗口对象
getTopWebview: 获取应用显示栈顶的WebviewObject窗口对象
hide: 隐藏Webview窗口
open: 创建并打开Webview窗口
show: 显示Webview窗口
defaultHardwareAccelerated: 获取Webview默认是否开启硬件加速

对象:

AnimationTypeShow: 一组用于定义页面或控件显示动画效果
AnimationTypeClose: 一组用于定义页面或控件关闭的动画效果
WebviewObject: Webview窗口对象,用于操作加载HTML页面的窗口
WebviewBounceStyle: Webview窗口回弹样式
WebviewDock: 原生控件在窗口中停靠的方式
WebviewEvent: Webview窗口事件
WebviewRefreshStyle: Webview窗口下拉刷新样式
WebviewPosition: 原生控件在窗口中显示的位置
WebviewStyles: JSON对象,原生窗口设置参数的对象
WebviewExtraOptions: JSON对象,原生窗口扩展参数
WebviewTransform: 一组用于定义页面或控件变形的属性
WebviewTransition: 一组用于定义页面或控件转换效果的属性
WebviewOverridResourceOptions: 拦截Webview窗口资源请求的参数
WebviewOverrideUrlOptions: 拦截Webview窗口URL请求的属性
WebviewListenResourceOptions: 监听Webview窗口资源加载的属性

回调方法:

BounceEventCallback: Webview窗口回弹事件的回调函数
EventCallback: Webview窗口事件的回调函数
PopGestureCallback: Webview窗口侧滑事件的回调函数
HistoryQueryCallback: 历史记录记录查询的回调函数
ListenResourceLoadingCallback: Webview窗口加载资源事件的回调函数
OverrideUrlLoadingCallback: Webview窗口拦截URL链接跳转的回调函数
TitleUpdateCallback: Webview窗口加载页面标题更新的回调函数
SuccessCallback: Webview窗口操作成功回调函数
ErrorCallback: Webview窗口操作失败回调函数

权限:

功能模块(permissions)

{
// ...
"permissions":{
    // ...
    "Webview": {
        "description": "窗口管理"
    }
}
}

all

获取所有Webview窗口

Array[WebviewObject] plus.webview.all();

说明:

获取应用中已创建的所有Webview窗口,包括所有未显示的Webview窗口。 返回WebviewObject对象在数组中按创建的先后顺序排列,即数组中第一个WebviewObject对象用是加载应用的入口页面。
参数:


返回值:
Array[ WebviewObject ] : 应用中创建的所有Webview窗口对象数组。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    // 获取所有Webview窗口
    var wvs=plus.webview.all();
    for(var i=0;i<wvs.length;i++){
        console.log("webview"+i+": "+wvs[i].getURL());
    }
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
    获取所有Webview窗口
    </body>
</html>

close

关闭Webview窗口

void plus.webview.close( id_wvobj, aniClose, duration, extras );

说明:

关闭已经打开的Webview窗口,需先获取窗口对象或窗口id,并可指定关闭窗口的动画及动画持续时间。
参数:

id_wvobj: ( String | WebviewObject ) 必选 要关闭Webview窗口id或窗口对象
若操作窗口对象已经关闭,则无任何效果。 使用窗口id时,则查找对应id的窗口,如果有多个相同id的窗口则操作最先打开的窗口,若没有查找到对应id的WebviewObject对象,则无任何效果。
aniClose: ( AnimationTypeClose ) 可选 关闭Webview窗口的动画效果
如果没有指定关闭窗口动画,则使用默认值“auto”,即使用显示时设置的窗口动画相对应的关闭动画。
duration: ( Number ) 可选 关闭Webview窗口动画的持续时间
单位为ms,如果没有设置则使用显示窗口动画时间。
extras: ( WebviewExtraOptions ) 可选 关闭Webview窗口扩展参数
可用于指定Webview窗口动画是否使用图片加速。

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 关闭自身窗口
function closeme(){
    var ws=plus.webview.currentWebview();
    plus.webview.close(ws);
}
    </script>
    </head>
    <body>
        关闭Webview窗口<br/>
        <button onclick="closeme()">close</button>
    </body>
</html>

create

创建新的Webview窗口

WebviewObject plus.webview.create( url, id, styles, extras );

说明:

创建Webview窗口,用于加载新的HTML页面,可通过styles设置Webview窗口的样式,创建完成后需要调用show方法才能将Webview窗口显示出来。
参数:

url: ( String ) 可选 新窗口加载的HTML页面地址
新打开Webview窗口要加载的HTML页面地址,可支持本地地址和网络地址。
id: ( String ) 可选 新窗口的标识
窗口标识可用于在其它页面中通过getWebviewById来查找指定的窗口,为了保持窗口标识的唯一性,应该避免使用相同的标识来创建多个Webview窗口。 如果传入无效的字符串则使用url参数作为WebviewObject窗口的id值。
styles: ( WebviewStyles ) 可选 创建Webview窗口的样式(如窗口宽、高、位置等信息)
extras: ( JSON ) 可选 创建Webview窗口的额外扩展参数
值为JSON类型,设置扩展参数后可以直接通过Webview的点(“.”)操作符获取扩展参数属性值,如: var w=plus.webview.create('url.html','id',{},{preload:"preload webview"}); // 可直接通过以下方法获取preload值 console.log(w.preload); // 输出值为“preload webview”

返回值:
WebviewObject : Webview窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 创建并显示新窗口
function create(){
    var w = plus.webview.create( "http://weibo.com/dhnetwork" );
    w.show(); // 显示窗口
}
    </script>
    </head>
    <body>
        创建新的Webview窗口<br/>
        <button onclick="create()">Create</button>
    </body>
</html>

currentWebview

获取当前窗口的WebviewObject
WebviewObject plus.webview.currentWebview();

说明:

获取当前页面所属的Webview窗口对象。
参数:


返回值:
WebviewObject : Webview窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    var ws=plus.webview.currentWebview();
    console.log( "当前Webview窗口:"+ws.getURL() );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        获取自身Webview窗口
    </body>
</html>

getWebviewById

查找指定标识的WebviewObject窗口

WebviewObject plus.webview.getWebviewById( id );

说明:

在已创建的窗口列表中查找指定标识的Webview窗口并返回。 若没有查找到指定标识的窗口则返回null,若存在多个相同标识的Webview窗口,则返回第一个创建的Webview窗口。 如果要获取应用入口页面所属的Webview窗口,其标识为应用的%APPID%,可通过plus.runtime.appid获取。
参数:

id: ( String ) 必选 要查找的Webview窗口标识

返回值:
WebviewObject : WebviewObject窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    // 查找应用首页窗口对象
    var h=plus.webview.getWebviewById( plus.runtime.appid );
    console.log( "应用首页Webview窗口:"+h.getURL() );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        查找指定标识的窗口
    </body>
</html>

getLaunchWebview

获取应用首页WebviewObject窗口对象

WebviewObject plus.webview.getLaunchWebview();

参数:


返回值:
WebviewObject : WebviewObject窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    // 获取应用首页窗口对象
    var h=plus.webview.getLaunchWebview();
    console.log( "应用首页Webview窗口:"+h.getURL() );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        获取应用首页WebviewObject窗口对象
    </body>
</html>

getTopWebview

获取应用显示栈顶的WebviewObject窗口对象

WebviewObject plus.webview.getTopWebview();

参数:


返回值:
WebviewObject : WebviewObject窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    // 获取应用首页窗口对象
    var h=plus.webview.getTopWebview();
    console.log( "应用显示栈顶的Webview窗口:"+h.getURL() );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        获取应用显示栈顶的WebviewObject窗口对象
    </body>
</html>

hide

隐藏Webview窗口

void plus.webview.hide( id_wvobj, aniHide, duration, extras );

说明:

根据指定的WebviewObject对象或id隐藏Webview窗口,使得窗口不可见。
参数:

id_wvobj: ( String | WebviewObject ) 必选 要隐藏的Webview窗口id或窗口对象
使用窗口对象时,若窗口对象已经隐藏,则无任何效果。 使用窗口id时,则查找对应id的窗口,如果有多个相同id的窗口则操作最先打开的,若没有查找到对应id的WebviewObject对象,则无任何效果。
aniHide: ( AnimationTypeClose ) 可选 隐藏Webview窗口的动画效果
如果没有指定窗口动画,则使用默认动画效果“none”。
duration: ( Number ) 可选 隐藏Webview窗口动画的持续时间
单位为ms,如果没有设置则使用默认窗口动画时间。
extras: ( WebviewExtraOptions ) 可选 隐藏Webview窗口扩展参数
可用于指定Webview窗口动画是否使用图片加速。

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 隐藏自身窗口
function hideeme(){
    plus.webview.hide( plus.webview.currentWebview() );
}
    </script>
    </head>
    <body>
        隐藏Webview窗口<br/>
        <button onclick="hideeme()">Hide</button>
    </body>
</html>

open

创建并打开Webview窗口

WebviewObject plus.webview.open( url, id, styles, aniShow, duration, showedCB );

说明:

创建并显示Webview窗口,用于加载新的HTML页面,可通过styles设置Webview窗口的样式,创建完成后自动将Webview窗口显示出来。
参数:

url: ( String ) 可选 打开窗口加载的HTML页面地址
新打开Webview窗口要加载的HTML页面地址,可支持本地地址和网络地址。
id: ( String ) 可选 打开窗口的标识
窗口标识可用于在其它页面中通过getWebviewById来查找指定的窗口,为了保持窗口标识的唯一性,应该避免使用相同的标识来创建多个Webview窗口。 如果传入无效的字符串则使用url参数作为WebviewObject窗口的id值。
styles: ( WebviewStyles ) 可选 创建Webview窗口的样式(如窗口宽、高、位置等信息)
aniShow: ( AnimationTypeShow ) 可选 显示Webview窗口的动画效果
如果没有指定窗口动画,则使用默认无动画效果“none”。
duration: ( Number ) 可选 显示Webview窗口动画的持续时间
单位为ms,如果没有设置则使用默认窗口动画时间600ms。
showedCB: ( SuccessCallback ) 可选 Webview窗口显示完成的回调函数
当指定Webview窗口显示动画执行完毕时触发回调函数,窗口无动画效果(如"none"动画效果)时也会触发此回调。

返回值:
WebviewObject : WebviewObject窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 创建并显示新窗口
function openWebview(){
    var w = plus.webview.open( "http://weibo.com/dhnetwork" );
}
    </script>
    </head>
    <body>
        打开Webview窗口<br/>
        <button onclick="openWebview()">Open</button>
    </body>
</html>

show

显示Webview窗口

void plus.webview.show( id_wvobj, aniShow, duration, showedCB, extras );

说明:

显示已创建或隐藏的Webview窗口,需先获取窗口对象或窗口id,并可指定显示窗口的动画及动画持续时间。
参数:

id_wvobj: ( String | WebviewObject ) 必选 要显示Webview窗口id或窗口对象
若操作Webview窗口对象显示,则无任何效果。 使用窗口id时,则查找对应id的窗口,如果有多个相同id的窗口则操作最先创建的窗口,若没有查找到对应id的WebviewObject对象,则无任何效果。
aniShow: ( AnimationTypeShow ) 可选 显示Webview窗口的动画效果
如果没有指定窗口动画类型,则使用默认值“auto”,即自动选择上一次显示窗口的动画效果,如果之前没有显示过,则使用“none”动画效果。
duration: ( Number ) 可选 显示Webview窗口动画的持续时间
单位为ms,如果没有设置则使用默认窗口动画时间600ms。
showedCB: ( SuccessCallback ) 可选 Webview窗口显示完成的回调函数
当指定Webview窗口显示动画执行完毕时触发回调函数,窗口无动画效果(如"none"动画效果)时也会触发此回调。
extras: ( WebviewExtraOptions ) 可选 显示Webview窗口扩展参数
可用于指定Webview窗口动画是否使用图片加速。

返回值:
WebviewObject : Webview窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 创建并显示新窗口
function create(){
    var w = plus.webview.create( "http://weibo.com/dhnetwork" );
    plus.webview.show( w ); // 显示窗口
}
    </script>
    </head>
    <body>
        显示Webview窗口<br/>
        <button onclick="create()">Create</button>
    </body>
</html>

defaultHardwareAccelerated

获取Webview默认是否开启硬件加速

Boolean plus.webview.defaultHardwareAccelerated();

说明:

由于不同设备对硬件加速的支持情况存在差异,开启硬件加速能加速HTML页面的渲染,但也会消耗更多的系统资源,从而导致在部分设备上可能出现闪屏、发虚、分块渲染等问题, 因此5+ Runtime会根据设备实际支持情况自动选择是否开启硬件加速。 关闭硬件加速则可能会导致Webview页面无法支持Video标签播放视频等问题,如果在特定情况下需要调整修改默认开启硬件加速的行为,则可通过plus.webview.defaultHardwareAccelerated()方法获取当前设备默认是否开启硬件加速状态,从而决定是否需要显式开启或关闭指定Webview的硬件加速功能(通过WebviewStyles的hardwareAccelerated属性设置)。
参数:


返回值:
Boolean : Webview窗口默认开启硬件加速则返回true,否则返回false。
平台支持:

Android - 2.3+ (支持): 返回当前设备默认是否开启硬件加速。
iOS - 5.1+ (不支持): 返回固定值true。

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 创建新窗口并设置开启硬件加速
function create(){
    var styles={};
    // 在Android5以上设备,如果默认没有开启硬件加速,则强制设置开启
    if(!plus.webview.defaultHardwareAccelerated()&&parseInt(plus.os.version)>=5){
        styles.hardwareAccelerated=true;
    }
    var w = plus.webview.create( "http://weibo.com/dhnetwork", "test", styles );
    plus.webview.show( w ); // 显示窗口
}
    </script>
    </head>
    <body>
        开启硬件加速显示Webview窗口<br/>
        <button onclick="create()">Create</button>
    </body>
</html>

AnimationTypeShow

一组用于定义页面或控件显示动画效果
常量:

"auto": (String 类型 )自动选择动画效果

自动选择动画效果,使用上次显示窗口设置的动画效果,如果是第一次显示则默认动画效果“none”。
"none": (String 类型 )无动画效果

立即显示页面,无任何动画效果,页面显示默认的动画效果。 此效果忽略动画时间参数,立即显示。 对应关闭动画"none"。
"slide-in-right": (String 类型 )从右侧横向滑动效果

页面从屏幕右侧外向内横向滑动显示。 对应关闭动画"slide-out-right"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"slide-in-left": (String 类型 )从左侧横向滑动效果

页面从屏幕左侧向右横向滑动显示。 对应关闭动画"slide-out-left"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"slide-in-top": (String 类型 )从上侧竖向滑动效果

页面从屏幕上侧向下竖向滑动显示。 对应关闭动画"slide-out-top"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"slide-in-bottom": (String 类型 )从下侧竖向滑动效果

页面从屏幕下侧向上竖向滑动显示。 对应关闭动画"slide-out-bottom"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"fade-in": (String 类型 )从透明到不透明逐渐显示效果

页面从完全透明到不透明逐渐显示。 对应关闭动画"fade-out"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"zoom-out": (String 类型 )从小到大逐渐放大显示效果

页面在屏幕中间从小到大逐渐放大显示。 对应关闭动画"zoom-in"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为100ms。
    iOS - 5.1.1+ (支持): 默认动画时间为100ms。
"zoom-fade-out": (String 类型 )从小到大逐渐放大并且从透明到不透明逐渐显示效果

页面在屏幕中间从小到大逐渐放大并且从透明到不透明逐渐显示。 对应关闭动画"zoom-fade-in"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为100ms。
    iOS - 5.1.1+ (支持): 默认动画时间为100ms。
"pop-in": (String 类型 )从右侧平移入栈动画效果

页面从屏幕右侧滑入显示,同时上一个页面带阴影效果从屏幕左侧滑出隐藏。 对应关闭动画"pop-out"。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。 此动画是新开窗口侧滑挤压当前屏幕窗口特效,必须是两个Webview窗口的组合动画, 如果当前屏幕已显示多个Webview窗口,则显示新窗口不支持此动画类型,自动转成“slide-in-right”。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。

AnimationTypeClose

一组用于定义页面或控件关闭的动画效果
常量:

"auto": (String 类型 )自动选择动画效果

自动选择显示窗口相对于的动画效果。
"none": (String 类型 )无动画

立即关闭页面,无任何动画效果。 此效果忽略动画时间参数,立即关闭。
"slide-out-right": (String 类型 )横向向右侧滑出屏幕动画

页面从屏幕中横向向右侧滑动到屏幕外关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"slide-out-left": (String 类型 )横向向左侧滑出屏幕动画

页面从屏幕中横向向左侧滑动到屏幕外关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"slide-out-top": (String 类型 )竖向向上侧滑出屏幕动画

页面从屏幕中竖向向上侧滑动到屏幕外关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"slide-out-bottom": (String 类型 )竖向向下侧滑出屏幕动画

页面从屏幕中竖向向下侧滑动到屏幕外关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"fade-out": (String 类型 )从不透明到透明逐渐隐藏动画

页面从不透明到透明逐渐隐藏关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。
"zoom-in": (String 类型 )从大逐渐缩小关闭动画

页面逐渐向页面中心缩小关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为100ms。
    iOS - 5.1.1+ (支持): 默认动画时间为100ms。
"zoom-fade-in": (String 类型 )从大逐渐缩小并且从不透明到透明逐渐隐藏关闭动画

页面逐渐向页面中心缩小并且从不透明到透明逐渐隐藏关闭。
平台支持
    Android - 2.2+ (支持): 默认动画时间为100ms。
    iOS - 5.1.1+ (支持): 默认动画时间为100ms。
"pop-out": (String 类型 )从右侧平移出栈动画效果

页面从屏幕右侧滑出消失,同时上一个页面带阴影效果从屏幕左侧滑入显示。
平台支持
    Android - 2.2+ (支持): 默认动画时间为200ms。
    iOS - 5.1.1+ (支持): 默认动画时间为300ms。

WebviewObject

Webview窗口对象,用于操作加载HTML页面的窗口
属性:

id: Webview窗口的标识

方法:

addEventListener: 添加事件监听器
append: 在Webview窗口中添加子窗口
appendJsFile: 添加Webview窗口预加载js文件
back: 后退到上次加载的页面
canBack: 查询Webview窗口是否可后退
canForward: 查询Webview窗口是否可前进
children: 获取Webview窗口的所有子Webview窗口
clear: 清空原生Webview窗口加载的内容
close: 关闭Webview窗口
draw: 截屏绘制
evalJS: 在Webview窗口中执行JS脚本
forward: 前进到上次加载的页面
getStyle: 获取Webview窗口的样式
getTitle: 获取Webview窗口加载HTML页面的标题
getURL: 获取Webview窗口加载HTML页面的地址
hide: 隐藏Webview窗口
isHardwareAccelerated: 查询Webview窗口是否开启硬件加速
isVisible: 查询Webview窗口是否可见
listenResourceLoading: 监听页面开始加载资源
loadData: 加载新HTML数据
loadURL: 加载新URL页面
nativeInstanceObject: 获取Webview窗口对象的原生(Native.JS)实例对象
opened: 获取在当前Webview窗口中创建的所有窗口
opener: 获取当前Webview窗口的创建者
overrideResourceRequest: 拦截Webview窗口的资源加载
overrideUrlLoading: 拦截Webview窗口的URL请求
parent: 获取当前Webview窗口的父窗口
reload: 重新加载Webview窗口显示的HTML页面
resetBounce: 重置Webview窗口的回弹位置
remove: 移除子Webview窗口
removeEventListener: 移除Webview窗口事件监听器
removeFromParent: 从父窗口中移除
setBounce: 设置Webview窗口的回弹效果
setBlockNetworkImage: 设置Webview窗口是否阻塞加载页面中使用的网络图片
setContentVisible: 设置HTML内容是否可见
setPullToRefresh: 设置Webview窗口的下拉刷新效果
setStyle: 设置Webview窗口的样式
setJsFile: 设置预加载的JS文件
show: 显示Webview窗口
stop: 停止加载HTML页面内容

事件:

onclose: Webview窗口关闭事件
onerror: Webview窗口错误事件
onloaded: Webview窗口页面加载完成事件
onloading: Webview窗口页面开始加载事件

id

Webview窗口的标识
说明:

String 类型

在打开或创建Webview窗口时设置,如果没有设置窗口标识,此属性值为undefined。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    // 获取自身webview窗口
    var ws=plus.webview.currentWebview();
    console.log( "窗口标识: "+ws.id );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        Webview窗口的标识<br/>
    </body>
</html>

addEventListener

添加事件监听器

wobj.addEventListener( event, listener, capture );

说明:

向Webview窗口添加事件监听器,当指定的事件发生时,将触发listener函数的执行。 可多次调用此方法向Webview添加多个监听器,当监听的事件发生时,将按照添加的先后顺序执行。
参数:

event: ( WebviewEvent ) 必选 Webview窗口事件类型
listener: ( EventCallback ) 必选 监听事件发生时执行的回调函数
capture: ( Boolean ) 可选 捕获事件流顺序,暂无效果

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

var nw=null;
// 监听Webview窗口事件
function eventTest() {
    if(nw){return;}
    var w=plus.nativeUI.showWaiting()
    // 打开新窗口
    nw=plus.webview.create( "http://weibo.com/dhnetwork" );
    nw.addEventListener( "loaded", function(){
        console.log( "New Window loaded!" );
        nw.show(); // 显示窗口
        w.close();
        w=null;
    }, false );
}
    </script>
    </head>
    <body>
        添加事件监听器<br/>
        <button onclick="eventTest()">Event Listener</button>
    </body>
</html>

append

在Webview窗口中添加子窗口

void wobj.append( webview );

说明:

将另一个Webview窗口作为子窗口添加到当前Webview窗口中,添加后其所有权归父Webview窗口,当父窗口关闭时子窗口自动关闭。
参数:

webview: ( plus.nativeObj.View | WebviewObject ) 必选 被添加的子Webview窗口或View控件对象
被添加的Webview窗口需通过plus.webview.create方法创建,并且不能调用其show方法进行显示。 父窗口显示时子窗口会自动显示,父窗口隐藏时子窗口也会自动隐藏。 被添加的View控件需通过new plus.nativeObj.View()创建,添加到Webview窗口后所有权一起转移(即Webview关闭后View控件也自动关闭)。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        在Webview窗口中添加子窗口
        <button onclick="plus.webview.currentWebview().close();">Back</button>
    </body>
</html>

appendJsFile

添加Webview窗口预加载js文件

wobj.appendJsFile( file );

说明:

对于一些网络HTML页面,在无法修改HTML页面时可通过此方法自动加载本地js文件。 当Webview窗口跳转到新页面时也会自动加载指定的js执行,添加多个js文件将按照添加的先后顺序执行。
参数:

file: ( String ) 必选 窗口预加载的js文件地址
js文件路径只支持本地文件,应该使用扩展相对路径类型的文件,如"_www/preload.js"。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
// H5 plus事件处理
function plusReady(){
    var nw=plus.webview.create("http://weibo.com/dhnetwork");
    nw.appendJsFile("_www/preload.js");
    nw.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        添加Webview窗口预加载js文件
    </body>
</html>

back

后退到上次加载的页面

void wobj.back()

说明:

Webview窗口历史记录操作,后退到窗口上次加载的HTML页面。 如果窗口历史记录中没有可后退的页面则不触发任何操作。
参数:


返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 返回上次页面
function goBack() {
    embed.back();
}
// 前进到上次页面
function goForward() {
    embed.forward();
}
    </script>
    </head>
    <body>
        后退到上次加载的页面
        <button onclick="goBack()">Back</button>
        <button onclick="goForward()">Forward</button>
    </body>
</html>

canBack

查询Webview窗口是否可后退

void wobj.canBack( queryCallback );

说明:

Webview窗口历史记录查询操作,获取Webview是否可后退到历史加载的页面,结果通过queryCallback回调方法返回。
参数:

queryCallback: ( HistoryQueryCallback ) 必选 查询历史记录操作回调函数

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 是否可后退
function canBack() {
    embed.canBack( function(e){
        console.log( "是否可返回:"+e.canBack );
    });
}
    </script>
    </head>
    <body>
        查询Webview窗口是否可后退
        <button onclick="canBack()">canBack</button>
    </body>
</html>

canForward

查询Webview窗口是否可前进

void wobj.canForward( queryCallback );

说明:

Webview窗口历史记录查询操作,获取Webview是否可前进到历史加载的页面,结果通过queryCallback回调方法返回。
参数:

queryCallback: ( HistoryQueryCallback ) 必选 查询历史记录操作回调函数

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 是否可前进
function canForward() {
    embed.canForward( function(e){
        console.log( "是否可前进:"+e.canForward );
    });
}
    </script>
    </head>
    <body>
        查询Webview窗口是否可前进
        <button onclick="canForward()">canForward</button>
    </body>
</html>

children

获取Webview窗口的所有子Webview窗口

Array[WebviewObject] wobj.children();

说明:

获取添加到Webview窗口中的所有子Webview窗口,如果没有子Webview窗口则返回空数组。
参数:


返回值:
Array[ WebviewObject ] : 包含的子Webview窗口对象数组,没有则返回空数组。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 获取子Webview窗口
function listChildren() {
    var list=plus.webview.currentWebview().children();
    for(var i=0;i<list.length;i++){
        console.log( "Children["+i+"]: "+list[i].getURL() );
    }
}
    </script>
    </head>
    <body>
        获取Webview窗口的所有子Webview窗口
        <button onclick="listChildren()">Children</button>
    </body>
</html>

clear

清空原生Webview窗口加载的内容

void wobj.clear();

说明:

清除原生窗口的内容,用于重置原生窗口加载的内容,清除其加载的历史记录等内容。
参数:


返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 清空Webview窗口
function webviewClear() {
    embed.clear();
}
    </script>
    </head>
    <body>
        清空原生Webview窗口加载的内容
        <button onclick="webviewClear()">Clear</button>
    </body>
</html>

close

关闭Webview窗口

void wobj.close( aniClose, duration, extras );

说明:

关闭并销毁Webview窗口,可设置关闭动画和动画持续时间。
参数:

aniClose: ( AnimationTypeClose ) 可选 关闭Webview窗口动画效果
如果没有指定关闭窗口动画,则使用默认值“auto”,即使用显示时设置的窗口动画相对应的关闭动画。
duration: ( Number ) 可选 关闭Webview窗口的动画持续时间
单位为ms,默认为窗口show方法设定的动画时间。
extras: ( WebviewExtraOptions ) 可选 关闭Webview窗口扩展参数
可用于指定Webview窗口动画是否使用图片加速。

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 关闭窗口
function closeMe() {
    ws.close();
}
    </script>
    </head>
    <body>
        关闭Webview窗口
        <button onclick="closeMe()">Close</button>
    </body>
</html>

draw

截屏绘制

void wobj.draw( bitmap, successCallback, errorCallback );

说明:

将Webview窗口的可视区域截屏并绘制到Bitmap图片对象中。
参数:

bitmap: ( plus.nativeObj.Bitmap ) 可选 要绘制的图片对象
如果图片中已经存在内容则覆盖,如果截屏绘制失败则保留之前的图片内容。
successCallback: ( SuccessCallback ) 可选 截屏绘制操作成功回调
截屏绘制操作成功时调用。
errorCallback: ( NativeObjErrorCallback ) 必选 截屏绘制操作失败回调
截屏绘制操作失败时调用,并返回失败信息。

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 截屏绘制
var bitmap=null;
function captureWebview() {
    bitmap = new plus.nativeObj.Bitmap("test");
    // 将webview内容绘制到Bitmap对象中
    ws.draw(bitmap,function(){
        console.log('截屏绘制图片成功');
    },function(e){
        console.log('截屏绘制图片失败:'+JSON.stringify(e));
    });
}
    </script>
    </head>
    <body>
        截屏绘制Webview窗口<br/>
        <button onclick="captureWebview()">Draw</button>
    </body>
</html>

evalJS

在Webview窗口中执行JS脚本

void wobj.evalJS( js );

说明:

将JS脚本发送到Webview窗口中运行,可用于实现Webview窗口间的数据通讯。
参数:

js: ( String ) 必选 要在窗口中运行的脚本字符串

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 在Webview窗口中执行JS脚本
function evalJS() {
    embed.evalJS("alert('evalJS: '+location.href);");
}
    </script>
    </head>
    <body>
        在Webview窗口中执行JS脚本
        <button onclick="evalJS()">evalJS</button>
    </body>
</html>

forward

前进到上次加载的页面

void wobj.forward();

说明:

Webview窗口历史记录操作,前进到窗口上次加载的HTML页面。 如果窗口历史记录中没有可前进的页面则不触发任何操作。
参数:


返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var embed=null;
// H5 plus事件处理
function plusReady(){
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    plus.webview.currentWebview().append( embed );
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 返回上次页面
function goBack() {
    embed.back();
}
// 前进到上次页面
function goForward() {
    embed.forward();
}
    </script>
    </head>
    <body>
        前进到上次加载的页面
        <button onclick="goBack()">Back</button>
        <button onclick="goForward()">Forward</button>
    </body>
</html>

getStyle

获取Webview窗口的样式

WebviewStyles wobj.getStyle();

说明:

获取Webview窗口的样式属性,如窗口位置、大小等信息。
参数:


返回值:
WebviewStyles : WebviewStyles对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 获取Webview窗口的样式
function getStyle() {
    var style=ws.getStyle();
    alert( JSON.stringify(style) );
}
    </script>
    </head>
    <body>
        获取Webview窗口的样式
        <button onclick="getStyle()">getStyle</button>
    </body>
</html>

getTitle

获取Webview窗口加载HTML页面的标题

String wobj.getTitle();

说明:

标题为HTML页面head节点下title节点中的文本内容,当窗口内容发生页面内跳转时可通过窗口触发的“loaded”事件中调用此方法来获取跳转后页面的标题。 如果HTML页面没有使用title节点来设置标题,则返回空字符串。
参数:


返回值:
String : 窗口加载页面的标题
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 获取Webview窗口的标题
function getTitle() {
    alert( "标题为:"+embed.getTitle() );
}
    </script>
    </head>
    <body>
        获取Webview窗口加载HTML页面的标题
        <button onclick="getTitle()">getTitle</button>
    </body>
</html>

getURL

获取Webview窗口加载HTML页面的地址

String wobj.getURL();

说明:

当窗口内容发生页面内跳转时可通过窗口触发的“loaded”事件中调用此方法来获取跳转后页面的地址。
参数:


返回值:
String : 窗口加载页面的URL地址
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 获取Webview窗口加载HTML页面的地址
function getURL() {
    alert( "页面地址为:"+embed.getURL() );
}
    </script>
    </head>
    <body>
        获取Webview窗口加载HTML页面的地址
        <button onclick="getURL()">getURL</button>
    </body>
</html>

hide

隐藏Webview窗口

void wobj.hide( aniHide, duration, extras );

说明:

隐藏Webview窗口可保存已加载HTML页面的上下文数据,能降低应用使用的系统资源,通过show方法可将隐藏的Webview窗口显示出来。
参数:

aniHide: ( AnimationTypeClose ) 可选 隐藏Webview窗口动画效果
如果没有指定隐藏窗口动画,则使用默认动画效果“none”。
duration: ( Number ) 可选 隐藏Webview窗口的动画持续时间
单位为ms,默认为窗口show方法设定的动画时间。
extras: ( WebviewExtraOptions ) 可选 隐藏Webview窗口扩展参数
可用于指定Webview窗口动画是否使用图片加速。

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 隐藏Webview窗口
function hideWebview() {
    embed.hide();
}
    </script>
    </head>
    <body>
        隐藏Webview窗口
        <button onclick="hideWebview()">hide</button>
    </body>
</html>

isHardwareAccelerated

查询Webview窗口是否开启硬件加速

Boolean wobj.isHardwareAccelerated();

说明:

若Webview窗口已经开启硬件加速则返回true,否则返回false。
参数:


返回值:
Boolean : Webview窗口是否开启硬件加速
平台支持:

Android - 3.0+ (支持): 5+ Runtime会根据当前设备环境来决定是否开启硬件加速,也可通过WebviewStyles对象的hardwareAccelerated属性来强制设置是否开启硬件加速。 

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 查询Webview窗口是否开启硬件加速
function isHardwareAccelerated() {
    alert( "是否开启硬件加速:"+ws.isHardwareAccelerated() );
}
    </script>
    </head>
    <body>
        查询Webview窗口是否开启硬件加速
        <button onclick="isHardwareAccelerated()">isHardwareAccelerated</button>
    </body>
</html>

isVisible

查询Webview窗口是否可见

Boolean wobj.isVisible();

说明:

若Webview窗口已经显示则返回true,若Webview窗口被隐藏则返回false。
参数:


返回值:
Boolean : Webview窗口是否可见
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 查询Webview窗口是否可见
function visibleWebview() {
    alert( "是否可见:"+embed.isVisible() );
}
// 隐藏Webview窗口
function hideWebview() {
    embed.hide();
}
    </script>
    </head>
    <body>
        查询Webview窗口是否可见
        <button onclick="visibleWebview()">isVisible</button>
        <button onclick="hideWebview()">hide</button>
    </body>
</html>

listenResourceLoading

监听页面开始加载资源

void wobj.listenResourceLoading(options, callback);

说明:

Webview加载资源时,如果满足options参数中定义的条件,则触发callback回调。 此方法仅触发回调事件,不会阻止资源的加载。
参数:

options: ( WebviewListenResourceOptions ) 可选 监听加载资源的参数
callback: ( ListenResourceLoadingCallback ) 可选 监听加载资源的回调函数

返回值:
void : 无
平台支持:

Android - ALL (支持): 5+APP需要选择“解压资源后运行”模式后才能监听加载资源。
iOS - ALL (支持)

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,nw=null;
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 监听页面加载资源
function listenResourceLoading() {
    var wv=plus.webview.create("http://weibo.com/dhnetwork");
    // 监听到页面加载图片资源时显示({match:".*\.(jpg|png|jpeg|bmp)\b"})
    wv.listenResourceLoading({match:".*\\.(jpg|png|jpeg|bmp)\\b"}, function(e){
        console.log("loading resource: "+e.url);
        wv.show();
    });
    console.log("create webview");
}
    </script>
    </head>
    <body>
        监听页面开始加载资源
        <button onclick="listenResourceLoading()">加载图片资源时显示窗口</button>
    </body>
</html>

loadData

加载新HTML数据

void wobj.loadData( data );

说明:

触发Webview窗口加载HTML页面数据,如果HTML数据无效将导致页面加载失败。
参数:

data: ( String ) 必选 要加载的HTML数据

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 加载新HTML数据
function loadHtmlData() {
    embed.loadData( '<html><body>Hello! loadData!</body></html>' );
}
    </script>
    </head>
    <body>
        加载新HTML数据
        <button onclick="loadHtmlData()">loadData</button>
    </body>
</html>

loadURL

加载新URL页面

void wobj.loadURL( url );

说明:

触发Webview窗口从新的URL地址加载页面,如果url地址无效将导致页面显示失败。
参数:

url: ( String ) 必选 要加载的页面URL地址

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 加载新URL页面
function loadHtmlUrl() {
    embed.loadURL( 'http://m.csdn.net/' );
}
    </script>
    </head>
    <body>
        加载新URL页面
        <button onclick="loadHtmlUrl()">loadURL</button>
    </body>
</html>

nativeInstanceObject

获取Webview窗口对象的原生(Native.JS)实例对象

InstanceObject wobj.nativeInstanceObject();

说明:

Android平台返回Webview窗口对象的android.webkit.Webview实例对象, iOS平台返回Webview窗口对象的UIWebview实例对象。
参数:


返回值:
InstanceObject : Webview窗口对象的原生(Native.JS)实例对象。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var nws=null;
// H5 plus事件处理
function plusReady(){
    // 获取当前Webview实例的原生(Native.JS)实例对象
    nws=plus.webview.currentWebview().nativeInstanceObject();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        获取Webview窗口对象的原生(Native.JS)实例对象
    </body>
</html>

opened

获取在当前Webview窗口中创建的所有窗口

Array[WebviewObject] wobj.opened();

说明:

返回从当前Webview中调用plus.webview.open或plus.webview.create创建的所有Webview窗口数组。
参数:


返回值:
Array[ WebviewObject ] : 此窗口创建的Webview窗口对象数组,没有则返回空数组。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 获取在当前Webview窗口中创建的所有窗口
function openedWebview() {
    var list=ws.opened();
    for(var i=0;i<list.length;i++){
        alert( "opened["+i+"]: "+list[i].getURL() );
    }
}
    </script>
    </head>
    <body>
        获取在当前Webview窗口中创建的所有窗口
        <button onclick="openedWebview()">opened</button>
    </body>
</html>

opener

获取当前Webview窗口的创建者

WebviewObject wobj.opener();

说明:

创建者为调用plus.webview.open或plus.webview.create方法创建当前窗口的Webview窗口。
参数:


返回值:
WebviewObject : 创建当前窗口的Webview窗口对象
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 取当前Webview窗口的创建者
function openerWebview() {
    var wo=embed.opener();
    alert( "opener: "+wo.getURL() );
}
    </script>
    </head>
    <body>
        获取当前Webview窗口的创建者
        <button onclick="openerWebview()">opener</button>
    </body>
</html>

overrideResourceRequest

拦截Webview窗口的资源加载

void wobj.overrideResourceRequest(options);

说明:

根据区配规则拦截Webview窗口加载资源的URL地址,重定向到其它资源地址(暂仅支持本地地址)。 注意:多次调用overrideResourceRequest时仅以最后一次调用设置的参数值生效。
参数:

options: ( Array[ WebviewOverridResourceOptions ] ) 可选 拦截URL资源加载的参数

返回值:
void : 无
平台支持:

Android - ALL (支持): 5+APP需要选择“解压资源后运行”模式后才能截获应用资源的URL请求。
iOS - ALL (不支持)

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,nw=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    // 拦截Webview窗口的资源请求
    nw=plus.webview.create("http://weibo.com/dhnetwork");
    nw.overrideResourceRequest([{match:"http://tva3.sinaimg.cn/crop.121.80.980.980.180/be8dcc14gw1e7lz65y6g3j20uo0uoq4r.jpg",redirect:"_www/logo.png"}]);
    nw.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        拦截Webview窗口的资源加载
    </body>
</html>

overrideUrlLoading

拦截Webview窗口的URL请求

void wobj.overrideUrlLoading(options, callback);

说明:

拦截URL请求后,Webview窗口将不会跳转到新的URL地址,此时将通过callback回调方法返回拦截的URL地址(可新开Webview窗口加载URL页面等)。 此方法只能拦截窗口的网络超链接跳转(包括调用loadURL方法触发的跳转),不可拦截页面请求资源请求(如加载css/js/png等资源的请求)。 注意:多次调用overrideUrlLoading时仅以最后一次调用设置的参数值生效。
参数:

options: ( WebviewOverrideUrlOptions ) 可选 拦截URL请求的参数
callback: ( OverrideUrlLoadingCallback ) 可选 拦截URL请求的回调函数

返回值:
void : 无
平台支持:

Android - ALL (支持): 5+APP需要选择“解压资源后运行”模式后才能截获应用资源的URL请求。
iOS - ALL (支持)

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,nw=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    nw=plus.webview.create("http://weibo.com/dhnetwork");
    nw.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 拦截Webview窗口的URL请求
function overrideUrl() {
    // 拦截所有页面跳转,可使用参数拦截weibo.com域名之外的跳转({mode:"allow",match:".*weibo\.com/.*"})
    nw.overrideUrlLoading({mode:"reject"}, function(e){
        console.log("reject url: "+e.url);
    });
}
    </script>
    </head>
    <body>
        拦截Webview窗口的URL请求
        <button onclick="overrideUrl()">overrideUrlLoading</button>
    </body>
</html>

parent

获取当前Webview窗口的父窗口

WebviewObject wobj.parent();

说明:

Webview窗口作为子窗口添加(Webview.append)到其它Webview窗口中时有效,这时其它Webview窗口为父窗口。
参数:


返回值:
WebviewObject : 父Webview窗口对象,没有则返回null。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 获取当前Webview窗口的父窗口
function parentWebview() {
    var wp=embed.parent();
    alert( "parent: "+wp.getURL() );
}
    </script>
    </head>
    <body>
        获取当前Webview窗口的父窗口
        <button onclick="parentWebview()">parent</button>
    </body>
</html>

reload

重新加载Webview窗口显示的HTML页面

void wobj.reload( force );

说明:

触发Webview窗口重新加载当前显示的页面内容。 如果当前HTML页面未加载完则停止并重新加载,如果当前Webview窗口没有加载任何页面则无响应。
参数:

force: ( Boolean ) 必选 是否强制不使用缓存
为加速HTML页面加载速度,默认在重新加载时会使用缓存,若force设置为true则不使用缓存,重新从URL地址加载所有页面内容。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.show();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 重新加载Webview窗口显示的HTML页面
function reloadWebview() {
    embed.reload(true);
}
    </script>
    </head>
    <body>
        重新加载Webview窗口显示的HTML页面
        <button onclick="reloadWebview()">reload</button>
    </body>
</html>

resetBounce

重置Webview窗口的回弹位置

void wobj.resetBounce();

说明:

开启窗口回弹效果后,当窗口中展现的内容滚动到头(顶部或底部)时,再拖拽时窗口整体内容将跟随移动,松开后自动回弹到停靠位置。 这时需要调用此方法来重置窗口的回弹位置,窗口将采用动画方式回弹到其初始显示的位置。
参数:


返回值:
void : 无
平台支持:

Android - 2.2+ (支持)
iOS - ALL (不支持)

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    ws.setBounce({position:{top:"100px"},changeoffset:{top:"44px"}});
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 重置窗口回弹位置
function resetBounce(){
    ws.resetBounce();
}
    </script>
    </head>
    <body style="text-align:center;">
        <br/><br/><br/>
        设置Webview窗口的回弹效果<br/>
        回弹后显示停靠到44px的位置<br/><br/>
        <button onclick="resetBounce()">重置回弹位置</button>
        <br/><br/><br/>
        *暂仅支持顶部的回弹效果*
    </body>
</html>

remove

移除子Webview窗口

void wobj.remove( webview );

说明:

从当前Webview窗口移除指定的子Webview窗口,若指定的webview对象不是当前窗口的子窗口则无任何作用。 移除后子Webview窗口不会关闭,需要调用其close方法才能真正关闭并销毁。
参数:

webview: ( plus.nativeObj.View | WebviewObject ) 必选 要移除的Webview窗口

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 移除子Webview窗口
function removeWebview() {
    ws.remove(embed);
    embed.close();
}
    </script>
    </head>
    <body>
        移除子Webview窗口
        <button onclick="removeWebview()">remove</button>
    </body>
</html>

removeEventListener

移除Webview窗口事件监听器

void wobj.removeEventListener( event, listener );

说明:

从Webview窗口移除通过addEventListener方法添加的事件监听器,若没有查找到对应的事件监听器,则无任何作用。
参数:

event: ( NWindowEvent ) 必选 要移除的事件类型
listener: ( EventCallback ) 必选 要移除监听函数对象

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.addEventListener( "loaded", embedLoaded, false );
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 页面跳转监听器
function embedLoaded(e){
    alert( "Loaded: "+embed.getURL() );
}
// 移除Webview窗口事件监听器
function removeEvent() {
    embed.removeEventListener( "loaded", embedLoaded );
}
    </script>
    </head>
    <body>
        移除Webview窗口事件监听器
        <button onclick="removeEvent()">removeEventListener</button>
    </body>
</html>

removeFromParent

从父窗口中移除

void wobj.removeFromParent();

说明:

从所属的父Webview窗口移除,如果没有父窗口,则无任何作用。 从父窗口中移除后子Webview窗口不会关闭,需要调用其close方法才能真正关闭并销毁。
参数:


返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 从父窗口中移除
function removeFromeWebview() {
    embed.removeFromParent();
    embed.close();
}
    </script>
    </head>
    <body>
        从父窗口中移除
        <button onclick="removeFromeWebview()">removeFromParent</button>
    </body>
</html>

setBounce

设置Webview窗口的回弹效果

void wobj.setBounce( style );

说明:

开启窗口回弹效果后,当窗口中展现的内容滚动到头(顶部或底部)时,再拖拽时窗口整体内容将跟随移动,松开后自动回弹到停靠位置(可通过style设置)。 拖拽窗口内容时页面显示Webview窗口的背景色,默认为透明,此时显示Webview下面的内容,利用这个特点可以实现自定下拉刷新特效。
参数:

style: ( WebviewBounceStyle ) 必选 Webview窗口回弹样式参数
可设置窗口的回弹效果支持的方向,自动回弹后停靠的位置等参数。 

返回值:
void : 无
平台支持:

Android - 2.2+ (支持)
iOS - ALL (不支持)

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    ws.setBounce({position:{top:"100px"},changeoffset:{top:"0px"}});
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body style="text-align:center;">
        <br/><br/><br/>
        设置Webview窗口的回弹效果<br/><br/><br/>
        *暂仅支持顶部的回弹效果*
    </body>
</html>

setBlockNetworkImage

设置Webview窗口是否阻塞加载页面中使用的网络图片

void wobj.setBlockNetworkImage( block );

参数:

block: ( Boolean ) 必选 是否阻塞加载网络图片
true表示不加载页面中使用的网络图片,false表示加载也页面中使用的网络图片。 

返回值:
void : 无
平台支持:

Android - 2.2+ (支持)
iOS - ALL (不支持)

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
function blockOpen(){
    // 阻塞网络图片模式打开页面
    var w=plus.webview.create("http://m.csdn.net/","csdn",{blockNetworkImage:true});
    w.addEventListener("loaded",function(){
        w.show("slide-in-right",300);
        // 加载网络图片
        w.setBlockNetworkImage(false);
    },false);
}
    </script>
    </head>
    <body>
        显示窗口后再加载网络图片<br/>
        <button onclick="blockOpen()">打开页面</button>
    </body>
</html>

setContentVisible

设置HTML内容是否可见

void wobj.setContentVisible( visible );

说明:

设置HTML内容不可见后,将显示Webview窗口的背景色。
参数:

visible: ( Boolean ) 必选 设置页面是否可见,true表示可见,false表示不可见

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 设置HTML内容是否可见
function setContentVisible(v) {
    embed.setContentVisible(v);
}
    </script>
    </head>
    <body>
        设置HTML内容是否可见
        <button onclick="setContentVisible(true)">可见</button>
        <button onclick="setContentVisible(false)">不可见</button>
    </body>
</html>

setPullToRefresh

设置Webview窗口的下拉刷新效果

void wobj.setPullToRefresh( style, refreshCB );

说明:

开启Webview窗口的下拉刷新功能,显示窗口内置的下拉刷新控件样式。
参数:

style: ( WebviewRefreshStyle ) 必选 Webview窗口下拉刷新样式参数
可设置窗口内置的下拉刷新控件在各种状态显示的文字内容。
refreshCB: ( SuccessCallback ) 必选 Webview窗口下拉刷新事件回调
用户操作窗口的下拉刷新触发窗口刷新事件时触发。

返回值:
void : 无
平台支持:

Android - 2.2+ (支持)
iOS - ALL (支持)

示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>Webview Example</title>
        <script type="text/javascript" charset="utf-8">
var ws=null;
var list=null;
// 扩展API加载完毕,现在可以正常调用扩展API 
function plusReady(){
    ws=plus.webview.currentWebview();
    ws.setPullToRefresh({support:true,
        height:"50px",
        range:"200px",
        contentdown:{
            caption:"下拉可以刷新"
        },
        contentover:{
            caption:"释放立即刷新"
        },
        contentrefresh:{
            caption:"正在刷新..."
        }
    },onRefresh);
    plus.nativeUI.toast("下拉可以刷新");
}
// 判断扩展API是否准备,否则监听"plusready"事件
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// DOM构建完成获取列表元素
document.addEventListener("DOMContentLoaded",function(){
    list=document.getElementById("list");
})
// 刷新页面
function onRefresh(){
    setTimeout(function(){
        if(list){
            var item=document.createElement("li");
            item.innerHTML="<span>New Item "+(new Date())+"</span>";
            list.insertBefore(item,list.firstChild);
        }
        ws.endPullToRefresh();
    },2000);
}
        </script>
        <link rel="stylesheet" href="../css/common.css" type="text/css" charset="utf-8"/>
        <style type="text/css">
li {
    padding: 1em;
    border-bottom: 1px solid #eaeaea;
}
li:active {
    background: #f4f4f4;
}
        </style>
    </head>
    <body>
        <ul id="list" style="list-style:none;margin:0;padding:0;">
            <li><span>Initializ List Item 1</span></li>
            <li><span>Initializ List Item 2</span></li>
            <li><span>Initializ List Item 3</span></li>
            <li><span>Initializ List Item 4</span></li>
            <li><span>Initializ List Item 5</span></li>
            <li><span>Initializ List Item 6</span></li>
            <li><span>Initializ List Item 7</span></li>
            <li><span>Initializ List Item 8</span></li>
            <li><span>Initializ List Item 9</span></li>
            <li><span>Initializ List Item 10</span></li>
        </ul>
    </body>
</html>

setStyle

设置Webview窗口的样式

void wobj.setStyle( styles );

说明:

更新Webview窗口的样式,如窗口位置、大小、背景色等。
参数:

styles: ( WebviewStyles ) 必选 要设置的窗口样式

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 设置Webview窗口的样式
function updateStyle() {
    embed.setStyle( {top:"92px"} );
}
    </script>
    </head>
    <body>
        设置Webview窗口的样式
        <button onclick="updateStyle()">setStyle</button>
    </body>
</html>

setJsFile

设置预加载的JS文件

void wobj.setJsFile( path );

说明:

预加载JS文件不需要在HTML页面中显式引用,在Webview窗口加载HTML页面时自动加载,在页面跳转时也会自动加载。 设置新的JS文件后将清空之前设置的值。
参数:

file: ( String ) 必选 预载入的JS文件地址,仅支持本地文件,格式为相对路径URL(plus.io.RelativeURL),如"_www/preload.js"

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.setJsFile( "_www/js/preload.js" );
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
    </script>
    </head>
    <body>
        设置预加载的JS文件
    </body>
</html>

show

显示Webview窗口

void wobj.show( aniShow, duration, showedCB, extras );

说明:

当调用plus.webview.create方法创建Webview窗口后,需要调用其show方法才能显示,并可设置窗口显示动画及动画时间。 Webview窗口被隐藏后也可调用此方法来重新显示。
参数:

aniShow: ( AnimationTypeShow ) 可选 Webview窗口显示动画类型
如果没有指定窗口动画类型,则使用默认值“none”,即无动画。
duration: ( Number ) 可选 Webview窗口显示动画持续时间
单位为ms,默认使用动画类型想对应的默认时间。
showedCB: ( SuccessCallback ) 可选 Webview窗口显示完成的回调函数
当指定Webview窗口显示动画执行完毕时触发回调函数,窗口无动画效果(如"none"动画效果)时也会触发此回调。
extras: ( WebviewExtraOptions ) 可选 显示Webview窗口扩展参数
可用于指定Webview窗口动画是否使用图片加速。

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webview Example</title>
<script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
ws=plus.webview.currentWebview();
}
if(window.plus){
plusReady();
}else{
document.addEventListener("plusready",plusReady,false);
}
// 创建并显示Webview窗口
function showWebview(){
embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
embed.show( "slide-in-right", 300 );
}
</script>
</head>
<body>
显示Webview窗口
<button onclick="showWebview()">show</button>
</body>
</html>

stop

停止加载HTML页面内容

void wobj.stop();

说明:

触发Webview窗口停止加载页面内容,如果已经加载部分内容则显示部分内容,如果加载完成则显示全部内容。
参数:


返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 停止加载HTML页面内容
function stopWebview(){
    embed.stop();
}
    </script>
    </head>
    <body>
        停止加载HTML页面内容
        <button onclick="stopWebview()">stop</button>
    </body>
</html>

onclose

Webview窗口关闭事件
说明:

EventCallback 类型

当Webview窗口关闭时触发此事件,类型为EventCallback。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.onclose=embedClose;
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 页面关闭事件回调函数
function embedClose(e){
    alert( "Closed!" );
}
    </script>
    </head>
    <body>
        Webview窗口关闭事件
        <button onclick="embed.close()">onclose</button>
    </body>
</html>

onerror

Webview窗口错误事件
说明:

WebviewEvent 类型

当Webview窗口加载错误时触发此事件,类型为EventCallback。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.onerror=embedError;
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 页面错误事件回调函数
function embedError(e){
    alert( "Error!" );
}
    </script>
    </head>
    <body>
        Webview窗口错误事件
        <button onclick="embed.loadData('<xml>Not html</xml>')">onerror</button>
    </body>
</html>

onloaded

Webview窗口页面加载完成事件
说明:

WebviewEvent 类型

当Webview窗口页面加载完成时触发此事件,类型为EventCallback。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.onloaded=embedLoaded;
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 页面加载完成事件回调函数
function embedLoaded(e){
    alert( "Loaded!" );
}
    </script>
    </head>
    <body>
        Webview窗口页面加载完成事件
        <button onclick="embed.loadURL('http://m.csdn.net')">onloaded</button>
    </body>
</html>

onloading

Webview窗口页面开始加载事件
说明:

WebviewEvent 类型

当Webview窗口开始加载新页面时触发此事件,类型为EventCallback。
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,embed=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    embed=plus.webview.create("http://weibo.com/dhnetwork","",{top:"46px",bottom:"0px"});
    embed.onloading=embedLoading;
    ws.append(embed);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 页面开始加载事件回调函数
function embedLoading(e){
    alert( "Loading!" );
}
    </script>
    </head>
    <body>
        Webview窗口页面开始加载事件
        <button onclick="embed.loadURL('http://m.csdn.net')">onloading</button>
    </body>
</html>

WebviewBounceStyle

Webview窗口回弹样式
属性:

position: (JSON 类型 )Webview窗口支持回弹效果的方向

可通过此参数设置开启Webview哪个方向支持回弹效果。 支持以下属性: top:表示窗口顶部支持回弹效果; left:表示窗口左侧支持回弹效果; right:表示窗口右侧支持回弹效果; bottom:表示窗口底部支持回弹效果。 **目前仅支持top属性** 属性值:用于指定可拖拽的范围,可取百分比,如"10%";像素值,如"100px";自动计算值,如"auto";无回弹效果值,如"none";
changeoffset: (JSON 类型 )Webview窗口回弹时停靠的位置

开启窗口回弹效果后,当窗口中展现的内容滚动到头(顶部或底部)时,再拖拽时窗口整体内容将跟随移动,拖拽过程中将触发"dragBounce"事件,松开后自动回弹到停靠位置。 支持以下属性: top:表示窗口顶部回弹时停靠的位置。 属性值:用于指定窗口回弹的位置,可取百分比,如"5%";像素值,如"100px";自动计算值,如"auto",默认为可拖拽的范围值的一半;
slideoffset: (JSON 类型 )Webview窗口侧滑时停靠的位置

开启窗口回弹效果后,当窗口中展现的内容滚动到头(左侧或右侧)时,在拖拽时窗口整体内容将跟随移动,松开后自动停靠的侧滑位置,并触发"slideBounce"事件。 支持以下属性: left:表示窗口左侧侧滑的位置; right:表示窗口右侧侧滑的位置。 属性值:用于指定滑动后停靠的距离,可取百分比(left/right相对于窗口的宽度,top/bottom相对于窗口的高度),如"30%";像素值,如"100px";自动计算值,为可拖拽范围,如"auto"。
offset: (JSON 类型 )Webview窗口拖拽偏移的位置

开启窗口回弹效果后,可以通过此属性值来主动设置拖拽的偏移位置,与手动操作拖拽至此偏移位置松开后的逻辑一致。 支持以下属性: top:表示窗口顶部偏移的位置; left:表示窗口左侧偏移的位置; right:表示窗口右侧偏移的位置; bottom:表示窗口底部偏移的位置。 属性值:用于指定偏移的位置,可取百分比,如"5%";像素值,如"100px";有效值范围为0到position属性定义的位置。
preventTouchEvent: (Boolean 类型 )Webview窗口是否阻止touch事件传递给DOM元素

设置为true表示阻止touch事件,设置为false表示不阻止touch事件。当开启侧滑功能(左侧滑和右侧滑)时默认值为true,否则为false。
平台支持
    Android - 2.3+ (支持): 由于Touch事件存在冲突,如果Webview使用侧滑功能,需要将Webview设置为阻止touch事件传递才能触发。 当html页面内容需要处理横向滚动时并且也需要侧滑效果时,需初始化时设置不阻止touch事件传递:webview.setBounce({position:{left:"100px"},solideoffset:{left:"auto"},preventTouchEvent:false});。当操作非横向滚动元素时及时动态设置阻止touch事件传递以触发侧滑效果:webview.setBounce({preventTouchEvent:true});。
    iOS - 5.1+ (不支持): Touch事件不存在冲突,不支持此功能。

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    ws.setBounce({position:{top:"100px"},changeoffset:{top:"44px"}});
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 重置窗口回弹位置
function resetBounce(){
    ws.resetBounce();
}
    </script>
    </head>
    <body style="text-align:center;">
        <br/><br/><br/>
        设置Webview窗口的回弹效果<br/>
        回弹后显示停靠到44px的位置<br/><br/>
        <button onclick="resetBounce()">重置回弹位置</button>
        <br/><br/><br/>
        *暂仅支持顶部的回弹效果*
    </body>
</html>

WebviewDock

原生控件在窗口中停靠的方式
常量:

"top": (String 类型 )控件停靠则页面顶部
"bottom": (String 类型 )控件停靠在页面底部
"right": (String 类型 )控件停靠在页面右侧
"left": (String 类型 )控件停靠在页面左侧

WebviewEvent

Webview窗口事件
常量:

"close": (String 类型 )Webview窗口关闭事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口关闭时触发此事件,回调函数类型为EventCallback。
   <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 监听Webview窗口关闭事件
    function eventTest(){
        if(nw){return;}
        // 打开新窗口
        nw=plus.webview.create( "http://weibo.com/dhnetwork", {top:"46px",bottom:"0px"} );
        nw.addEventListener( "close", function(e){
            console.log( "Webview closed!" );
            nw=null;
        }, false );
        nw.show(); // 显示窗口
    }
        </script>
        </head>
        <body>
            Webview窗口关闭事件
            <button onclick="eventTest()">start</button>
            <button onclick="nw.close()">close</button>
        </body>
    </html>
"dragBounce": (String 类型 )Webview窗口回弹事件

通过WebviewObject对象的setBounce方法开启回弹效果设置顶部下拉回弹changeoffset属性后,当用户向下拖拽窗口时触发发此事件,回调函数类型为BounceEventCallback。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var ws=null;
    // H5 plus事件处理
    function plusReady(){
        ws=plus.webview.currentWebview();
        ws.setBounce({position:{top:"100px"},changeoffset:{top:"44px"}});
        ws.addEventListener("dragBounce",onPullStateChange,false);
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }
    // 下拉状态改变
    function onPullStateChange(e){
        switch(e.status){
            case "beforeChangeOffset":
            console.log("顶部回弹:可继续往下拖拽");
            break;
            case "afterChangeOffset":
            console.log("顶部回弹:松开会重置回弹位置");
            break;
            case "dragEndAfterChangeOffset":
            console.log("顶部回弹:松开停靠回弹");
            break;
            default:
            break;
        }
    }
    // 重置窗口回弹位置
    function resetBounce(){
        ws.resetBounce();
    }
        </script>
        </head>
        <body style="text-align:center;">
            <br/><br/><br/>
            设置Webview窗口的回弹效果<br/>
            回弹后显示停靠到44px的位置<br/><br/>
            <button onclick="resetBounce()">重置回弹位置</button>
            <br/><br/><br/>
            *暂仅支持顶部的回弹效果*
        </body>
    </html>
"slideBounce": (String 类型 )Webview窗口回弹事件

通过WebviewObject对象的setBounce方法开启回弹效果设置左右侧侧滑slideoffset属性后,当用户向左右侧拖拽窗口侧滑时触发发此事件,回调函数类型为BounceEventCallback。
"error": (String 类型 )Webview窗口加载错误事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口加载错误时触发此事件,回调函数类型为EventCallback。
  <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 监听Webview窗口加载错误事件
    function eventTest() {
        // 打开新窗口
        nw=plus.webview.create( "", {top:"46px",bottom:"0px"} );
        nw.addEventListener( "error", function(e){
            console.log( "Error: "+nw.getURL() );
        }, false );
        nw.show(); // 显示窗口
    }
        </script>
        </head>
        <body>
            Webview窗口加载错误事件
            <button onclick="eventTest()">start</button>
            <button onclick="nw.loadURL('http://bucunzaideyuming.abcdeg')">error</button>
        </body>
    </html>
"hide": (String 类型 )Webview窗口隐藏事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口隐藏(窗口动画完成后)时触发此事件,回调函数类型为EventCallback。
    <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
        createWebview();
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 创建Webview窗口监听show、hide事件
    function createWebview(){
        // 打开新窗口
        nw=plus.webview.create( "http://m.csdn.net/", {top:"46px",bottom:"0px"} );
        nw.addEventListener( "show", function(e){
            console.log( "Webview Showed" );
        }, false );
        nw.addEventListener( "hide", function(e){
            console.log( "Webview Hided" );
        }, false );
    }
    // 显示Webview窗口
    function showWebview(){
        nw.show( "slide-in-right" );
    }
    // 隐藏Webview窗口
    function hideWebview(){
        nw.hide();
    }
        </script>
        </head>
        <body>
            Webview窗口显示隐藏事件
            <button onclick="showWebview()">Show</button>
            <button onclick="hideWebview()">Hide</button>
        </body>
    </html>
"loading": (String 类型 )Webview窗口页面开始加载事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口开始加载新页面时触发此事件,回调函数类型为EventCallback。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 监听Webview窗口页面开始加载事件
    function eventTest() {
        // 打开新窗口
        nw=plus.webview.create( "", {top:"46px",bottom:"0px"} );
        nw.addEventListener( "loading", function(e){
            console.log( "Loading: "+nw.getURL() );
        }, false );
        nw.show(); // 显示窗口
    }
        </script>
        </head>
        <body>
            Webview窗口页面开始加载事件
            <button onclick="eventTest()">start</button>
            <button onclick="nw.loadURL('http://m.csdn.net')">loading</button>
        </body>
    </html>
"loaded": (String 类型 )Webview窗口页面加载完成事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口页面加载完成时触发此事件,回调函数类型为EventCallback。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 监听Webview窗口页面加载完成事件
    function eventTest() {
        // 打开新窗口
        nw=plus.webview.create( "", "", {top:"46px",bottom:"0px"} );
        nw.addEventListener( "loaded", function(e){
            console.log( "Loaded: "+nw.getURL() );
        }, false );
        nw.show(); // 显示窗口
    }
        </script>
        </head>
        <body>
            Webview窗口页面加载完成事件
            <button onclick="eventTest()">start</button>
            <button onclick="nw.loadURL('http://m.csdn.net')">loaded</button>
        </body>
    </html>
"maskClick": (String 类型 )Webview窗口显示遮罩层时点击事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口通过mask属性设置显示遮罩层并且点击时触发此事件,回调函数类型为EventCallback。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var ws=null;
    // H5 plus事件处理
    function plusReady(){
        ws=plus.webview.currentWebview();
        // 显示遮罩层
        ws.setStyle({mask:"rgba(0,0,0,0.5)"});
        // 点击关闭遮罩层
        ws.addEventListener("maskClick",function(){
            ws.setStyle({mask:"none"});
        },false);
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }
        </script>
        </head>
        <body>
            Webview窗口页面加载完成事件
            <br/>
            点击窗口关闭遮罩层
        </body>
    </html>
"show": (String 类型 )Webview窗口显示事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口显示(窗口动画完成后)时触发此事件,回调函数类型为EventCallback。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
        createWebview();
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 创建Webview窗口监听show、hide事件
    function createWebview(){
        // 打开新窗口
        nw=plus.webview.create( "http://m.csdn.net/", "", {top:"46px",bottom:"0px"} );
        nw.addEventListener( "show", function(e){
            console.log( "Webview Showed" );
        }, false );
        nw.addEventListener( "hide", function(e){
            console.log( "Webview Hided" );
        }, false );
    }
    // 显示Webview窗口
    function showWebview(){
        nw.show( "slide-in-right" );
    }
    // 隐藏Webview窗口
    function hideWebview(){
        nw.hide();
    }
        </script>
        </head>
        <body>
            Webview窗口显示隐藏事件
            <button onclick="showWebview()">Show</button>
            <button onclick="hideWebview()">Hide</button>
        </body>
    </html>
"popGesture": (String 类型 )Webview窗口侧滑返回事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口侧滑返回时触发此事件,回调函数类型为PopGestureCallback。
平台支持
    Android - 2.2+ (不支持): 不支持侧滑返回功能,不会触发此事件。
    iOS - ALL (支持): Webview设置侧滑返回功能才能触发此事件。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
        createWebview();
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 创建Webview窗口监听侧滑返回事件
    function createWebview(){
        // 打开新窗口
        nw=plus.webview.create( "http://m.csdn.net/","",{top:"100px",bottom:"0px",popGesture:"hide"} );
        nw.addEventListener( "popGesture", function(e){
            poplog.innerText="popGesture: "+e.type+","+e.result+","+e.progress;
        }, false );
    }
    // 显示Webview窗口
    function showWebview(){
        nw.show( "slide-in-right" );
    }
    // 隐藏Webview窗口
    function hideWebview(){
        nw.hide();
    }
    // 关闭窗口
    function closeWebview(){
        nw.close();
        plus.webview.currentWebview().close();
    }
        </script>
        </head>
        <body>
            Webview窗口侧滑返回事件
            <button onclick="closeWebview()">Close</button>
            <button onclick="showWebview()">Show</button>
            <button onclick="hideWebview()">Hide</button>
            <div id="poplog"></div>
        </body>
    </html>
"titleUpdate": (String 类型 )Webview加载页面标题更新事件

通过WebviewObject对象的addEventListener方法添加事件监听函数,当Webview窗口加载新页面更新标题时触发此事件,回调函数类型为SuccessCallback。 注意:此事件会先于loaded事件触发,通常在加载网络页面时通过此事件可更快获取到页面的标题。
平台支持
    Android - 2.2+ (支持)
    iOS - 5.1+ (支持)
    <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var nw=null;
    // H5 plus事件处理
    function plusReady(){
        createWebview();
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 创建Webview窗口监听侧滑返回事件
    function createWebview(){
        // 打开新窗口
        nw=plus.webview.create( "http://weibo.com/dhnetwork/","",{top:"100px",bottom:"0px"} );
        nw.addEventListener( "titleUpdate", function(e){
            console.log("Update title: "+e.title);
        }, false );
        plus.webview.currentWebview().append(nw);
    }
        </script>
        </head>
        <body>
            Webview窗口标题更新事件
            <button onclick="closeWebview()">New</button>
        </body>
    </html>

WebviewRefreshStyle

Webview窗口下拉刷新样式
属性:

support: (Boolean 类型 )是否开启Webview窗口的下拉刷新功能

true表示开启窗口的下拉刷新功能; false表示关闭窗口的下拉刷新功能。
height: (String 类型 )窗口的下拉刷新控件高度

支持百分比,如"10%";像素值,如"50px"。
range: (String 类型 )窗口可下拉拖拽的范围

支持百分比,如"10%";像素值,如"50px"。
contentdown: (JSON 类型 )在下拉可刷新状态时显示的内容

支持以下属性: caption:在下拉可刷新状态时下拉刷新控件上显示的标题内容。
contentover: (JSON 类型 )在释放可刷新状态时显示的内容

支持以下属性: caption:在释放可刷新状态时下拉刷新控件上显示的标题内容。
contentrefresh: (JSON 类型 )在正在刷新状态时显示的内容

支持以下属性: caption:在正在刷新状态时下拉刷新控件上显示的标题内容。

示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>Webview Example</title>
        <script type="text/javascript" charset="utf-8">
var ws=null;
var list=null;
// 扩展API加载完毕,现在可以正常调用扩展API 
function plusReady(){
    ws=plus.webview.currentWebview();
    ws.setPullToRefresh({support:true,
        height:"50px",
        range:"200px",
        contentdown:{
            caption:"下拉可以刷新"
        },
        contentover:{
            caption:"释放立即刷新"
        },
        contentrefresh:{
            caption:"正在刷新..."
        }
    },onRefresh);
    plus.nativeUI.toast("下拉可以刷新");
}
// 判断扩展API是否准备,否则监听"plusready"事件
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// DOM构建完成获取列表元素
document.addEventListener("DOMContentLoaded",function(){
    list=document.getElementById("list");
})
// 刷新页面
function onRefresh(){
    setTimeout(function(){
        if(list){
            var item=document.createElement("li");
            item.innerHTML="<span>New Item "+(new Date())+"</span>";
            list.insertBefore(item,list.firstChild);
        }
        ws.endPullToRefresh();
    },2000);
}
        </script>
        <link rel="stylesheet" href="../css/common.css" type="text/css" charset="utf-8"/>
        <style type="text/css">
li {
    padding: 1em;
    border-bottom: 1px solid #eaeaea;
}
li:active {
    background: #f4f4f4;
}
        </style>
    </head>
    <body>
        <ul id="list" style="list-style:none;margin:0;padding:0;">
            <li><span>Initializ List Item 1</span></li>
            <li><span>Initializ List Item 2</span></li>
            <li><span>Initializ List Item 3</span></li>
            <li><span>Initializ List Item 4</span></li>
            <li><span>Initializ List Item 5</span></li>
            <li><span>Initializ List Item 6</span></li>
            <li><span>Initializ List Item 7</span></li>
            <li><span>Initializ List Item 8</span></li>
            <li><span>Initializ List Item 9</span></li>
            <li><span>Initializ List Item 10</span></li>
        </ul>
    </body>
</html>

WebviewPosition

原生控件在窗口中显示的位置
常量:

"static": (String 类型 )控件在页面中正常定位,如果页面存在滚动条则随窗口内容滚动
"absolute": (String 类型 )控件在页面中绝对定位,如果页面存在滚动条不随窗口内容滚动
"dock": (String 类型 )控件在页面中停靠,停靠的位置通过dock属性进行定义

WebviewStyles

JSON对象,原生窗口设置参数的对象
属性:

cachemode: (String 类型 )窗口的缓存模式

可取值: "default" - 根据cache-control决定是否使用缓存数据,如果存在缓存并且没有过期则使用本地缓存资源,否则从网络获取; "cacheElseNetwork" - 只要存在缓存(即使过期)数据则使用,否则从网络获取; "noCache" - 不使用缓存数据,全部从网络获取; "cacheOnly" - 仅使用缓存数据,不从网络获取(注:如果没有缓存数据则会导致加载失败)。 默认使用"default"。
平台支持
    Android - 2.2+ (支持)
    iOS - 5.0+ (不支持)
background: (String 类型 )窗口的背景颜色

窗口空白区域的背景模式,设置background为颜色值(参考CSS Color Names,可取值/十六进制值/rgb值/rgba值),窗口为独占模式显示(占整个屏幕区域); 设置background为“transparent”,则表示窗口背景透明,为非独占模式。
平台支持
    Android - 2.2+ (支持): Android平台4.0以上系统才支持“transparent”背景透明样式,4.0以下系统窗口显示白色背景。
    iOS - 5.0+ (支持): iOS平台默认使用白色背景。
blockNetworkImage: (Boolean 类型 )是否阻塞网络图片的加载

布尔类型,true表示阻塞,false表示不阻塞,默认值为false。 阻塞后Webview窗口将不加载页面中使用的所有网络图片,可通过Webview窗口对象的setBlockNetWorkImage()方法动态修改此状态。
平台支持
    Android - 2.2+ (支持)
    iOS - 5.0+ (不支持): 忽略此属性,Webview窗口加载页面中所有图片。
bottom: (String 类型 )窗口垂直向上的偏移量

支持百分比、像素值,默认值无值(根据top和height属性值来自动计算)。 当设置了top和height值时,忽略此属性值; 当未设置height值时,可通过top和bottom属性值来确定窗口的高度。
bounce: (String 类型 )窗口遇到边框是否有反弹效果

可取值:none表示没有反弹效果;vertical表示垂直方向有反弹效果;horizontal表示水平方向有反弹效果;all表示垂直和水平方向都有反弹效果。
平台支持
    Android - ALL (支持): 默认值为"none",无法单独控制垂直和水平方向(即"vertical","horizontal","all"都表示开启反弹效果)。 无法动态修改,只能在创建时设置此值。
    iOS - 5.1.1+ (支持): 默认值为none,垂直和水平方向都没有反弹效果。
bounceBackground: (String 类型 )窗口回弹效果区域的背景

窗口回弹效果区域背景可支持颜色值或图片: 颜色值格式为"#RRGGBB",如"#FFFFFF"为设置白色背景; 背景图为"url(%image path%)",如"url(./icon.png)"为设置icon.png为背景图,图片采用平铺模式绘制。
平台支持
    Android - (不支持)
    iOS - 5.0+ (支持): 默认值为系统窗口背景色,通常为黑色。
decelerationRate: (Number 类型 )窗口内容停止滑动的减速度

当Webview加载的内容超过其高度时,可以拖拽滑动内容,decelerationRate属性控制手指松开后页面滑动的速度。 设置值越大手指松开后的滑动速度越快(滑动距离越长),其值域范围为0.0-1.0,默认值为0.989。
平台支持
    Android - ALL (不支持)
    iOS - 5.0+ (支持)
dock: (WebviewDock 类型 )窗口的停靠方式

当Webview窗口添加到另外一个窗口中时,停靠方式才会生效,采用停靠方式添加会导致原Webview窗口自动调整其大小避免其内容被子窗口盖住。 可取值:"top",控件停靠则页面顶部;"bottom",控件停靠在页面底部;"right",控件停靠在页面右侧;"left",控件停靠在页面左侧。
平台支持
    Android - 2.2+ (支持)
    iOS - 5.0+ (支持)
errorPage: (String 类型 )窗口加载错误时跳转的页面地址

当Webview窗口无法加载指定的url地址时(如本地页面不存在,或者无法访问的网络地址),此时会自动跳转到指定的错误页面地址(仅支持本地页面地址)。 设置为“none”则关闭跳转到错误页面功能,此时页面显示Webview默认的错误页面内容。默认使用5+ Runtime内置的错误页面。
平台支持
    Android - 2.2+ (支持)
    iOS - 5.0+ (支持)
hardwareAccelerated: (Boolean 类型 )窗口是否开启硬件加速

布尔类型,true表示开启硬件加速,false表示不开启硬件加速,默认情况5+ Runtime会根据设备实际支持情况自动选择是否开启硬件加速,可以通过plus.webview.defaultHardwareAccelerated()方法获取默认Webview是否开启硬件加速。 由于不同设备对硬件加速的支持情况存在差异,开启硬件加速能加速HTML页面的渲染,但也会消耗更多的系统资源,从而导致在部分设备上可能出现闪屏、发虚、分块渲染等问题,因此在特定设备的特定页面如果出现以上问题需要手动设置关闭硬件加速来避免。
平台支持
    Android - 2.3+ (支持): 注意: 1. 如果在页面中使用Video标签播放视频则必须打开硬件加速功能; 2. 页面必须在创建时确定是否打开硬件加速功能,无法动态切换页面的硬件加速开关; 3. 首页Webview窗口是否开启硬件加速需在manifest.json中plus节点下的hardwareAccelerated属性值控制。
    iOS - 5.1+ (不支持): 忽略此属性。
 <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    // H5 plus事件处理
    function plusReady(){
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }

    // 创建新窗口并设置开启硬件加速
    function create(){
        var styles={};
        // 在Android5以上设备,如果默认没有开启硬件加速,则强制设置开启
        if(!plus.webview.defaultHardwareAccelerated()&&parseInt(plus.os.version)>=5){
            styles.hardwareAccelerated=true;
        }
        var w = plus.webview.create( "http://weibo.com/dhnetwork", "test", styles );
        plus.webview.show( w ); // 显示窗口
    }
        </script>
        </head>
        <body>
            开启硬件加速显示Webview窗口<br/>
            <button onclick="create()">Create</button>
        </body>
    </html>
**height:** (String 类型 )窗口的高度

支持百分比、像素值,默认为100%。 当未设置height属性值时,优先通过top和bottom属性值来计算窗口的高度。
**kernel:** (String 类型 )窗口使用的内核

可取值: "WKWebview" - 在iOS8.0及以上系统使用WKWebview内核,低版本下仍然使用UIWebview内核; "UIWebview" - 在所有版本上都使用UIWebview内核。 默认值为“UIWebview”。 使用UKWebview内核会有更好的性能,但在功能上有些限制,目前已知的问题有: 1. 不支持设置cookie,即plus.navigator.setCookie() API无法使用; 2. 本地的HTML页面中的XHR不支持跨域访问,需使用plus.net.XMLHttpRequest来替换; 3. 不支持使用WebSQL,需使用indexDB来替换; 4. 不支持js原生混淆功能,需使用前端js混淆来替换。
平台支持
    Android - ALL (不支持)
    iOS - ALL (支持)
left: (String 类型 )窗口水平向右的偏移量

支持百分比、像素值,默认值为0px。 未设置left属性值时,优先通过right和width属性值来计算窗口的left位置。
**margin:** (String 类型 )窗口的边距

用于定位窗口的位置,支持auto,auto表示居中。若设置了left、right、top、bottom则对应的边距值失效。
**mask:** (String 类型 )窗口的遮罩

用于设置Webview窗口的遮罩层样式,遮罩层会覆盖Webview中所有内容,包括子webview,并且截获webview的所有触屏事件,此时Webview窗口的点击操作会触发maskClick事件。 字符串类型,可取值: rgba格式字符串,定义纯色遮罩层样式,如"rgba(0,0,0,0.5)",表示黑色半透明; "none",表示不使用遮罩层; 默认值为"none",即无遮罩层。
    <!DOCTYPE html>
    <html>
        <head>
        <meta charset="utf-8">
        <title>Webview Example</title>
        <script type="text/javascript">
    var ws=null;
    // H5 plus事件处理
    function plusReady(){
        ws=plus.webview.currentWebview();
        // 显示遮罩层
        ws.setStyle({mask:"rgba(0,0,0,0.5)"});
        // 点击关闭遮罩层
        ws.addEventListener("maskClick",function(){
            ws.setStyle({mask:"none"});
        },false);
    }
    if(window.plus){
        plusReady();
    }else{
        document.addEventListener("plusready",plusReady,false);
    }
        </script>
        </head>
        <body>
            Webview窗口页面加载完成事件
            <br/>
            点击窗口关闭遮罩层
        </body>
    </html>
**opacity:** (Number 类型 )窗口的不透明度

0为全透明,1为不透明,默认值为1,即不透明。
平台支持
    Android - 2.2+ (支持): 需Android4.0及以上平台才支持,Android4.0以前平台忽略此属性。
    iOS - 4.3+ (支持)
popGesture: (String 类型 )窗口的侧滑返回功能

可取值"none":无侧滑返回功能;"close":侧滑返回关闭Webview窗口;"hide":侧滑返回隐藏webview窗口。 仅iOS平台支持。
平台支持
    Android - 2.2+ (不支持): 忽略此属性。
    iOS - 5.0+ (支持)
   // 创建webview支持侧滑返回:
    var wv=plus.webview.create('url','id',{'popGesture':'close'});
    wv.show();
    // 或这样写
    var wv=plus.webview.open('url','id',{'popGesture':'close'});

    // 动态改变webview的侧滑返回功能:
    var wv=plus.webview.currentWebview();
    // 关闭侧滑返回功能
    wv.setStyle({'popGesture':'none'});
    // 侧滑返回后关闭webview
    wv.setStyle({'popGesture':'close'});
    // 侧滑返回后隐藏webview
    wv.setStyle({'popGesture':'hide'});
**render:** (String 类型 )窗口渲染模式

支持以下属性值: "onscreen" - Webview窗口在屏幕区可见时渲染,不可见时不进行渲染,此时能减少内存使用量; "always" - Webview在任何时候都渲染,在内存较大的设备上使用,被遮挡的窗口在此中模式下显示的时候会有更流畅的效果。 默认值为"onscreen"。 仅Android平台支持。
平台支持
    Android - 2.2+ (支持)
    iOS - 4.3+ (不支持)
**right:** (String 类型 )窗口水平向左的偏移量

支持百分比、像素值,默认无值(根据left和width属性值来自动计算)。 当设置了left和width值时,忽略此属性值; 当未设置width值时,可通过left和bottom属性值来确定窗口的宽度。
**scalable:** (Boolean 类型 )窗口是否可缩放

窗口设置为可缩放(scalable:true)时,用户可通过双指操作放大或缩小页面,此时html页面可通过meta节点设置“name="viewport" content="user-scalable=no"”来限制页面不可缩放。 窗口设置为不可缩放(scalable:false)时,用户不可通过双指操作放大或缩小页面,即使页面中的meta节点也无法开启可缩放功能。 默认值为false,即不可缩放。
**scrollIndicator:** (String 类型 )窗口是否显示滚动条

用于控制窗口滚动条样式,可取值: "all":垂直和水平滚动条都显示; "vertical":仅显示垂直滚动条; "horizontal":仅显示水平滚动条; "none":垂直和水平滚动条都不显示。 默认值为"all",即垂直和水平滚动条都显示。 注意:显示滚动条的前提条件是窗口中的内容超过窗口显示的宽或高。
**scrollsToTop:** (Boolean 类型 )点击设备的状态栏时是否滚动返回至顶部

true表示点击设备的状态栏可以滚动返回至顶部,false表示点击设备的状态栏不可以,默认值为true。 此功能仅iOS平台支持,在iPhone上有且只有一个Webview窗口的scrollsToTop属性值为true时才生效,所以在显示和关闭Webview窗口时需动态更新所有Webview的scrollsToTop值,已确保此功能生效。
平台支持
    Android - ALL (不支持)
    iOS - 5.0+ (支持)
softinputMode: (String 类型 )弹出系统软键盘模式

可选值:“adjustPan”- 弹出软键盘时Webview窗口自动上移,以保证当前输入框可见;“adjustResize”- 自动调整Webview窗口大小(屏幕区域减去软键盘区域),同时自动滚动Webview保证输入框可见。 默认值为“adjustPan”。
平台支持
    Android - ALL (不支持)
    iOS - 5.0+ (支持)
top: (String 类型 )窗口垂直向下的偏移量

支持百分比、像素值,默认值为0px。 未设置top属性值时,优先通过bottom和height属性值来计算窗口的top位置。

transition: (WebviewTransition 类型 )窗口定义窗口变换的动画效果
transform: (WebviewTransform 类型 )窗口定义窗口变形效果
position: (WebviewPosition 类型 )Webview窗口的排版位置

当Webview窗口添加到另外一个窗口中时,排版位置才会生效,排版位置决定子窗口在父窗口中的定位方式。 可取值:"static",控件在页面中正常定位,如果页面存在滚动条则随窗口内容滚动;"absolute",控件在页面中绝对定位,如果页面存在滚动条不随窗口内容滚动;"dock",控件在页面中停靠,停靠的位置由dock属性值决定。 默认值为"absolute"。
平台支持
    Android - (支持)
    iOS - (支持)
width: (String 类型 )窗口的宽度

支持百分比、像素值,默认为100%。未设置width属性值时,可同时设置left和right属性值改变窗口的默认宽度。
zindex: (Number 类型 )窗口的堆叠顺序值

拥有更高堆叠顺序的窗口总是会处于堆叠顺序较低的窗口的前面,拥有相同堆叠顺序的窗口后调用show方法则在前面。

WebviewExtraOptions

JSON对象,原生窗口扩展参数
属性:

acceleration: (String 类型 )窗口动画加速

开启窗口动画加速功能可优化窗口动画效果,提升动画流程度,可避免部分设备上打开(关闭)窗口闪屏的问题。 可取值: "auto" - 自动优化窗口动画; "none" - 关闭窗口动画加速功能; "capture" - 使用截屏方式加速窗口动画。 默认值为"auto"。
平台支持
    Android - 2.3+ (支持): 设置为"auto"值时:"pop-in"、"pop-out"窗口动画使用截屏方式加速,窗口关闭动画("slide-out-right"、"slide-out-left"、"fade-out"、"zoom-in"、"zoom-fade-in")在Android5以上设备使用截屏方式加速,其它窗口动画不使用截屏加速; 设置为"none"值时:"pop-in"动画类型依然会使用截屏方式加速窗口动画(此时请使用"slid-in-*"动画效果替换); 设置为"capture"值时:"none"动画类型不生效。
    iOS - 5.1+ (不支持): 暂不支持,忽略此参数。
capture: (Bitmap 类型 )窗口动画加速时使用的图片

当使用截屏方式加速窗口动画时,可设置已经创建好的截屏图片,此时不会进行实时截屏操作,加速窗口动画响应时间,提升用户体验。 如果未指定截屏图片,则实时截取当前Webview窗口对象的图片进行动画操作。 如果窗口未使用截屏方式加速动画,则忽略此参数。
平台支持
    Android - 2.3+ (支持): 仅"pop-in"、"pop-out"窗口动画效果支持此参数,其它窗口动画忽略此参数。
    iOS - 5.1+ (不支持): 暂不支持,忽略此参数。
otherCapture: (Bitmap 类型 )关联窗口动画使用的图片

当使用截屏方式加速窗口动画时,可设置已经创建好的截屏图片,此时不会进行实时截屏操作,加速关联窗口动画响应时间,提升用户体验。 如果未指定截屏图片,则实时截取关联Webview窗口对象的图片进行动画操作。 如果窗口未使用截屏方式加速动画,则忽略此参数。
平台支持
    Android - 2.3+ (支持): 仅"pop-in"、"pop-out"窗口动画效果支持此参数,其它窗口动画忽略此参数。
    iOS - 5.1+ (不支持): 暂不支持,忽略此参数。

示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null,wn=null;
var bitmap1=null,bitmap2=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    // 截图
    bitmap1 = new plus.nativeObj.Bitmap();
    // 将webview内容绘制到Bitmap对象中
    wc.draw(bitmap1,function(){
        console.log('bitmap1绘制图片成功');
    },function(e){
        console.log('bitmap1绘制图片失败:'+JSON.stringify(e));
    });
    // 预创建新Webview窗口
    wn=plus.webview.create("http://weibo.com/dhnetwork");
    wn.addEventListener("loaded",function(){
        bitmap2 = new plus.nativeObj.Bitmap();
        wn.draw( bitmap2, function(){
            console.log("bitmap2截图成功");
        }, function(e){
            console.log("bitmap2截图失败:"+JSON.stringify(e));
        } );
    },false);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 创建并显示Webview窗口
function showWebview(){
    wn.show( "pop-in", 300, function(){
        // 动画完成,销毁截图
        bitmap1.clear();
        bitmap2.clear();
    }, {capture:bitmap2,otherCapture:bitmap1} );
}
    </script>
    </head>
    <body>
        使用截图显示Webview窗口<br/>
        <button onclick="showWebview()">show</button>
    </body>
</html>

WebviewTransform

一组用于定义页面或控件变形的属性
WebviewTransition

一组用于定义页面或控件转换效果的属性
属性:

property: (String 类型 )产生变换效果的属性

默认值为"all",暂不支持其它值。
平台支持
    Android - 2.2+ (支持)
    iOS - 4.3+ (支持)
duration: (String 类型 )变换持续的时间

默认值为0,即无动画效果。
平台支持
    Android - 2.2+ (支持)
    iOS - 4.3+ (支持)
timingfunction: (String 类型 )窗口变换效果

可取值: "linear":匀速变化,匀速动画效果; "ease-in":匀加速变化,逐渐变快的动画效果; "ease-out":匀减速变化,逐渐变慢的动画效果; "ease-in-out":先加速后减速变化,先变快后变慢的动画效果。 默认值为"ease-in-out"。
平台支持
    Android - 2.2+ (支持)
    iOS - 4.3+ (支持)

WebviewOverridResourceOptions

拦截Webview窗口资源请求的参数
属性:

match: (String 类型 )区配需要拦截请求资源的URL地址

支持正则表达式,默认值为空字符串(即不拦截)。
redirect: (String 类型 )拦截重定向的资源地址

仅支持本地资源地址,如"_www"、"_doc"、"_downloads"、"_documents"等开头的路径。
mime: (String 类型 )重定向的资源数据类型

RFC2045/RFC2046/RFC2047/RFC2048/RFC2049规范中定义的数据类型。 如普通文本(text/plain)、PNG图像(image/png)、GIF图形(image/gif)、JPEG图形(image/jpeg)。 如果未指定mime类型,则根据重定向资源路径自动区配。
encoding: (String 类型 )重定向的资源数据编码

如未设置,则使用默认值"UTF-8"。

WebviewOverrideUrlOptions

拦截Webview窗口URL请求的属性
属性:

mode: (String 类型 )拦截模式

可取值: "allow"表示满足match属性定义的条件时不拦截url继续加载,不满足match属性定义的条件时拦截url跳转并触发callback回调; "reject"表示满足match属性定义的提交时拦截url跳转并触发callback回调,不满足match属性定义的条件时不拦截url继续加载。 默认值为"reject"。
match: (String 类型 )区配是否需要处理的URL请求

支持正则表达式,默认值为对所有URL地址生效(相当于正则表达式“.*”)。 如果mode值为"allow"则允许区配的URL请求跳转,mode值为"reject"则拦截区配的URL请求。

WebviewListenResourceOptions

监听Webview窗口资源加载的属性
属性:

match: (String 类型 )区配是否需要处理的URL资源

支持正则表达式,默认值为对所有URL资源请求生效(相当于正则表达式“.*”)。 如果Webview加载的资源区配条件,则触发回调事件。

BounceEventCallback

Webview窗口回弹事件的回调函数

void onEvent( Event event ){
    // Event handled code.
}

参数:

event: ( Event ) 必选 Webview窗口回弹事件触发时事件数据
Event对象包含以下属性: status - 表示回弹位置状态,设置顶部下拉回弹changeoffset属性后可取值:"beforeChangeOffset"表示可继续拖拽,此时松开拖拽窗口会回弹到其初始位置; "afterChangeOffset"表示回弹可停靠,此时松开拖拽窗口会回弹到停靠位置; "dragEndAfterChangeOffset"表示已进松开拖拽,并且窗口回弹到停靠位置。 设置左右侧侧滑slideoffset属性后可取值:"beforeSlide"表示未侧滑状态;"afterSlide"表示已侧滑状态。 offset - 表示回弹方向,可取值:"left"表示左侧滑动,"right"表示右侧滑动,"top"表示上侧下拉滑动。 target - 保存触发回弹此事件的Webview窗口对象。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var ws=null;
// H5 plus事件处理
function plusReady(){
    ws=plus.webview.currentWebview();
    ws.setBounce({position:{top:"100px"},changeoffset:{top:"44px"}});
    ws.addEventListener("dragBounce",onPullStateChange,false);
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}
// 下拉状态改变
function onPullStateChange(e){
    switch(e.status){
        case "beforeChangeOffset":
        console.log("顶部回弹:可继续往下拖拽");
        break;
        case "afterChangeOffset":
        console.log("顶部回弹:松开会重置回弹位置");
        break;
        case "dragEndAfterChangeOffset":
        console.log("顶部回弹:松开停靠回弹");
        break;
        default:
        break;
    }
}
// 重置窗口回弹位置
function resetBounce(){
    ws.resetBounce();
}
    </script>
    </head>
    <body style="text-align:center;">
        <br/><br/><br/>
        设置Webview窗口的回弹效果<br/>
        回弹后显示停靠到44px的位置<br/><br/>
        <button onclick="resetBounce()">重置回弹位置</button>
        <br/><br/><br/>
        *暂仅支持顶部的回弹效果*
    </body>
</html>

EventCallback

Webview窗口事件的回调函数

void onEvent( Event event ){
    // Event handled code.
}

参数:

event: ( Event ) 必选 Webview窗口事件触发时事件数据
Event对象包含以下属性: target:保存触发此事件的Webview窗口对象。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var nw=null;
// H5 plus事件处理
function plusReady(){
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 监听Webview窗口页面加载完成事件
function eventTest() {
    // 打开新窗口
    nw=plus.webview.create( "", "", {top:"46px",bottom:"0px"} );
    nw.addEventListener( "loaded", function(e){
        console.log( "Loaded: "+e.target.getURL() );
    }, false );
    nw.show(); // 显示窗口
}
    </script>
    </head>
    <body>
        Webview窗口页面加载完成事件
        <button onclick="eventTest()">start</button>
        <button onclick="nw.loadURL('http://m.csdn.net')">loaded</button>
    </body>
</html>

PopGestureCallback

Webview窗口侧滑事件的回调函数

void onEvent( PopGestureEvent event ){
    // Event handled code.
}

参数:

event: ( PopGestureEvent ) 必选 Webview窗口事件触发时事件数据
PopGestureEvent对象包含以下属性: target:保存侧滑操作的Webview窗口对象。 type:保存侧滑事件类型,"start"表示开始侧滑返回,用户按下滑动时触发; “end”表示结束侧滑返回,用户松手时触发; “move"表示侧滑返回动作结束,用户移动侧滑时触发。 result:保存操作结果,仅在e.type为end时有效,boolean类型, true表示侧滑返回执行,false表示侧滑返回取消;否则为undefined。 progress:保存侧滑位置,Number类型,可带小数点,范围为0-100。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
    <meta charset="utf-8">
    <title>Webview Example</title>
    <script type="text/javascript">
var nw=null;
// H5 plus事件处理
function plusReady(){
    createWebview();
}
if(window.plus){
    plusReady();
}else{
    document.addEventListener("plusready",plusReady,false);
}

// 创建Webview窗口监听侧滑返回事件
function createWebview(){
    // 打开新窗口
    nw=plus.webview.create( "http://m.csdn.net/","",{top:"100px",bottom:"0px",popGesture:"hide"} );
    nw.addEventListener( "popGesture", function(e){
        poplog.innerText="popGesture: "+e.type+","+e.result+","+e.progress;
    }, false );
}
// 显示Webview窗口
function showWebview(){
    nw.show( "slide-in-right" );
}
// 隐藏Webview窗口
function hideWebview(){
    nw.hide();
}
// 关闭窗口
function closeWebview(){
    nw.close();
    plus.webview.currentWebview().close();
}
    </script>
    </head>
    <body>
        Webview窗口侧滑返回事件
        <button onclick="closeWebview()">Close</button>
        <button onclick="showWebview()">Show</button>
        <button onclick="hideWebview()">Hide</button>
        <div id="poplog"></div>
    </body>
</html>

HistoryQueryCallback

历史记录记录查询的回调函数

void onQuery( Event event ) {
    // Event handled code.
}

参数:

event: ( Event ) 必选 查询Webview窗口历史记录操作事件数据
可通过event的canBack属性获取Webview窗口是否可后退,通过event的canForward属性获取Webview窗口是否可前进。 

返回值:
void : 无
ListenResourceLoadingCallback

Webview窗口加载资源事件的回调函数

void onLoadingResource( Event event ) {
    // Event handled code.
    var url = event.url;
}

参数:

event: ( Event ) 必选 Webview窗口加载资源事件数据
可通过event的url属性获取要加载的资源URL地址。 

返回值:
void : 无
OverrideUrlLoadingCallback

Webview窗口拦截URL链接跳转的回调函数

void onOverride( Event event ) {
    // Event handled code.
    var url = event.url;
}

参数:

event: ( Event ) 必选 Webview窗口拦截URL跳转事件数据
可通过event的url属性获取拦截的URL地址。 

返回值:
void : 无
TitleUpdateCallback

Webview窗口加载页面标题更新的回调函数

void onQuery( Event event ) {
    // Event handled code.
}

参数:

event: ( Event ) 必选 Webview窗口加载页面标题更新事件数据
可通过event的title属性获取Webview窗口的标题。 

返回值:
void : 无
SuccessCallback

Webview窗口操作成功回调函数

void onSuccess(){
    // Success code.
}

说明:

Webview窗口业务操作成功后触发回调函数。
参数:


返回值:
void : 无
ErrorCallback

Webview窗口操作失败回调函数

void onError(error){
    // Handle the error
    var code = error.code; // 错误编码
    var message = error.message; // 错误描述信息
}

参数:

error: ( Exception ) 可选 Webview窗口操作错误信息
可通过error.code(Number类型)获取错误编码; 可通过error.message(String类型)获取错误描述信息。 

返回值:
void : 无

作者:xiejunna 发表于2016/11/8 20:41:56 原文链接
阅读:11 评论:0 查看评论

setContentView那些事

$
0
0

刨根问底setContentView

在平时的android开发中,经常会使用到Activity#setContentView方法来设置我们自己的布局,那么setContentView中到底做了什么,我们的布局
是怎么加载并显示到手机屏幕上的,这就是今天要讨论的内容,看下Activity#setContentView方法

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}

可以看到这里是使用getWindow().setContentView(layoutResID);来进行具体的设置,那么getWindow()又是什么

public Window getWindow() {
        return mWindow;
}

这里直接返回mWindow,其实mWindow是在Activity#attach方法中赋值的。

 mWindow = new PhoneWindow(this);

根据前面的activity启动流程中知道Activity#attach方法是在activity启动过程中执行的,所以当该activity启动完成以后,mWindow其实就是PhoneWindow

// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;

@Override
public void setContentView(int layoutResID) {

        if (mContentParent == null) {//如果我们之前没有设置过布局,或者第一次设置布局
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {//默认为false
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            // 加载我们设置的布局到mContentParent中去
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        // 可以多次设置布局,当布局发生改变,会回调activity中的方法
        final Window.Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
}

在activity中,我们可以多次设置布局,如果我们之前没有设置过布局,或者第一次设置布局,此时mContentParent就为null,mContentParent是我们设置布局的父布局,是一个ViewGroup,也就是系统中id是”com.android.internal.R.id.content”的ViewGroup

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

hasFeature(FEATURE_CONTENT_TRANSITIONS)默认值为false,通过inflate来加载我们设置的布局

创建DecorView

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); // 1.创建一个DecorView
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) { // 如果第一次设置布局,mContentParent是我们设置布局的父布局
            mContentParent = generateLayout(mDecor);

            .......
        }
    }

将需要显示的布局添加到DecorView中

DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。在该布局下面,有标题view和内容view这两个子元素,而内容view则是上面提到的mContentParent

 protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 获取系统自定义的一些属性
        TypedArray a = getWindowStyle();

        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }

        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        // 下面是根据应用层在代码中或者样式文件里设置的requestFeature进行具体的设置,可以看到这里
        // requestFeature(FEATURE_NO_TITLE)的设置就是在setContentView中设置的,这也就是为什么
        // 我们必须要在setContentView之前设置requestFeature(FEATURE_NO_TITLE)的原因,其实不止这一个,
        // 下面的属性都应遵循这样的规则
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        ......

        // 设置WindowManager窗口显示的一些布局属性
        WindowManager.LayoutParams params = getAttributes();

        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }

        if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            if (!haveDimAmount()) {
                params.dimAmount = a.getFloat(
                        android.R.styleable.Window_backgroundDimAmount, 0.5f);
            }
        }

        .....
        mDecor.startChanging();
        // 加载设置的布局,并且添加到decor中
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        mDecor.finishChanging();

        return contentParent;
    }

DecorView和Window的关联

到现在位置,我们的布局已经加载到了DecorView中,那么DecorView是怎么和window关联起来的呢,在activity启动流程中,我们知道当activity启动的时候,会执行ActivityThread#handleLaunchActivity方法


private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { 
    ....
    Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);

        }
    ....

}





final void handleResumeActivity(IBinder token,
      ....
            // 获取当前activity对应的window,并将其和DecorView进行关联
            // 这里多说一句,由于
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 添加当前decor和对应的layoutparams到window中
                    wm.addView(decor, l);
                }
           }
    .....
}

我们知道Window的实现类是WindowManagerImpl,看下WindowManagerImpl#addView

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        // 每一个window都又一个唯一标识的token,这里如果没有,则设置系统默认的
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
}

mGlobal是一个WindowManagerGlobal类型对象,最终添加view到窗口显示也是由它来具体操作的

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        .......
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            .......
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams); 
        }

        // do this last because it fires off messages to start doing things
        try {
            // 2. 通过ViewRootImpl设置当前DecorView
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

View的绘制流程

继续,看下ViewRootImpl的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ....
    requestLayout(); // 核心的调用,requestLayout开始绘制当前DecorView
    ....


}


@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
       checkThread(); //检查当前是否是UI线程
       mLayoutRequested = true;
       scheduleTraversals(); 
    }
}

在requestLayout方法中,又调用了scheduleTraversals,我们继续:

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

这里开启一个TraversalRunnable线程

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
}




void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

performTraversals方法很长,实际上主要做了下面三件事情:

private void performTraversals() {
    ....
    if (需要重新计算宽度和高度) {
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    if (需要重新布局) {
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    }

    if (需要重新布局) {
        performDraw();
    }

    ....

}

在performMeasure中调用了对应的View的measure方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}



public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ....
    // measure ourselves, this should set the measured dimension flag back
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....
}

可以发现,最终通过onMeasure方法设置我们的View的宽度和高度,并且一定要通过setMeasuredDimension来设置

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    ....
    final View host = mView;
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ....

}



public void layout(int l, int t, int r, int b) {
        ....

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

        }

}

这里的onLayout主要是用来设置ViewGroup的子元素的位置的,这也就是为什么我们需要在自定义的ViewGroup中onLayout方法来设置子元素的位置了。

接下来是performDraw

private void performDraw() {
    ....
     try {
            draw(fullRedrawNeeded);
     } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
    ....

}



private void draw(boolean fullRedrawNeeded) {

        .....
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
        }

        // 如果是动画的,比如scroller控制的,则又会接着重新调用scheduleTraversals进行绘制
        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
}


private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
    ....
    mView.draw(canvas);
    ....

}

在看下View#draw方法

@CallSuper
    public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }


        .....
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
}

在该方法中调用了onDraw方法进行具体的绘制操作.

ok,关于setContentView的流程就到这里了,其中包括DecorView是如何与Window建立关联的,以及View的绘制流程。如有补充,欢迎指教。

作者:mockingbirds 发表于2016/11/8 20:44:07 原文链接
阅读:14 评论:0 查看评论

html5 net XMLHttpRequest

$
0
0

XMLHttpRequest模块管理网络请求,与标准HTML中的XMLHttpRequest用途一致,差别在于前者可以进行跨域访问。通过plus.net可获取网络请求管理对象。
对象:

XMLHttpRequest: 跨域网络请求对象
ProgressEvent: HTTP请求进度事件

回调方法:

XhrStateChangeCallback: 网络请求状态变化的回调函数
XhrProgressEventCallback: 网络请求进度事件的回调函数

权限:

permissions

{
// ...
"permissions":{
    // ...
    "XMLHttpRequest": {
        "description": "跨域网络连接"
    }
}
}

XMLHttpRequest
跨域网络请求对象
构造:

XMLHttpRequest(): 创建一个XMLHttpRequest 对象,对象创建时不触发任何时间和网络请求,需和open,send方法配合使用。

属性:

    readyState: HTTP 请求的状态
    response: 请求从服务器接收到的响应数据
    responseText: 请求从服务器接收到的响应数据(字符串数据)
    responseType: 请求响应数据response的类型
    responseXML: 请求响应的Document对象
    status: 服务器返回的HTTP状态代码
    statusText: 服务器返回的HTTP状态描述
    timeout: 请求服务器的超时时间,单位为毫秒(ms)
    withCredentials: 是否支持跨域请求

方法:

 abort: 取消当前响应,关闭连接并且结束任何未决的网络活动
    getAllResponseHeaders: 获取HTTP响应头部信息
    getResponseHeader: 获取指定的HTTP响应头部的值
    open: 初始化HTTP请求参数,例如URL和HTTP方法,但是并不发送请求
    overrideMimeType: 重写服务器返回的MIME类型
    send: 发送HTTP请求
    setRequestHeader: 指定一个HTTP请求的Header

事件:

   onreadystatechange: 网络请求状态发生变化事件
    onloadstart: 网络请求开始事件
    onprogress: 网络请求传输数据事件
    onabort: 网络请求取消事件
    onerror: 网络请求错误事件
    onload: 网络请求成功事件
    ontimeout: 网络请求超时事件
    onloadend: 网络请求结束事件

XMLHttpRequest()

创建一个XMLHttpRequest 对象,对象创建时不触发任何时间和网络请求,需和open,send方法配合使用。

var xhr = new plus.net.XMLHttpRequest();

参数:


返回值:
XMLHttpRequest :
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"/>
        <script type="text/javascript">
document.addEventListener('plusready', function(){
var xhr = new plus.net.XMLHttpRequest();
xhr.onreadystatechange = function () {
    switch ( xhr.readyState ) {
        case 0:
            alert( "xhr请求已初始化" );
        break;
        case 1:
            alert( "xhr请求已打开" );
        break;
        case 2:
            alert( "xhr请求已发送" );
        break;
        case 3:
            alert( "xhr请求已响应");
            break;
        case 4:
            if ( xhr.status == 200 ) {
                alert( "xhr请求成功:"+xhr.responseText );
            } else {
                alert( "xhr请求失败:"+xhr.readyState );
            }
            break;
        default :
            break;
    }
}
xhr.open( "GET", "http://www.baidu.com/" );
xhr.send();
}, false );
        </script>
    </head>
    <body onload="onload();">
    </body>
</html>

readyState

HTTP 请求的状态
说明:

Number 类型

当一个 XMLHttpRequest 初次创建时,这个属性的值从 0 开始,直到接收到完整的 HTTP 响应,这个值增加到 4。 5 个状态中每一个都有一个相关联的非正式的名称,下表列出了状态、名称和含义: 0 Uninitialized,未初始化状态。XMLHttpRequest对象已创建或已被abort()方法重置。 1 Open,open()方法已调用,但是send()方法未调用。请求还没有被发送。 2 Sent,send()方法已调用,HTTP 请求已发送到Web服务器。未接收到响应。 3 Receiving,所有响应头部都已经接收到。响应体开始接收但未完成。 4 Loaded,HTTP响应已经完全接收。
response

请求从服务器接收到的响应数据
说明:

String 类型 只读属性

如果没有从服务器接收到数据,则为null; 否则根据responseType类型决定: 如果responseType设置为空字符串或”text”,则返回空字符串; 如果responseType设置为”document”,则返回Document对象; 如果responseType设置为”json”,则返回JSON对象; 若服务器返回的数据与设置的responseType类型不区配,则返回null。
responseText

请求从服务器接收到的响应数据(字符串数据)
说明:

String 类型

如果还没有接收到数据的话,此属性值为空字符串; 如果readyState小于3,此属性值为空字符串; 如果readyState为3,此属性值返回目前已经接收的HTTP响应部分数据值; 如果readyState为4,此属性值保存了完整的HTTP响应数据体。 如果HTTP请求返回的数据头中包含了Content-Type值中指定字符编码,就使用该编码,否则,使用UTF-8字符集。
responseType

请求响应数据response的类型
说明:

String 类型

默认值为空字符串,即reponse为String,类型可设置:”document”表示Document对象,”json”表示JSON对象,”text”表示字符串。 此值必须在调用send方法之前设置,否则设置的值不生效,仍使用之前设置的值。
responseXML

请求响应的Document对象
说明:

String 类型

对请求的响应,解析为 XML 并作为 Document 对象返回。 如果请求未成功,或响应的数据无法被解析为XML,则返回null。
status

服务器返回的HTTP状态代码
说明:

Number 类型

服务器返回的HTTP状态代码,如200表示成功,而404表示”Not Found”错误; 当readyState小于3的时候此属性值为0。
statusText

服务器返回的HTTP状态描述
说明:

String 类型

此属性值用名称而不是数字指定了请求的HTTP的状态代码。 也就是说,当状态为200的时候它是”OK”;当状态为404的时候它是”Not Found”。 和status属性类似,当readyState小于3的时候读取这一属性会返回空字符串。
timeout

请求服务器的超时时间,单位为毫秒(ms)
说明:

Number 类型

数值类型,单位为ms,其默认值为120秒。 超时时间为服务器响应请求的时间(不是Http请求完成的总时间),如果设置为0则表示永远不超时。 必须在请求发起前设置,否则当前请求将不生效,在当前请求完成后重新发起新请求时生效。
withCredentials

是否支持跨域请求
说明:

Boolean 类型 只读属性

此对象创建的HTTP请求都支持跨域,所以永远返回true。
abort

取消当前响应,关闭连接并且结束任何未决的网络活动

void xhr.abort();

说明:

此方法把XMLHttpRequest对象重置为readyState为0的状态,并且取消所有未决的网络活动。 调用此方法后将停止触发XMLHttpRequest对象的所有事件。 例如,如果请求用了太长时间,而且响应不再必要的时候,可以调用这个方法。
参数:


返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "GET", "http://www.dcloud.io/" );
    xhr.send();
}
function abortXHR(){
    if(xhr){
        xhr.abort();
        xhr=null;
    }
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
        <button onclick="abortXHR()">Abort</button>
    </body>
</html>

getAllResponseHeaders

获取HTTP响应头部信息

String xhr.getAllResponseHeaders();

说明:

把HTTP响应头部作为未解析的字符串返回。 如果readyState小于3,这个方法返回null。 否则,它返回服务器发送的所有 HTTP 响应的头部。头部作为单个的字符串返回,一行一个头部。每行用换行符”\r\n”隔开。
参数:


返回值:
String : HTTP 响应头
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "GET", "http://www.dcloud.io/" );
    xhr.send();
}
// 获取所有HTTP响应头部信息
function allResponseHeader(){
    if(xhr){
        console.log(xhr.getAllResponseHeaders());
    }
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
        <button onclick="allResponseHeader()">getAllResponseHeaders</button>
    </body>
</html>

getResponseHeader

获取指定的HTTP响应头部的值

String xhr.getResponseHeader( headerName );

说明:

其参数是要返回的 HTTP 响应头部的名称。可以使用任何大小写来制定这个头部名字,和响应头部的比较是不区分大小写的。 该方法的返回值是指定的 HTTP 响应头部的值,如果没有接收到这个头部或者readyState小于3则为空字符串。 如果接收到多个有指定名称的头部,这个头部的值被连接起来并返回,使用逗号和空格分隔开各个头部的值。
参数:

headerName: ( String ) 可选 HTTP响应头数据名称

返回值:
String : HTTP响应头数据值
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "GET", "http://www.dcloud.io/" );
    xhr.send();
}
// 获取所有HTTP响应Content-Type信息
function getResponseHeader(){
    if(xhr){
        console.log(xhr.getResponseHeaders("Content-Type"));
    }
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
        <button onclick="getResponseHeader()">getResponseHeader</button>
    </body>
</html>

open

初始化HTTP请求参数,例如URL和HTTP方法,但是并不发送请求

void xhr.open( method, url, username, password );

说明:

这个方法初始化请求参数以供 send() 方法稍后使用。它把readyState设置为1,删除之前指定的所有请求头部,以及之前接收的所有响应头部,并且把responseText、responseXML、status 以及 statusText 参数设置为它们的默认值。 当readyState为0的时候(当XMLHttpRequest对象刚创建或者abort()方法调用后)以及当readyState为4时(已经接收响应时),调用这个方法是安全的。 当针对任何其他状态调用的时候,open()方法的行为是为指定的。 除了保存供send()方法使用的请求参数,以及重置 XMLHttpRequest 对象以便复用,open()方法没有其他的行为。 要特别注意,当这个方法调用的时候,实现通常不会打开一个到Web服务器的网络连接。
参数:

  method: ( String ) 必选 请求URL的HTTP协议方法
    值可以为"GET""POST"。
    url: ( String ) 必选 请求URL地址
    username: ( String ) 可选 请求URL所需的授权提供认证资格用户名
    password: ( String ) 可选 请求URL所需的授权提供认证资格密码

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    // 初始化HTTP请求
    xhr.open( "GET", "http://www.dcloud.io/" );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
    </body>
</html>

overrideMimeType

重写服务器返回的MIME类型

void xhr.overrideMimeType( mime );

说明:

此方法覆盖HTTP请求返回数据头”Content-Type”字段值中包含的IMIE类型,如果设置的MIME类型无效则继续使用”Content-Type”字段值中包含的IMIE类型。 如果MIME类型中指定了字符集类型(charset),则需按照指定的字符集类型对接收到的数据体(respose)进行处理,否则默认为UTF-8字符集。 注意:此方法需在send方法前调用。
参数:

mime: ( String ) 必选 要指定的MIME类型

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    // 初始化HTTP请求
    xhr.open( "GET", "http://www.dcloud.io/" );
    xhr.overrideMimeType( "text/plain; charset=utf-8" );        // 将返回的数据当做UTF-8字符集的纯文本类型处理
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
    </body>
</html>

send

发送HTTP请求

void send( body );

说明:

此方法触发HTTP请求发送,如果之前没有调用open(),或者更具体地说,如果readyState不是1,send()抛出一个异常。否则,将发送HTTP请求,该请求由以下几部分组成: 之前调用open()时指定的HTTP方法、URL; 之前调用setRequestHeader()时指定的请求头部(如果有的话); 传递给这个方法的body参数。 一旦请求发送了,send()把readyState设置为2,并触发onreadystatechange事件; 如果服务器响应带有一个HTTP重定向,send()方法在后台线程自动遵从重定向; 当所有的HTTP响应头部已经接收,send()或后台线程把readyState设置为3并触发onreadystatechange事件; 如果响应较长,send()或后台线程可能在状态3中触发多次onreadystatechange事件; 最后,当响应完成,send()或后台线程把readyState设置为4,并最后一次触发onreadystatechange事件。
参数:

body: ( String ) 可选 请求HTTP提交的数据内容
当open方法中设置的method参数为POST时必需传入body值。 

返回值:
void : 无
示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "POST", "http://demo.dcloud.net.cn/test/xhr/post.php" );
    var data={name:'HBuilder',version:'0.1.0'};
    // 发送HTTP请求
    xhr.send(JSON.stringify(data));
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
    </body>
</html>

setRequestHeader

指定一个HTTP请求的Header

void setRequestHeader( headerName, headerValue );

说明:

Http的Header应该包含在通过后续send()调用而发起的请求中。 此方法只有当readyState为1的时候才能调用,例如,在调用了open()之后,但在调用send()之前。 如果带有指定名称的头部已经被指定了,这个头部的新值就是:之前指定的值,加上逗号、以及这个调用指定的值(形成一个数组)。 如果Web服务器已经保存了和传递给open()的URL相关联的cookie,适当的Cookie或Cookie2头部也自动地包含到请求中,可以通过调用setRequestHeader()来把这些cookie添加到头部。
参数:

  headerName: ( String ) 必选 HTTP Header名称
    headerValue: ( String ) 必选 HTTP Header值

返回值:
void : 无
平台支持:

Android - 2.2+ (支持): 不支持设置“User-Agent”、“Cookie”的值。
iOS - 5.1+ (支持): 不支持设置“User-Agent”的值。

示例:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var xhr=null;
function testXHR(){
    if(xhr){
        return;
    }
    xhr=new plus.net.XMLHttpRequest();
    xhr.onreadystatechange=function(){
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "POST", "http://demo.dcloud.net.cn/test/xhr/post.php" );
    var data={name:'HBuilder',version:'0.1.0'};
    xhr.setRequestHeader('Content-Type','application/json');
    // 发送HTTP请求
    xhr.send(JSON.stringify(data));
}
        </script>
    </head>
    <body>
        <button onclick="testXHR()">XMLHttpRequest</button><br/>
    </body>
</html>

onreadystatechange
网络请求状态发生变化事件

xhr.onreadystatechange = function(){
    // 判断xhr状态
};

说明:

XhrStateChangeCallback 类型

网络请求状态发生变化时触发,通常在函数中判断对象的state属性值来获取当前请求的状态。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/event.html";
var xhr=null;

function xhrEvent(){
    xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

onloadstart

网络请求开始事件

xhr.onloadstart = function(){
    // xhr请求开始事件处理
};

说明:

XhrProgressEventCallback 类型

通常在调用send方法开始发起HTTP请求时触发。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/json.php";
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onloadstart=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onloadstart: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

onprogress

网络请求传输数据事件

xhr.onprogress= function(){
    // xhr数据传输开始事件处理
};

说明:

XhrProgressEventCallback 类型

通常在HTTP请求链接已经建立,开始传输数据时触发,在数据传输的过程中可能多次触发,此事件与onreadystatechange事件触发状态3类似。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/video/nocrygirl.mp4";//访问一个大文件,以便多次触发onprogress事件
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onprogress=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onprogress: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

onabort

网络请求取消事件

xhr.onabort = function(){
    // xhr请求被取消事件处理
};

说明:

XhrProgressEventCallback 类型

通常在调用abort方法取消HTTP请求时触发。 此事件在onreadystatechange事件触发状态4事件之后。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/video/nocrygirl.mp4";//访问一个大文件,以便操作触发onabort事件
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onabort=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onabort: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
function xhrAbort() {
    if ( xhr ) {
        xhr.abort();
        xhr = null;
    }
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
        <button onclick="xhrAbort()">Abort</button>
    </body>
</html>

onerror

网络请求错误事件

xhr.onerror = function(){
    // xhr请求出错事件处理
};

说明:

XhrProgressEventCallback 类型

通常在HTTP请求发生错误时触发,如无法连接到服务器等各种错误都触发此事件。 此事件在onreadystatechange事件触发状态4事件之后。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/error.html";//访问无效的网络地址处罚onerror事件
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onerror=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onerror: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

onload

网络请求成功事件

xhr.onload = function(){
    // xhr请求成功事件处理
};

说明:

XhrProgressEventCallback 类型

通常在HTTP请求成功完成时触发,如果HTTP请求发生错误则不触发此事件。 此事件在onreadystatechange事件触发状态4事件之后。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/json.php";
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onload=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onload: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

ontimeout

网络请求超时事件

xhr.ontimeout = function(){
    // xhr请求超时事件处理
};

说明:

XhrProgressEventCallback 类型

通常在HTTP请求超时时触发,此时不会触发onerror事件。 此事件在onreadystatechange事件触发状态4事件之后。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/json.php";
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.ontimeout=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("ontimeout: "+str);
    }
    xhr.timeout=1;  // 设置极小的超时时间用于处罚ontimeout事件
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

onloadend

网络请求结束事件

xhr.onloadend = function(){
    // xhr请求结束事件处理
};

说明:

XhrProgressEventCallback 类型

通常在HTTP请求结束时触发,不管是HTTP请求失败、成功、或超时之后都会触发此事件。 此事件在onreadystatechange事件触发状态4事件之后。
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/json.php";
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onloadend=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onloadend: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

ProgressEvent

HTTP请求进度事件

interface ProgressEvent : Event {
    readonly attribute XMLHttpRequest target;
    readonly attribute Boolean lengthComputable;
    readonly attribute Number loaded;
    readonly attribute Number total;
};

属性:

target: (XMLHttpRequest 类型 )事件的目标对象

通知HTTP请求进度事件的XMLHttpRequest对象。
lengthComputable: (Number 类型 )进度信息是否可计算

HTTP请求进度信息是否有效,如果HTTP请求头中包含Content-Length头信息则为true,否则为false。
loaded: (XMLHttpRequest 类型 )当前已经接收到的数据长度

HTTP请求接收到的数据长度,单位为字节。
total: (XMLHttpRequest 类型 )总数据长度

HTTP请求返回的总数据长度,单位为字节。 如果无法获取则设置为0。

XhrStateChangeCallback

网络请求状态变化的回调函数

void onSuccess() {
    // State changed code.
}

参数:


返回值:
void : 无
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/json.php";
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onreadystatechange = function () {
        console.log("onreadystatechange: "+xhr.readyState);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>

XhrProgressEventCallback

网络请求进度事件的回调函数

void onProgressEvent( ProgressEvent event ) {
    // Progress changed code.
}

参数:

event: ( ProgressEvent ) 必选 HTTP请求进度事件

返回值:
void : 无
示例:

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <meta name="HandheldFriendly" content="true"/>
        <meta name="MobileOptimized" content="320"/>
        <title>XMLHttpRequest</title>
        <script type="text/javascript">
var url="http://demo.dcloud.net.cn/test/xhr/json.php";
var xhr=null;
function xhrEvent(){
    xhr = new plus.net.XMLHttpRequest();
    xhr.onloadend=function(e){
        var str="lengthComputable="+e.lengthComputable+"loaded="+e.loaded+";total="+e.total;
        console.log("onloadend: "+str);
    }
    xhr.open( "GET", url );
    xhr.send();
}
        </script>
    </head>
    <body>
        <button onclick="xhrEvent()">XMLHttpRequest</button><br/>
    </body>
</html>
作者:xiejunna 发表于2016/11/8 20:52:12 原文链接
阅读:16 评论:0 查看评论

Android布局优化

$
0
0

重用

  1. include

    < include>标签可以在一个布局中引入另外一个布局,这个的好处显而易见。类似于我们经常用到的工具类,随用随调。便于统一修改使用。

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
    
        <include layout="@layout/toolbar" />
        ...省略
    </LinearLayout>
    

而我toolbar的布局是单独的一个,可以在任务需要的地方进行include

    <android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="?attr/colorPrimary"
    android:navigationIcon="@drawable/back"
    app:layout_scrollFlags="scroll|enterAlways"
    app:navigationIcon="@drawable/back"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

    <!--<RelativeLayout-->
    <!--android:layout_width="match_parent"-->
    <!--android:layout_height="wrap_content"-->
    <!--android:gravity="center">-->


    <TextView
        android:id="@+id/toolbar_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:ellipsize="end"
        android:maxEms="9"
        android:singleLine="true"
        android:text="@string/gallery_app_name"
        android:textColor="@android:color/white"
        android:textSize="18sp" />

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right">

        <TextView
            android:id="@+id/title_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_gravity="right"
            android:layout_marginRight="10dp"
            android:padding="10dp"
            android:textColor="@android:color/white"
            android:textSize="15sp" />


        <ImageView
            android:id="@+id/image_right"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_gravity="right"
            android:layout_marginRight="10dp"
            android:padding="10dp" />
    </FrameLayout>

</android.support.v7.widget.Toolbar>

2.合并


2.1 减少嵌套

  • 首先我们心中要有一个大原则:尽量保持布局层级的扁平化。在这个大原则下我们要知道:
    在不影响层级深度的情况下,使用LinearLayout而不是RelativeLayout。因为RelativeLayout会让子View调用2次onMeasure,LinearLayout 在有weight时,才会让子View调用2次onMeasure。Measure的耗时越长那么绘制效率就低。

  • 如果非要是嵌套,那么尽量避免RelativeLayout嵌套RelativeLayout。这简直就是恶性循环,丧心病狂

2.2 使用merge

< merge/>主要用来去除不必要的FrameLayout。它的使用最理想的情况就是你的根布局是FrameLayout,同时没有使用background等属性。这时可以直接替换。因为我们布局外层就是FrameLayout,直接“合并”。

PS: 上面已经说了同时## 没有使用background等属性。 ##

2.3 用TextView同时显示图片和文字

这种效果很常见,一般实现方法是这样。(貌似没人这样写吧,哈哈)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="horizontal"
        android:background="@color/white"
        android:layout_width="match_parent"
        android:layout_height="50dp">

        <ImageView
            android:layout_marginLeft="10dp"
            android:layout_width="wrap_content"
            android:src="@drawable/icon_1"
            android:layout_height="match_parent" />

        <TextView
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:textSize="16sp"
            android:text="我的卡券"
            android:gravity="center_vertical"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="match_parent" />

        <ImageView
            android:layout_marginRight="10dp"
            android:src="@drawable/icon_4"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"/>
    </LinearLayout>

</LinearLayout> 

这里可以直接使用textview的drawable的right|left|top|bottom等

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:drawableLeft="@drawable/icon_1"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="我的卡券"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

你没有看错,少了两个ImageView和去除嵌套LinearLayout。效果不用说一样一样的

这里也可以动态设置图片的。setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)可以让我们动态去设置图片。

2.4 使用textview的行间距

想要实现的

我平时如果遇到这种之前是直接用下面的布局实现的

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="100dp"
    android:background="@color/white"
    android:layout_width="match_parent"
    xmlns:tools="http://schemas.android.com/tools">

    <ImageView
        android:padding="25dp"
        android:src="@drawable/kd_1"
        android:layout_width="100dp"
        android:layout_height="100dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="100dp">

        <TextView
            tools:text="揽件方式:上门取件"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="25dp"/>

        <TextView
            tools:text="快递公司:顺丰快递"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="25dp"/>

        <TextView
            tools:text="预约时间:9月6日 立即取件"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="25dp"/>

        <TextView
            tools:text="快递费用:等待称重确定价格"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="25dp"/>

    </LinearLayout>

</LinearLayout

优化后代码:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="100dp"
android:background="@color/white"
android:layout_width="match_parent">

<ImageView
    android:padding="25dp"
    android:src="@drawable/kd_1"
    android:layout_width="100dp"
    android:layout_height="match_parent"/>

<TextView
    android:textSize="14dp"
    android:lineSpacingExtra="8dp"
    android:gravity="center_vertical"
    android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

</LinearLayout>

老规矩,效果一样一样的。可以看到我们仅仅利用Android:lineSpacingExtra=”8dp”这一行
代码就省去了3个TextView,如果行数更多呢?是不是方便多了。

其中:lineSpacingExtra属性代表的是行间距,他默认是0,是一个绝对高度值。同时还有>lineSpacingMultiplier属性,它代表行间距倍数,默认为1.0f,是一个相对高度值。我们来使>用一下:

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="100dp"
    android:background="@color/white"
    android:layout_width="match_parent">

    <ImageView
        android:padding="25dp"
        android:src="@drawable/kd_1"
        android:layout_width="100dp"
        android:layout_height="100dp"/>

    <TextView
        android:textSize="14dp"
        android:lineSpacingMultiplier="1.3"
        android:gravity="center_vertical"
        android:text="揽件方式:上门取件\n快递公司:顺丰快递\n预约时间:9月6日 立即取件\n快递费用:等待称重确定价格"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

当然了这两条属性可以同时使用,查看源码可以知道,他们的高度计算规则为mTextPaint.getFontMetricsInt(null) * 行间距倍数 + 行间距

使用Spannable或Html.fromHtml

如果实现上图红框中的效果,笨办法就是写三个TextView,“¥”,“价格”,“门市价”分别实现,其实用一个TextVIew就可以实现,类似如下代码:

 String text = String.format("¥%1$s  门市价:¥%2$s", 18.6, 22);
 int z = text.lastIndexOf("门");
 SpannableStringBuilder style = new SpannableStringBuilder(text);
 style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号
 style.setSpan(new ForegroundColorSpan(Color.parseColor("#afafaf")), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //颜色
 style.setSpan(new AbsoluteSizeSpan(DisplayUtil.dip2px(mContext,14)), z, text.length(), Spannable.SPAN_EXCLUSIVE_INCLUSIVE); //字号

 tv.setText(style);

用LinearLayout自带的分割线

还记得上文用TextView同时显示图片和文字中的例子吗?我们可以看到每个条目之间都是有一根分隔线的,那么怎么实现呢?别人我不知道,反正我原来是用一个View设置高度实现的。相信一定有人和我一样。

那么老办法我就不演示了,直接上代码:

    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:divider="@drawable/divider"
    android:showDividers="middle">

    <TextView
        android:drawableLeft="@drawable/icon_1"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="我的卡券"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <TextView
        android:drawableLeft="@drawable/icon_2"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="地址管理"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <TextView
        android:drawableLeft="@drawable/icon_3"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="检查更新"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

这里核心实现:

android:divider="@drawable/divider"
android:showDividers="middle"

其中divider.xml是分隔线样式

<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">

    <size android:width="1dp"
          android:height="1dp"/>

    <solid android:color="#e1e1e1"/>

</shape>

showDividers 是分隔线的显示位置,beginning、middle、end分别代表显示在开始位置,中间,末尾。

还有dividerPadding属性这里没有用到,意思很明确给divider添加padding。感兴趣可以试试。

Space控件

还是接着上面的例子,如果要给条目中间添加间距,怎么实现呢?当然也很简单,比如添加一个高10dp的View,或者使用android:layout_marginTop=”10dp”等方法。但是增加View违背了我们的初衷,并且影响性能。使用过多的margin其实会影响代码的可读性。

这时你就可以使用Space,他是一个轻量级的。我们可以看下源码:

public final class Space extends View {
    /**
     * {@inheritDoc}
     */
    public Space(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        if (getVisibility() == VISIBLE) {
            setVisibility(INVISIBLE);
        }
    }

    /**
     * {@inheritDoc}
     */
    public Space(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    /**
     * {@inheritDoc}
     */
    public Space(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * {@inheritDoc}
     */
    public Space(Context context) {
        //noinspection NullableProblems
        this(context, null);
    }

    /**
     * Draw nothing.
     *
     * @param canvas an unused parameter.
     */
    @Override
    public void draw(Canvas canvas) {
    }

    /**
     * Compare to: {@link View#getDefaultSize(int, int)}
     * If mode is AT_MOST, return the child size instead of the parent size
     * (unless it is too big).
     */
    private static int getDefaultSize2(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(size, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
                getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
}

可以看到在draw方法没有绘制任何东西,那么性能也就几乎没有影响。

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:divider="@drawable/divider"
    android:showDividers="middle|beginning|end">

    <TextView
        android:drawableLeft="@drawable/icon_1"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="我的卡券"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <TextView
        android:drawableLeft="@drawable/icon_2"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="地址管理"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <Space
        android:layout_width="match_parent"
        android:layout_height="15dp"/>

    <TextView
        android:drawableLeft="@drawable/icon_3"
        android:drawableRight="@drawable/icon_4"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="检查更新"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

防止过度绘制

http://blog.csdn.net/lmj623565791/article/details/45556391

作者:u012721933 发表于2016/11/8 21:07:48 原文链接
阅读:14 评论:0 查看评论

Flutter基础—应用实例

$
0
0

总结之前学习的概念,编写一个完整的应用实例。假设我们使用一个网络购物的应用程序,它展示出售的各种商品和存放准备购买商品的购物车。首先定义一个展示类ShoppingListItem.dart:

import 'package:flutter/material.dart';
class Product {
  const Product({this.name});
  final String name;
}
typedef void CartChangedCallback(Product product, bool inCart);
class ShoppingListItem extends StatelessWidget {
  ShoppingListItem({Product product, this.inCart, this.onCartChanged})
    : product = product,
      super(key: new ObjectKey(product));
  final Product product;
  final bool inCart;
  final CartChangedCallback onCartChanged;
  Color _getColor(BuildContext context) {
    return inCart ? Colors.black54 : Theme.of(context).primaryColor;
  }
  TextStyle _getTextStyle(BuildContext context) {
    if (!inCart) return null;
    return new TextStyle(
      color: Colors.black54,
      decoration: TextDecoration.lineThrough,
    );
  }
  @override
  Widget build(BuildContext context) {
    return new ListItem(
      onTap: () {
        onCartChanged(product, !inCart);
      },
      leading: new CircleAvatar(
        backgroundColor: _getColor(context),
        child: new Text(product.name[0]),
      ),
      title: new Text(product.name, style: _getTextStyle(context)),
    );
  }
}

ShoppingListItem控件遵循无状态控件的通用模式,将在构造函数中接收到的值存储在final成员变量中,然后在build函数执行时使用。例如布尔值inCart在两个视觉效果之间切换:一个使用当前主题的颜色,另一个使用灰色。

当用户点击列表项时,控件不会直接修改inCart的值,但是控件从父控件接收onCartChanged函数。此模式允许你在控件较高的层次结构中存储状态,这样使状态的持续时间更长。在极端情况下,存储在runApp的控件状态在应用程序的生命周期内保持不变。

当父控件接收到onCartChanged回调,父控件会更新其内部状态,这将触发父控件重建并使用新的inCart值创建ShoppingListItem的新实例。尽管父控件在重建时创建了ShoppingListItem的新实例,但该操作很节省,因为框架会将新构建的控件与之前构建的控件时进行比较,并将有差异的部分应用于底层渲染对象。

下面编写存储可变状态的父控件:

import 'package:flutter/material.dart';
import 'ShoppingListItem.dart';
class ShoppingList extends StatefulWidget {
  ShoppingList({Key key, this.products}) : super(key: key);
  final List<Product> products;
  @override
  _ShoppingListState createState() => new _ShoppingListState();
}
class _ShoppingListState extends State<ShoppingList> {
  Set<Product> _shoppingCart = new Set<Product>();
  void _handleCartChanged(Product product, bool inCart) {
    setState(
      () {
        if (inCart)
          _shoppingCart.add(product);
        else
          _shoppingCart.remove(product);
      }
    );
  }
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('购物清单'),
      ),
      body: new MaterialList(
        type: MaterialListType.oneLineWithAvatar,
        children: config.products.map(
          (Product product) {
            return new ShoppingListItem(
              product: product,
              inCart: _shoppingCart.contains(product),
              onCartChanged: _handleCartChanged,
            );
          }
        ),
      ),
    );
  }
}
final List<Product> _kProducts = <Product>[
  new Product(name: '鸡蛋'),
  new Product(name: '面粉'),
  new Product(name: '巧克力脆片'),
];
void main() {
  runApp(
    new MaterialApp(
      title: 'Flutter教程',
      home: new ShoppingList(products: _kProducts),
    ),
  );
}

类ShoppingList继承了StatefulWidget,这意味着这个控件存储可变状态,当ShoppingList首次插入到树中,框架调用createState函数在树中相关联的位置创建一个新的_ShoppingListState实例(注意:通常命名中带有下划线的State子类,说明它们是私有类)。当该控件的父控件重建时,父控件会创建一个新的ShoppingList实例,但框架将重用已经在树上的_ShoppingListState实例,而不会再次调用createState函数。

要访问当前ShoppingList的属性,_ShoppingListState可以使用其config属性。如果父控件重建并创建一个新的ShoppingList,_ShoppingListState也将使用新的config值重建。如果想要在config属性更改时收到通知,可以覆盖didUpdateConfig函数,它通过oldConfig将旧配置与当前config进行比较。

当处理onCartChanged回调时,_ShoppingListState通过从_shoppingCart中添加或删除一个产品来改变其内部状态。为了向框架通知它改变了内部状态,它在setState中封装这些调用。调用setState将控件标记为dirty,并安排它在应用程序下次需要更新屏幕时重建。如果在修改控件内部状态时忘记调用setState,框架不会知道控件是dirty的,可能不会调用控件的build函数,这意味着用户界面不会更新以反映更改的状态。

通过这种方式管理动态,不再需要单独编写代码来创建和更新子控件。相反,只需要实现构建函数,即可处理这两种情况。

这里写图片描述

作者:hekaiyou 发表于2016/11/8 21:41:34 原文链接
阅读:0 评论:0 查看评论

Android AsyncTask使用步骤与源码分析

$
0
0

AsyncTask的一个典型的应用场景是:后台下载文件,并实时跟新下载进度。它既能使得耗时的事情在后台线程中执行,又能和主线程通信,告诉主线程更新UI。同时,AsyncTask内部使用了线程池来执行后台任务,因此它能处理多任务请求。那么它的内部是怎么实现的呢?

使用步骤

在阅读源码之前,我们还是看一下AsyncTask的使用步骤:
这部分参考了AsyncTask的基本用法 这篇博客。

1.子类化AsyncTask

2.实现AsyncTask中定义的下面一个或几个方法

2.1onPreExecute(), 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
2.2doInBackground(Params…), 将在onPreExecute 方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
2.3onProgressUpdate(Progress…),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
2.4onPostExecute(Result), 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread.

3.提交任务请求

使用executeOnExecutor后者execute方法。
同时,我们还需要注意一下几点:
  A)Task的实例必须在UI thread中创建
  B) execute方法必须在UI thread中调用
  C) 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
  D) 该task只能被执行一次,否则多次调用时将会出现异常

源码分析

AsyncTask的源码不多,因此分析比较容易。我之所以要分析这个类的源码是因为我遇到了这样的困惑:既然AsyncTask使用线程池来处理多任务请求,那么我提交多个任务是不是应该这样呢?

        //1 创建一个AsyncTask 的实例
        AsyncTask asyncTask = new XXAsyncTask()
        //2 提交多个任务
        asyncTask.execute(1);
        asyncTask.execute(2);
        asyncTask.execute(3);
        asyncTask.execute(4);
        asyncTask.execute(5);

很不幸的是,程序立刻就挂掉了,问什么呢?我想的,既然AsyncTask内部有个线程池,那么我不断给它提交任务不就可以了吗?而事实证明这样不行,那么正确的姿势是怎么样的呢?
比如要提交10个任务:

    for(int i=0;i<10;i++){
        //1 创建一个AsyncTask 的实例
        AsyncTask asyncTask = new XXAsyncTask()
        //2 提交多个任务
        asyncTask.execute(i);
    }

也就是说要不断的new AsyncTask 的实例,这是什么情况呢?难道不是每个AsyncTask都有一个线程池吗?还真不是的。
打开源码一看,立刻明白了,AsyncTask使用的是单例模式。

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    /**
     * An {@link Executor} that executes tasks one at a time in serial
     * order.  This serialization is global to a particular process.
     */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

AsyncTask构造了两个静态的线程池:THREAD_POOL_EXECUTOR和SERIAL_EXECUTOR 。因为它们是静态的,因此所有的AsyncTask对象都共享这两个线程池。也就是说,我已开始的理解就是错的,我以为一个AsyncTask内部有一个线程池,其实不然,而是所有的对象共享这两个线程池,其中SERIAL_EXECUTOR 是默认使用的线程池,当然我们可以选择使用THREAD_POOL_EXECUTOR作为线程池的,主要用到executeOnExecutor方法,比如:

asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,1);

理解了这点后,我们看看AsyncTask的具体内部实现。
当我们创建好AsyncTask实例后,采用默认的线程池的情况下,我们会执行excute方法,这个方法如下:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

调用executeOnExecutor方法进一步处理,注意传给executeOnExecutor方法的参数sDefaultExecutor定义如下:

  private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

也就是说默认的线程池是SERIAL_EXECUTOR了。
然后我们看一下executeOnExecutor是如何进一步处理的:

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

该方法中出现的Status定义如下:

    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }

Status中只定义了三种状态,因此,asyncTask的状态如果不是Status.PENDING,就会抛出异常。这是为什么我们向下面这样使用程序会挂掉:

        //1 创建一个AsyncTask 的实例
        AsyncTask asyncTask = new XXAsyncTask()
        //2 提交多个任务
        asyncTask.execute(1);
        asyncTask.execute(2);
        asyncTask.execute(3);

第一次调用execute的时候,asyncTask的状态由Status.PENDING转为Status.RUNNING,第二次调用的是有,asyncTask的状态还是Status.RUNNING,因此程序就会挂掉了。

step 1

接下来会调用onPreExecute()方法,也就是说这个方法是在UI线程中调用的,我们完全可以在其中更新UI。做一个初始化工作,这个时候,我们提交的任务还没有执行。继续往下看。

step 2

接下来调用了:

exec.execute(mFuture);

exec就是excute方法中传入的sDefaultExecutor ,它默认初始化为SERIAL_EXECUTOR,SERIAL_EXECUTOR的定义如下:

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
  private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

exec.excute方法也就是这里的execute方法了。这里构造了一个Runnable的实例并把它添加到mTasks 中,第一次执行到这里mActive 肯定为null,因此会调用scheduleNext方法,这个方法使用 mTasks.poll()去除之前添加的Runable,然后执行THREAD_POOL_EXECUTOR.execute(mActive),从而把Runable添加到线程池中。

step 3

Runnable中调用了传入的Runnable的run方法,这个Runable就是mFuture,mFuture定义如下:

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };

mFuture 是一个FutureTask的实例,并且这个实例接受一个mWork参数,这个mWork是一个和mFuture一样顶一个AsyncTask的构造方法中:

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

mWorker 其实是一个Callable的实例了,他有个call方法。而mFuture本质上是一个Runnable的实例,调用mFuture的run方法其实就是调用FutureTask中的run方法,FutureTask接受了mWorker 作为参数,并把它赋值给自己的callable 属性,代码如下:

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

step 4

所以我们看看FutureTask的run方法

 public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

这个方法做了如下事情:
1.调用callable的call方法,也就是mWork的call方法,mWork的call方法step 3中已经贴过,其中会调用doInBackground方法,也就是我们提交的需要在后台执行的代码了。因此,doInBackground是在mWork的call方法中被执行的,mWork的call方法又是通过THREAD_POOL_EXECUTOR.execute(mActive)被添加到线程池后被执行的。
2.调用postResult方法

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

postResult方法会发送一个消息,这个消息会使得InternalHandler的handleMessage方法被调用。这个时候,我们已经离开了后台线程,又回到UI线程了。因为InternalHandler的Looper是UI线程的Looper,其构造函数中有如下代码可以知晓:

        public InternalHandler() {
            super(Looper.getMainLooper());
        }

注意我们获取的消息为MESSAGE_POST_RESULT,因此根据handleMessage的定义:

        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }

会调用到AsyncTask的finish方法:

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

step 5

这个方法中,我们没有调用取消方法的话,就会调用onPostExecute方法了,这样,我们使用AsyncTask需要覆写的几个方法只有onProgressUpdate方法没有调用了。那么这个方法是怎么被调用的呢?我们说,我们要更新进度的话,需要使用publishProgress方法,我们看看这个方法:

    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

这个方法就是发送一个消息,然后还是InternalHandler方法handleMessage方法被调用,注意这会消息是MESSAGE_POST_PROGRESS,因此根据源码,再贴一次handleMessage方法吧:

        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }

可见这次调用的是onProgressUpdate方法。至此,所有需要覆写的方法的调用过程我们都分析结束了。

作者:u011913612 发表于2016/11/15 19:20:22 原文链接
阅读:63 评论:0 查看评论

创建和发布iOS framework

$
0
0

Update note: This tutorial has been updated to Xcode 8, Cocoapods 1.0, iOS 10 and Swift 3 on Sept 28, 2016.

文章包括framework的创建和发布,及其如何对生成的framework进行单元测试;

CocoaPod的创建和使用,以及如何发布Pod到GitHub。

原文:点击打开链接

Have you ever wanted to share a chunk of code between two or more of your apps, or wanted to share a part of your program with other developers?

Maybe you wanted to modularize your code similarly to how the iOS SDK separates its API by functionality, or perhaps you want to distribute your code in the same way as popular 3rd parties do.

In this iOS frameworks tutorial you’ll learn how to do all of the above!

In iOS 8 and Xcode 6, Apple provided a new template, Cocoa Touch Framework. As you’ll see, it makes creating custom frameworks much easier than before.

Frameworks have three major purposes:

  • Code encapsulation
  • Code modularity
  • Code reuse

You can share your framework with your other apps, team members, or the iOS community. When combined with Swift’s access control, frameworks help define strong, testable interfaces between code modules.

In Swift parlance, a module is a compiled group of code that is distributed together. A framework is one type of module, and an app is another example.

In this iOS frameworks tutorial, you’ll extract a piece of an existing app and set it free, and by doing so, you’ll learn the ins and outs of frameworks by:

  • Creating a new framework for the rings widget
  • Migrating the existing code and tests
  • Importing the whole thing back into the app.
  • Packing it up as an uber-portable CocoaPods
  • Bonus: Setting up a repository for your framework

By the time you’re done, the app will behave exactly as it did before, but it will use the portable framework you developed! :]

Getting Started

Download the Phonercise Starter Project.

Phonercise is a simple application that replicates the Apple Watch Activity app, except it measures your phone’s physical activity. The three rings on the main view represent movement, standing and exercise.

To get the most out of the project, you’ll need to build and run on an actual iOS device, and turn on the volume. Go ahead now, build and run!

ios frameworks tutorial

Move the phone around to get credit for movement, and “exercise” the phone by shaking it vigorously — that’ll get its heartrate up. To get credit for standing up, just hold the phone upright.

The logic for the app is pretty simple:

  • ActionViewController.swift contains the view lifecycle and motion logic.
  • All the view logic is in the files in the Three Ring View folder, where you’ll find ThreeRingView.swift, which handles the view, and Fanfare.swift, which handles the audio. The other files handle the custom drawing of the shading, gradient and shapes.

The ring controls are pretty sweet. They’ve got an addictive quality and they’re easy to understand. Wouldn’t it be nice to use them in a number of applications beyond this fun, but completely silly app? Frameworks to the rescue!

Creating a Framework

Frameworks are self-contained, reusable chunks of code and resources that you can import into any number of apps and even share across iOS, tvOS, watchOS, and macOS apps.

If you’ve programmed in other languages, you may have heard of node modules, packages, gems, jars, etc. Frameworks are the Xcode version of these. Some examples of common frameworks in the iOS SDK are:FoundationUIKitAVFoundationCloudKit, etc.

Framework Set Up

In Xcode 6, Apple introduced the Cocoa Touch Framework template along with access control, so creating frameworks has never been easier. The first thing to do is to create the project for the framework.

  1. Create a new project. In Xcode, go to File/New/Project.
  2. Choose iOS/Framework & Library/Cocoa Touch Framework to create a new framework.

    ios frameworks tutorial

  3. Click Next.
  4. Set the Product Name to ThreeRingControl. Use your own Organization Name and Organization Identifier. Check Include Unit Tests. That’s right! You’re going to have automated tests ensure your framework is bug free.

    ios frameworks tutorial

  5. Click Next.
  6. In the file chooser, choose to create the project at the same level as the Phonercise project.

    ios frameworks tutorial

  7. Click Create.

Now you have a project (albeit a boring one) that creates a framework!

Add Code and Resources

Your current state is a framework without code, and that is about as appealing as straight chocolate without sugar. In this section, you’ll put the pod in CocoaPods by adding the existing files to the framework.

From the Phonercise source directory, drag the following eight files into the ThreeRingControl project in Xcode:

  • CircularGradient.swift
  • coin07.mp3
  • Fanfare.swift
  • RingLayer.swift
  • RingTip.swift
  • ThreeRingView.swift
  • Utilities.swift
  • winning.mp3

ios frameworks tutorial

Make sure to check Copy items if needed, so that the files actually copy into the new project instead of just adding a reference. Frameworks need their own code, not references, to be independent.

Double-check that each of the files has Target Membership in ThreeRingControl to make sure they appear in the final framework. You can see this in the File Inspector for each file.

ios frameworks tutorial

Build the framework project to make sure that you get Build Succeeded with no build warnings or errors.

Add the Framework to the Project

Close the ThreeRingControl project, and go back to the Phonercise project. Delete the six files under theThree Ring View group as well as the two MP3 files in Helper Files. Select Move to Trash in the confirmation dialog.

ios frameworks tutorial

Build the project, and you’ll see several predictable errors where Xcode complains about not knowing what the heck a ThreeRingView is. Well, you’ll actually see messages along the lines of “Use of undeclared type 'ThreeRingView'“, among others.

Adding the Three Ring Control framework project to the workspace is the solution to these problems.

Add the Framework to the Project

Right-click on the root Phonercise node in the project navigator. Click Add Files to “Phonercise”. In the file chooser, navigate to and select ThreeRingControl.xcodeproj. This will add ThreeRingControl.xcodeproj as a sub-project.

Note: It isn’t strictly necessary to add the framework project to the app project; you could just add theThreeRingControl.framework output.

However, combining the projects makes it easier to develop both the framework and app simultaneously. Any changes you make to the framework project are automatically propagated up to the app. It also makes it easier for Xcode to resolve the paths and know when to rebuild the project.

Even though the two projects are now together in the workspace, Phonercise still doesn’t getThreeRingControl. It’s like they’re sitting in the same room, but Phonercise can’t see the new framework.

Try linking the framework to the app’s target to fix this problem. First, expand the ThreeRingControl project to see the Products folder, and then look for for ThreeRingControl.framework beneath it. This file is the output of the framework project that packages up the binary code, headers, resources and metadata.

Select the top level Phonercise node to open the project editor. Click the Phonercise target, and then go to theGeneral tab.

Scroll down to the Embedded Binaries section. Drag ThreeRingControl.framework from the Products folder ofThreeRingControl.xcodeproj onto this section.

You just added an entry for the framework in both Embedded Binaries and Linked Frameworks and Binaries.

ios frameworks tutorial

Now the app knows about the framework and where to find it, so that should be enough, right?

Build the Phonercise project. More of the same errors.
ios frameworks tutorial

Access Control

Your problem is that although the framework is part of the project, the project’s code doesn’t know about it — out of sight, out of mind.

Go to ActionViewController.swift, and add the following line to the list of imports at the top of the file.

import ThreeRingControl

It’s critical, but this inclusion won’t fix the build errors. This is because Swift uses access control to let you determine whether constructs are visible to other files or modules.

By default, Swift makes everything internal or visible only within its own module.

To restore functionality to the app, you have to update the access control on two Phonercise classes.

Although it’s a bit tedious, the process of updating access control improves modularity by hiding code not meant to appear outside the framework. You do this by leaving certain functions with no access modifier, or by explicitly declaring them internal.

Swift has three levels of access control. Use the following rules of thumb when creating your own frameworks:

  • Public: for code called by the app or other frameworks, e.g., a custom view.
  • Internal: for code used between functions and classes within the framework, e.g., custom layers in that view.
  • Fileprivate: for code used within a single file, e.g., a helper function that computes layout heights.
  • Private: for code used within an enclosing declaration, such as a single class block. Private code will not be visible to other blocks, such as extensions of that class, even in the same file, e.g., private variables, setters, or helper sub-functions.

Ultimately, making frameworks is so much easier than in the past, thanks to the new Cocoa Touch Framework template. Apple provided both of these in Xcode 6 and iOS 8.

Update the Code

When ThreeRingView.swift was part of the Phonercise app, internal access wasn’t a problem. Now that it’s in a separate module, it must be made public for the app to use it. The same is true of the code in Fanfare.swift.

Open ThreeRingView.swift inside of ThreeRingControlProject.

Make the class public by adding the public keyword to the class definition, like so:

public class ThreeRingView : UIView {

ThreeRingView will now be visible to any app file that imports the ThreeRingControl framework.

Add the public keyword to:

  • Both init functions
  • The variables RingCompletedNotificationAllRingsCompletedNotificationlayoutSubviews()
  • Everything marked @IBInspectable — there will be nine of these

Note: You might wonder why you have to declare inits as public. Apple explains this and other finer points of access control in their Access Control Documentation.

The next step is to do essentially the same thing as you did for ThreeRingView.swift, and add the publickeyword to the appropriate parts of Fanfare.swift. For your convenience, this is already done.

Note that the following variables are public: ringSoundallRingSound, and sharedInstance. The functionplaySoundsWhenReady() is public as well.

Now build and run. The good news is that the errors are gone, and the bad news is that you’ve got a big, white square. Not a ring in sight. Oh no! What’s going on?

ios frameworks tutorial

Update the Storyboard

When using storyboards, references to custom classes need to have both the class name and module set in theIdentity Inspector. At the time of this storyboard’s creation, ThreeRingView was in the app’s module, but now it’s in the framework.

Update the storyboard, as explained below, by telling it where to find the custom view; this will get rid of the white square.

  1. Open Main.Storyboard in the Phonercise project.
  2. Select the Ring Control in the view hierarchy.
  3. In the Identity Inspector, under Custom Class, change the Module to ThreeRingControl.

Once you set the module, Interface Builder should update and show the control in the editor area.

ios frameworks tutorial

Build and run. Now you’ll get some rings.

ios frameworks tutorial

Update the Tests

There’s one final place to update before you can actually use the framework. It’s the oft-forgotten test suite. :]

This app includes unit tests. However, the tests have a dependency on ThreeRingControl, so you need to fix that.

Note: Before Swift 2.0, unit testing was difficult, but Swift 2.0 came along with @testable, making testing far easier. It means you no longer have to make all your project’s items public just to be able to test them.

Simply import the module you want to test using the @testable keyword, and your test target will have access to all internal routines in the imported module.

The first test is ThreeRingTest.swift, which tests functionality within ThreeRingView.swift and should be part of the ThreeRingControl framework.

From the PhonerciseTests group in the app project, drag ThreeRingTest.swift to the ThreeRingControlTestsgroup in the framework project.

Select Copy items if needed, and make sure you select the ThreeRingControlTests target and not the framework target.

ios frameworks tutorial

Then delete the ThreeRingTest.swift from the app project. Move it to the trash.

Open the copied test file, and change the import line from:

@testable import Phonercise

To:

import ThreeRingControl

Click the run test button in the editor’s gutter, and you’ll see a green checkmark showing that the test succeeded.

ios frameworks tutorial

This test doesn’t need @testable in its import since it is only calling public methods and variables.

The remaining app test doesn’t work this way though. Go back the to the Phonercise target and openPhonerciseTests.swift.

Add the following import to the existing imports at the top:

import ThreeRingControl

Now run the PhonerciseTests tests. The particular test should execute and pass.

Congratulations. You now have a working stand-alone framework and an app that uses it!

ios frameworks tutorial

Creating a CocoaPod

CocoaPods is a popular dependency manager for iOS projects. It’s a tool for managing and versioning dependencies. Similar to a framework, a CocoaPod, or ‘pod’ for short, contains code, resources, etc., as well as metadata, dependencies, and set up for libraries. In fact, CocoaPods are built as frameworks that are included in the main app.

Anyone can contribute libraries and frameworks to the public repository, which is open to other iOS app developers. Almost all of the popular third-party frameworks, such as Alamofire, React native and SDWebImage, distribute their code as a pod.

Here’s why you should care: by making a framework into a pod, you give yourself a mechanism for distributing the code, resolving the dependencies, including and building the framework source, and easily sharing it with your organization or the wider development community.

Clean out the Project

Perform the following steps to remove the current link to ThreeRingControl.

  1. Select ThreeRingControl.xcodeproj in the project navigator and delete it.
  2. Choose Remove Reference in the confirmation dialog, since you’ll need to keep the files on disk to create the pod.

Install CocoaPods

If you’ve never used CocoaPods before, you’ll need to follow a brief installation process before going any further. Go to the CocoaPods Installation Guide and come back here when you’re finished. Don’t worry, we’ll wait!

Create the Pod

Open Terminal in the ThreeRingControl directory.

Run the following command:

pod spec create ThreeRingControl

This creates the file ThreeRingControl.podspec in the current directory. It’s a template that describes the pod and how to build it. Open it in a text editor.

The template contains plenty of comment descriptions and suggestions for the commonly used settings.

  1. Replace the Spec Metadata section with:
    s.name = "ThreeRingControl"
    s.version = "1.0.0"
    s.summary = "A three-ring control like the Activity status bars"
    s.description = "The three-ring is a completely customizable widget that can be used in any iOS app. It also plays a little victory fanfare."
    s.homepage = "http://raywenderlich.com"

    Normally, the description would be a little more descriptive, and the homepage would point to a project page for the framework.

  2. Replace the Spec License section with the below code, as this iOS frameworks tutorial code uses an MIT License:
    s.license = "MIT"
  3. You can keep the Author Metadata section as is, or set it u with how you’d lke to be credited and contacted.
  4. Replace the Platform Specifics section with the below code, because this is an iOS-only framework.:
    s.platform = :ios, "10.0"
  5. Replace the Source Location with the below code. When you’re ready to share the pod, this will be a link to the GitHub repository and the commit tag for this version.
    s.source = { :path => '.' }
  6. Replace the Source Code section with:
    s.source_files = "ThreeRingControl", "ThreeRingControl/**/*.{h,m,swift}"
  7. And the Resources section with:
    s.resources = "ThreeRingControl/*.mp3"

    This will include the audio resources into the pod’s framework bundle.

  8. Remove the Project Linking and Project Settings sections.
  9. Add the following line. This line helps the application project understand that this pod’s code was written for Swift 3.
    s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3' }
  10. Remove all the remaining comments — the lines that start with #.

You now have a workable development Podspec.

Note: If you run pod spec lint to verify the Podspec in Terminal, it’ll show an error because the source was not set to a valid URL. If you push the project to GitHub and fix that link, it will pass. However, having the linter pass is not necessary for local pod development. The Publish the Pod section below covers this.

Use the Pod

At this point, you’ve got a pod ready to rock and roll. Test it out by implementing it in the Phonercise app.

Back in Terminal, navigate up to the Phonercise directory, and then run the following command:

pod init

This steathily creates a new file named Podfile that lists all pods that the app uses, along with their versions and optional configuration information.

Open Podfile in a text editor.

Next, add the following line under the comment, # Pods for Phonercise:

pod 'ThreeRingControl', :path => '../ThreeRingControl'

Save the file.

Run this in Terminal:

pod install

With this command, you’re searching the CocoaPods repository and downloading any new or updated pods that match the Podfile criteria. It also resolves any dependencies, updates the Xcode project files so it knows how to build and link the pods, and performs any other required configuration.

Finally, it creates a Phonercise.xcworkspace file. Use this file from now on — instead of the xcodeproj — because it has the reference to the Pods project and the app project.

Check it Out

Close the Phonercise and ThreeRingControl projects if they are open, and then openPhonercise.xcworkspace.

Build and run. Like magic, the app should work exactly the same before. This ease of use is brought to you by these two facts:

  1. You already did the work to separate the ThreeRingControl files and use them as a framework, e.g. adding import statements.
  2. CocoaPods does the heavy lifting of building and packaging those files; it also takes care of all the business around embedding and linking.

Pod Organization

Take a look at the Pods project, and you’ll notice two targets:

ios frameworks tutorial

  • Pods-phonercise: a pod project builds all the individual pods as their own framework, and then combines them into one single framework: Pods-Phonercise.
  • ThreeRingControl: this replicates the same framework logic used for building it on its own. It even adds the music files as framework resources.

Inside the project organizer, you’ll see several groups. ThreeRingControl is under Development Pods. This is adevelopment pod because you defined the pod with :path link in the app’s Podfile. These files are symlinked, so you can edit and develop this code side-by-side with the main app code.

ios frameworks tutorial

Pods that come from a repository, such as those from a third party, are copied into the Pods directory and listed in a Pods group. Any modifications you make are not pushed to the repository and are overwritten whenever you update the pods.

Congratulations! You’ve now created and deployed a CocoaPod — and you’re probably thinking about what to pack into a pod first.

You’re welcome to stop here, congratulate yourself and move on to Where to Go From Here, but if you do, you’ll miss out on learning an advanced maneuver.

Publish the Pod

This section walks you through the natural next step of publishing the pod to GitHub and using it like a third party framework.

Create a Repository

If you don’t already have a GitHub account, create one.

Now create a new repository to host the pod. ThreeRingControl is the obvious best fit for the name, but you can name it whatever you want. Be sure to select Swift as the .gitignore language and MIT as the license.

ios frameworks tutorial

Click Create Repository. From the dashboard page that follows, copy the HTTPS link.

Clone the Repository

Go back to Terminal and create a new directory. The following commands will create a repo directory from the project’s folder.

mkdir repo
cd repo

From there, clone the GitHub repository. Replace the URL below with the HTTPS link from the GitHub page.

git clone URL

This will set up a Git folder and go on to copy the pre-created README and LICENSE files.

ios frameworks tutorial

Add the Code to the Repository

Next, copy the files from the previous ThreeRingControl directory to the repo/ThreeRingControl directory.

Open the copied version of ThreeRingControl.podspec, and update the s.source line to:

s.source = { :git => "URL", :tag => "1.0.0" }

Set the URL to be the link to your repository.

Make the Commitment

Now it gets real. In this step, you’ll commit and push the code to GitHub. Your little pod is about to enter the big kid’s pool.

Run the following commands in Terminal to commit those files to the repository and push them back to the server.

cd ThreeRingControl/
git add .
git commit -m "initial commit"
git push

Visit the GitHub page and refresh it to see all the files.

ios frameworks tutorial

Tag It

You need to tag the repository so it matches the Podspec. Run this command to set the tags:

git tag 1.0.0git push --tags

Check your handiwork by running:

pod spec lint

The response you’re looking for is ThreeRingControl.podspec passed validation.

Update the Podfile

Change the Podfile in the Phonercise directory. Replace the existing ThreeRingControl line with:

pod 'ThreeRingControl', :git => 'URL', :tag => '1.0.0'

Replace URL with your GitHub link.

From Terminal, run this in the Phonercise directory:

pod update

Now the code will pull the framework from the GitHub repository, and it is no longer be a development pod!

ios frameworks tutorial

Where to Go From Here?

In this iOS frameworks tutorial, you’ve made a framework from scratch that contains code and resources, you’ve reimported that framework back into your app, and even turned it into a CocoaPod. Nice work!

You can download the final project <a href="https://koenig-media.raywenderlich.com/uploads/2016/06/Phonercise-Final.zip" "="" style="border: 0px; font-family: inherit; font-style: inherit; font-weight: inherit; margin: 0px; outline: 0px; padding: 0px; vertical-align: baseline; color: rgb(0, 104, 55);">here. It doesn’t include the “advanced maneuver” steps, as the steps to set up a GitHub repository depends on your personal account information.

Hats off to Sam Davies for developing the ThreeRingControl. You might remember him from such videos asIntroducing Custom Controls, where you can learn more about custom controls and custom frameworks.

The team here has put together a lot of tutorials about CocoaPods, and now that you’ve gone through a crash course, you’re ready to learn more. Here are a couple that you might like:

Spend some time at CocoaPods.org and be sure to check out how to submit to the public pod repository and the dozens of configuration flags.

What did you learn from this? Any lingering questions? Want to share something that happened along the way? Let’s talk about it in the forums. See you there!

作者:Enter_ 发表于2016/11/15 19:34:12 原文链接
阅读:43 评论:0 查看评论

Android 7.0 ActivityManagerService(1) AMS的启动过程

$
0
0

一、概况
ActivityManagerService(AMS)是Android中最核心的服务,主要负责系统中四大组件的启动、切换、调度及应用程序的管理和调度等工作。

AMS通信结构如下图所示:

从图中可以看出:
1、AMS继承自ActivityManagerNative(AMN),并实现了Watchdog.Monitor和BatteryStatsImpl.BatteryCallback接口。

2、AMN继承Java的Binder类,同时实现了IActivityManager接口,即AMN将作为Binder通信的服务端为用户提供支持。

3、在ActivityManagerNative类中定义了内部类ActivityManagerProxy,该类同样实现了IActivityManager接口,将作为客户端使用的服务端代理。

4、其它进程将使用ActivityManager来使用AMS的服务。ActivityManager通过AMN提供的getDefault接口得到ActivityManagerProxy,然后再以Binder通信的方式调用AMS的接口。

对AMS的基本情况有一个大概的了解后,我们一起来分析一下AMS的启动过程。
由于AMS启动涉及的内容比较多,我们将分段进行分析。

二、createSystemContext
在进入到AMS相关的流程前,我们需要先了解一下相关的准备工作。

我们已经知道了,zygote创建出的第一个java进程是SystemServer。
在SystemServer的run函数中,在启动AMS之前,调用了createSystemContext函数。

其代码如下所示:

.............
//SystemServer在启动任何服务之前,就调用了createSystemContext
//创建出的Context保存在mSystemContext中
// Initialize the system context.
createSystemContext();

// Create the system service manager.
//SystemServiceManager负责启动所有的系统服务,使用的Context就是mSystemContext
mSystemServiceManager = new SystemServiceManager(mSystemContext);
.............

我们跟进一下createSystemContext:

private void createSystemContext() {
    //调用ActivityThread的systemMain函数,其中会创建出系统对应的Context对象
    ActivityThread activityThread = ActivityThread.systemMain();

    //取出上面函数创建的Context对象,保存在mSystemContext中
    mSystemContext = activityThread.getSystemContext();

    //设置系统主题
    mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);
}

以上函数中,最重要的就是ActivityThread.systemMain了,我们分析一下该函数。

1、ActivityThread.systemMain

public static ActivityThread systemMain() {
    // The system process on low-memory devices do not get to use hardware
    // accelerated drawing, since this can add too much overhead to the
    // process.
    if (!ActivityManager.isHighEndGfx()) {
        //虽然写着ActivityManager,但和AMS没有任何关系
        //就是利用系统属性和配置信息进行判断

        //关闭硬件渲染功能
        ThreadedRenderer.disable(true);
    } else {
        ThreadedRenderer.enableForegroundTrimming();
    }

    //创建ActivityThread
    ActivityThread thread = new ActivityThread();
    //调用attach函数,参数为true
    thread.attach(true);
    return thread;
}

从上面的代码可以看出,ActivityThread的systemMain函数中,除了进行是否开启硬件渲染的判断外,主要作用是:
创建出ActivityThread对象,然后调用该对象的attach函数。

ActivityThread的构造函数比较简单:

ActivityThread() {
    mResourcesManager = ResourcesManager.getInstance();
}

比较关键的是它的成员变量:

..........
//定义了AMS与应用通信的接口
final ApplicationThread mAppThread = new ApplicationThread();

//拥有自己的looper,说明ActivityThread确实可以代表事件处理线程
final Looper mLooper = Looper.myLooper();

//H继承Handler,ActivityThread中大量事件处理依赖此Handler
final H mH = new H();

//用于保存该进程的ActivityRecord
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>()
..........
//用于保存进程中的Service
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
...........
//用于保存进程中的Application
final ArrayList<Application> mAllApplications = new ArrayList<Application>();
...........

我们需要知道的是,ActivityThread是Android Framework中一个非常重要的类,它代表一个应用进程的主线程,其职责就是调度及执行在该线程中运行的四大组件。
在Android中,应用进程指那些运行APK的进程,它们由zygote fork出来,其中运行着独立的dalvik虚拟机。
与应用进程相对的就是系统进程,例如zygote和SystemServer。

注意到此处的ActivityThread创建于SystemServer进程中。
由于SystemServer中也运行着一些系统APK,例如framework-res.apk、SettingsProvider.apk等,因此也可以认为SystemServer是一个特殊的应用进程。

对于上面提到的ActivityThread的成员变量,其用途基本上可以从名称中得知,这里仅说明一下ApplicationThread。

AMS负责管理和调度进程,因此AMS需要通过Binder机制和应用进程通信。
为此,Android提供了一个IApplicationThread接口,该接口定义了AMS和应用进程之间的交互函数。

如上图所示,ActivityThread作为应用进程的主线程代表,在其中持有ApplicationThread。ApplicationThread继承ApplicationThreadNative。
当AMS与应用进程通信时,ApplicationThread将作为Binder通信的服务端。

AMS与应用进程通信时,通过ApplicationThreadNative获取应用进程对应的ApplicationThreadProxy对象。
通过ApplicationThreadProxy对象,将调用信息通过Binder传递到ActivityThread中的ApplicationThread。
这个调用过程,今后还会遇到,碰到的时候再详细分析。

2、ActivityThread.attach
我们看看ActivityThread的attach函数:

//此时,我们传入的参数为true,表示该ActivityThread是系统进程的ActivityThread
private void attach(boolean system) {
    //创建出的ActivityThread保存在类的静态变量sCurrentActivityThread
    //AMS中的大量操作将会依赖于这个ActivityThread
    sCurrentActivityThread = this;
    mSystemThread = system;

    if (!system) {
        //应用进程的处理流程
        ..........
    } else { 
        //系统进程的处理流程,该情况只在SystemServer中处理

        // Don't set application object here -- if the system crashes,
        // we can't display an alert, we just want to die die die.
        //设置DDMS(Dalvik Debug Monitor Service)中看到的SystemServer进程的名称为“system_process”
        android.ddm.DdmHandleAppName.setAppName("system_process",
                UserHandle.myUserId());

        try {
            //创建ActivityThread中的重要成员:Instrumentation、Application和Context
            mInstrumentation = new Instrumentation();
            ContextImpl context = ContextImpl.createAppContext(
                        this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        }
    }

    //以下系统进程和非系统进程均会执行
    ................
    //注册Configuration变化的回调通知
    ViewRootImpl.addConfigCallback(new ComponentCallbacks2() {
        @Override
        public void onConfigurationChanged(Configuration newConfig) {
            //当系统配置发生变化时(例如系统语言发生变化),回调该接口
            ...............
        }
        .............
    });
}

从上面的代码可以看出,对于系统进程而言,ActivityThread的attach函数最重要的工作就是创建了Instrumentation、Application和Context。

2.1 Instrumentation
Instrumentation是Android中的一个工具类,当该类被启用时,它将优先于应用中其它的类被初始化。
此时,系统先创建它,再通过它创建其它组件。

此外,系统和应用组件之间的交互也将通过Instrumentation来传递。
因此,Instrumentation就能监控系统和组件的交互情况了。

实际使用时,可以创建该类的派生类进行相应的操作。
这个类在介绍启动Activity的过程时还会碰到,此处不作展开。

2.2 Context
Context是Android中的一个抽象类,用于维护应用运行环境的全局信息。
通过Context可以访问应用的资源和类,甚至进行系统级的操作,例如启动Activity、发送广播等。

ActivityThread的attach函数中,通过下面的代码创建出系统应用对应的Context:

.......
//ContextImpl是Context的实现类
ContextImpl context = ContextImpl.createAppContext(
        this, getSystemContext().mPackageInfo);
.......

2.2.1 getSystemContext
我们先看看ActivityThread中getSystemContext的内容:

public ContextImpl getSystemContext() {
    synchronized (this) {
        if (mSystemContext == null) {
            //调用ContextImpl的静态函数createSystemContext
            mSystemContext = ContextImpl.createSystemContext(this);
        }
        return mSystemContext;
    }
}

进入ContextImpl的createSystemContext函数:

static ContextImpl createSystemContext(ActivityThread mainThread) {
    //创建LoadedApk类,代表一个加载到系统中的APK
    //注意此时的LoadedApk只是一个空壳
    //PKMS还没有启动,估无法得到有效的ApplicationInfo
    LoadedApk packageInfo = new LoadedApk(mainThread);

    //调用ContextImpl的构造函数
    ContextImpl context = new ContextImpl(null, mainThread,
            packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);

    //初始化资源信息
    context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
            context.mResourcesManager.getDisplayMetrics());
    return context;
}

可以看出createSystemContext的内容就是创建一个LoadedApk,然后初始化一个ContextImpl对象。
似乎没有什么特别的,那么为什么函数名被叫做create “System” Context?

为了回答这个问题,就要看看LoadedApk的构造函数了:

LoadedApk(ActivityThread activityThread) {
    mActivityThread = activityThread;
    mApplicationInfo = new ApplicationInfo();

    //packageName为"android"
    mApplicationInfo.packageName = "android";
    mPackageName = "android";
    //下面许多参数为null
    ................
}

注意到createSystemContext函数中,创建的LoadApk对应packageName为”android”,也就是framwork-res.apk。
由于该APK仅供SystemServer进程使用,因此创建的Context被定义为System Context。
现在该LoadedApk还没有得到framwork-res.apk实际的信息。

当PKMS启动,完成对应的解析后,AMS将重新设置这个LoadedApk。

2.2.2 ContextImpl.createAppContext

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    return new ContextImpl(null, mainThread,
            packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
}

相对而言,createAppContext的内容就比较简单了,就是利用ActivityThread和LoadedApk构造出ContextImpl。
ContextImpl的构造函数主要是完成一些变量的初始化,建立起ContextImpl与ActivityThread、LoadedApk、ContentResolver之间的关系。
代码简单但是冗长,此处不做展开。

2.3 Application
Android中Application类用于保存应用的全局状态。

我们经常使用的Activity和Service均必须和具体的Application绑定在一起。
通过上图的继承关系,每个具体的Activity和Service均被加入到Android运行环境中。

在ActivityThread中,针对系统进程,通过下面的代码创建了初始的Application:

..............
//调用LoadedApk的makeApplication函数
mInitialApplication = context.mPackageInfo.makeApplication(true, null);

//启动Application
mInitialApplication.onCreate();
.............. 

我们看一下LoadedApk.makeApplication:

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }
    .............
    Application app = null;

    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        //系统进程中,对应下面的appClass
        appClass = "android.app.Application";
    }

    try {
        java.lang.ClassLoader cl = getClassLoader();
        if (!mPackageName.equals("android")) {
            ............
        }

        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        //实际上最后通过反射创建出Application
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
        appContext.setOuterContext(app);
    } catch (Exception e) {
        ..........
    }

    //一个进程支持多个Application,mAllApplications用于保存该进程中的Application对象
    mActivityThread.mAllApplications.add(app);
    mApplication = app;

    ..............
}

从上面的代码不难看出,这部分主要是创建framework-res.apk对应的Application,然后调用它的onCreate函数,完成启动。

总结
至此,createSystemContext函数介绍完毕。

当SystemServer调用createSystemContext完毕后:
1、得到了一个ActivityThread对象,它代表当前进程 (此时为系统进程) 的主线程;
2、得到了一个Context对象,对于SystemServer而言,它包含的Application运行环境与framework-res.apk有关。

在继续分析AMS之前,我们先停下来思考一下,为什么在启动所有的服务前,SystemServer先要调用createSystemContext?

个人觉得《深入理解Android》对这个问题,解释的比较好,大致意思如下:
Android努力构筑了一个自己的运行环境。
在这个环境中,进程的概念被模糊化了。组件的运行及它们之间的交互均在该环境中实现。

createSystemContext函数就是为SystemServer进程搭建一个和应用进程一样的Android运行环境。

Android运行环境是构建在进程之上的,应用程序一般只和Android运行环境交互。
基于同样的道理,SystemServer进程希望它内部运行的应用,
也通过Android运行环境交互,因此才调用了createSystemContext函数。

创建Android运行环境时,
由于SystemServer的特殊性,调用了ActivityThread.systemMain函数;
对于普通的应用程序,将在自己的主线程中调用ActivityThread.main函数。

上图表示了进程的Android运行环境涉及的主要类之间的关系。
其中的核心类是ContextImpl,通过它可以得到ContentResolver、系统资源、应用信息等。

三、AMS初始化
创建完Android运行环境后,SystemServer调用startBootstrapServices,其中就创建并启动了AMS:

private void startBootstrapServices() {
    Installer installer = mSystemServiceManager.startService(Installer.class);

    // Activity manager runs the show.
    //启动AMS,然后获取AMS保存到变量中
    mActivityManagerService = mSystemServiceManager.startService(
            ActivityManagerService.Lifecycle.class).getService();

    //以下均是将变量存储到AMS中
    mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
    mActivityManagerService.setInstaller(installer);
    ..........
}

注意到上面的代码并没有直接启动AMS,而是启动AMS的内部类Lifecycle。
这是迫不得已的做法,由于AMS并没有继承SystemService,因此不能通过SystemServiceManager的startService直接启动它。
可以这样理解:内部类Lifecycle对于AMS而言,就像一个适配器一样,让AMS能够像SystemService一样被SystemServiceManager通过反射的方式启动。

public static final class Lifecycle extends SystemService {
    private final ActivityManagerService mService;

    public Lifecycle(Context context) {
        //Lifecycle由SystemServiceManager启动,传入的context就是SystemServer创建出的SystemContext
        super(context);

        //1、调用AMS的构造函数
        mService = new ActivityManagerService(context);
     }

    @Override
    public void onStart() {
        //2、调用AMS的start函数
        mService.start();
    }

    public ActivityManagerService getService() {
        return mService;
    }
}

接下来我们分别看看AMS的构造函数和start函数。

1、AMS的构造函数
先来看看AMS的构造函数:

public ActivityManagerService(Context systemContext) {
    //AMS的运行上下文与SystemServer一致
    mContext = systemContext;
    ............
    //取出的是ActivityThread的静态变量sCurrentActivityThread
    //这意味着mSystemThread与SystemServer中的ActivityThread一致
    mSystemThread = ActivityThread.currentActivityThread();
    ............
    mHandlerThread = new ServiceThread(TAG,
            android.os.Process.THREAD_PRIORITY_FOREGROUND, false /*allowIo*/);
    mHandlerThread.start();
    //处理AMS中消息的主力
    mHandler = new MainHandler(mHandlerThread.getLooper());

    //UiHandler对应于Android中的UiThread
    mUiHandler = new UiHandler();

    if (sKillHandler == null) {
        sKillThread = new ServiceThread(TAG + ":kill",
                android.os.Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
        sKillThread.start();
        //用于接收消息,杀死进程
        sKillHandler = new KillHandler(sKillThread.getLooper());
    }

    //创建两个BroadcastQueue,前台的超时时间为10s,后台的超时时间为60s
    mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "foreground", BROADCAST_FG_TIMEOUT, false);
    mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
            "background", BROADCAST_BG_TIMEOUT, true);
    mBroadcastQueues[0] = mFgBroadcastQueue;
    mBroadcastQueues[1] = mBgBroadcastQueue;

    //创建变量,用于存储信息
    mServices = new ActiveServices(this);
    mProviderMap = new ProviderMap(this);
    mAppErrors = new AppErrors(mContext, this);

    //这一部分,分析BatteryStatsService时提过,进行BSS的初始化
    File dataDir = Environment.getDataDirectory();
    File systemDir = new File(dataDir, "system");
    systemDir.mkdirs();
    mBatteryStatsService = new BatteryStatsService(systemDir, mHandler);
    mBatteryStatsService.getActiveStatistics().readLocked();
    mBatteryStatsService.scheduleWriteToDisk();
    mOnBattery = DEBUG_POWER ? true
            : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
    mBatteryStatsService.getActiveStatistics().setCallback(this);

    //创建ProcessStatsService,感觉用于记录进程运行时的统计信息,例如内存使用情况,写入/proc/stat文件
    mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));

    //启动Android的权限检查服务,并注册对应的回调接口
    mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
    mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_IN_BACKGROUND, null,
            new IAppOpsCallback.Stub() {
                @Override public void opChanged(int op, int uid, String packageName) {
                    if (op == AppOpsManager.OP_RUN_IN_BACKGROUND && packageName != null) {
                        if (mAppOpsService.checkOperation(op, uid, packageName)
                                != AppOpsManager.MODE_ALLOWED) {
                            runInBackgroundDisabled(uid);
                        }
                    }
                }
            });

    //用于定义ContentProvider访问指定Uri对应数据的权限,aosp中似乎没有这文件
    mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));

    //创建多用户管理器
    mUserController = new UserController(this);

    //获取OpenGL版本
    GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
            ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
    ............
    //资源配置信息置为默认值
    mConfiguration.setToDefaults();
    mConfiguration.setLocales(LocaleList.getDefault());
    mConfigurationSeq = mConfiguration.seq = 1;

    //感觉用于记录进程的CPU使用情况
    mProcessCpuTracker.init();

    //解析/data/system/packages-compat.xml文件,该文件用于存储那些需要考虑屏幕尺寸的APK的一些信息
    //当APK所运行的设备不满足要求时,AMS会根据xml设置的参数以采用屏幕兼容的方式运行该APK
    mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);

    //用于根据规则过滤一些Intent
    mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);

    //以下的类,似乎用于管理和监控AMS维护的Activity Task信息
    //ActivityStackSupervisor是AMS中用来管理Activity启动和调度的核心类
    mStackSupervisor = new ActivityStackSupervisor(this);
    mActivityStarter = new ActivityStarter(this, mStackSupervisor);
    mRecentTasks = new RecentTasks(this, mStackSupervisor);

    //创建线程用于统计进程的CPU使用情况
    mProcessCpuThread = new Thread("CpuTracker") {
        @Override
        public void run() {
            while (true) {
                try {
                    try {
                        //计算更新信息的等待间隔
                        //同时利用wait等待计算出的间隔时间
                        ......
                    } catch(InterruptedException e) {
                    }
                    //更新CPU运行统计信息
                    updateCpuStatsNow();
                } catch (Exception e) {
                    ..........
                }
            }
        }
    };

    //加入Watchdog的监控
    Watchdog.getInstance().addMonitor(this);
    Watchdog.getInstance().addThread(mHandler);
}

从代码来看,AMS的构造函数还是相对比较简单的,主要工作就是初始化一些变量。
大多数变量的用途,从命名上基本可以推测出来,实际的使用情况必须结合具体的场景才能进一步了解。

2、AMS的start函数

private void start() {
    //完成统计前的复位工作
    Process.removeAllProcessGroups();

    //开始监控进程的CPU使用情况
    mProcessCpuThread.start();

    //注册服务
    mBatteryStatsService.publish(mContext);
    mAppOpsService.publish(mContext);
    Slog.d("AppOps", "AppOpsService published");
    LocalServices.addService(ActivityManagerInternal.class, new LocalService());
}

AMS的start函数比较简单,主要是:
1、启动CPU监控线程。该线程将会开始统计不同进程使用CPU的情况。
2、发布一些服务,如BatteryStatsService、AppOpsService(权限管理相关)和本地实现的继承ActivityManagerInternal的服务。

至此AMS初始化相关的内容基本结束,从这些代码可以看出AMS涉及的类比较多,我们目前无法一一详述每个类的具体用途。
有机会遇到具体的场景时,再深入分析,此处有个大致印象即可。

四、将SystemServer纳入AMS的管理体系
1、setSystemProcess

AMS完成启动后,在SystemServer的startBootstrapServices函数中,
下一个与AMS相关的重要调用就是AMS.setSystemProcess了:

private void startBootstrapServices() {
    ...........
    // Set up the Application instance for the system process and get started.
    mActivityManagerService.setSystemProcess();
    ...........
}

我们跟进一下setSystemProcess函数:

public void setSystemProcess() {
    try {
        //以下是向ServiceManager注册几个服务

        //AMS自己
        ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);

        //注册进程统计信息的服务
        ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);

        //用于打印内存信息用的
        ServiceManager.addService("meminfo", new MemBinder(this));

        //用于输出进程使用硬件渲染方面的信息
        ServiceManager.addService("gfxinfo", new GraphicsBinder(this));

        //用于输出数据库相关的信息
        ServiceManager.addService("dbinfo", new DbBinder(this));

        //MONITOR_CPU_USAGE默认为true
        if (MONITOR_CPU_USAGE) {
            //用于输出进程的CPU使用情况
            ServiceManager.addService("cpuinfo", new CpuBinder(this));
        }

        //注册权限管理服务
        ServiceManager.addService("permission", new PermissionController(this));

        //注册获取进程信息的服务
        ServiceManager.addService("processinfo", new ProcessInfoService(this));

        //1、向PKMS查询package名为“android”的应用的ApplicationInfo
        ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
                "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);

        //2、调用installSystemApplicationInfo
        mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());

        //3、以下与AMS的进程管理有关
        synchronized (this) {
            ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);
            app.persistent = true;
            app.pid = MY_PID;
            app.maxAdj = ProcessList.SYSTEM_ADJ;
            app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
            synchronized (mPidsSelfLocked) {
                mPidsSelfLocked.put(app.pid, app);
            }
            updateLruProcessLocked(app, false, null);
            updateOomAdjLocked();
        }
    } catch (PackageManager.NameNotFoundException e) {
        throw new RuntimeException(
                "Unable to find android system package", e);
    }
}

从上面的代码可以看出,AMS的setSystemProcess主要有四个主要的功能:
1、注册一些服务;
2、获取package名为“android”的应用的ApplicationInfo;
3、调用ActivityThread的installSystemApplicationInfo;
4、AMS进程管理相关的操作。

这四个主要的功能中,第一个比较简单,就是用Binder通信完成注册。
我们主要看看后三个功能对应的流程。

1.1 获取ApplicationInfo
如前所述,这部分相关的代码为:

..........
ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
        "android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
..........

1.1.1 获取PKMS服务代理
我们先看看mContext.getPackageManager()的操作过程。

我们已经知道mContext的实现类是ContextImpl,其中对应的代码如下:

@Override
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }

    //依赖于ActivityThread的getPackageManager函数
    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // Doesn't matter if we make more than one instance.
        //利用PKMS的代理对象,构建ApplicationPackageManager
        //该类继承PackageManager
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    }

    return null;
}

跟进一下ActivityThread中的getPackageManager:

public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
        return sPackageManager;
    }
    //依赖于Binder通信,获取到PKMS对应的BpBinder
    IBinder b = ServiceManager.getService("package");
    .....................
    //得到PKMS对应的Binder服务代理
    sPackageManager = IPackageManager.Stub.asInterface(b);
    ....................
    return sPackageManager;
}

从上面的代码我们可以看到,AMS获取PKMS用到了Binder通信。

实际上,PKMS由SystemServer创建,与AMS运行在同一个进程。
AMS完全可以不经过Context、ActivityThread、Binder来获取PKMS。

根据一些资料,推断出原生代码这么做的原因是:
SystemServer进程中的服务,也使用Android运行环境来交互,
保留了组件之间交互接口的统一,为未来的系统保留了可扩展性。

1.1.2 通过PKMS获取ApplicationInfo
得到PKMS的代理对象后,AMS调用PKMS的getApplicationInfo接口,获取package名为”android”的ApplicationInfo。

在AMS的setSystemProcess被调用前,PKMS已经启动了。
之前分析PKMS的博客中,我们已经提到,在PKMS的构造函数中,
它将解析手机中所有的AndroidManifest.xml,然后形成各种数据结构以维护应用的信息。

getApplicationInfo就是通过package名,从对应的数据结构中,取出对应的应用信息。
这部分内容主要就是查询数据结构的内容,不作深入分析。

1.2 installSystemApplicationInfo
得到framework-res.apk对应的ApplicationInfo后,需要将这部分ApplicationInfo保存到SystemServer对应的ActivityThread中。

这部分对应的代码为:

..............
mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());
..............

AMS中的mSystemThread就是SystemServer中创建出的ActivityThread。
因此我们跟进一下ActivityThread的installSystemApplicationInfo函数:

public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
    synchronized (this) {
        //调用SystemServer中创建出的ContextImpl的installSystemApplicationInfo函数
        getSystemContext().installSystemApplicationInfo(info, classLoader);

        // give ourselves a default profiler
        //创建一个Profiler对象,用于性能统计
        mProfiler = new Profiler();
    }
}

继续跟进ContextImpl的installSystemApplicationInfo函数:

void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
    //前面已经提到过mPackageInfo的类型为LoadedApk
    mPackageInfo.installSystemApplicationInfo(info, classLoader);
}

随着流程进入到LoadedApk:

/**
* Sets application info about the system package.
*/
void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
    //这个接口仅供系统进程调用,故这里断言一下
    assert info.packageName.equals("android");

    mApplicationInfo = info;
    mClassLoader = classLoader;
}

至此,我们知道了installSystemApplicationInfo的真相就是:
将“android”对应的ApplicationInfo(即framework-res.apk对应的ApplicationInfo),
加入到SystemServer之前调用createSystemContext时,创建出的LoadedApk中。
毕竟SystemServer创建System Context时,PKMS并没有完成对手机中文件的解析,初始的LoadedApk中并没有持有有效的ApplicationInfo。

在此基础上,AMS下一步的工作就呼之欲出了。

由于framework-res.apk运行在SystemServer进程中,而AMS是专门用于进程管理和调度的,
因此SystemServer进程也应该在AMS中有对应的管理结构。

于是,AMS的下一步工作就是将SystemServer的运行环境和一个进程管理结构对应起来,并进行统一的管理。

1.3 AMS进程管理注意到上面的ContentProvider注册到AMS后,进行了notifyAll的操作。
举例来说:进程A需要查询一个数据库,需要通过进程B中的某个ContentProvider来实施。
如果B还未启动,那么AMS就需要先启动B。在这段时间内,A需要等待B启动并注册对应的ContentProvider。
B一旦完成ContentProvider的注册,就需要告知A退出等待以继续后续的查询工作。
setSystemProcess函数中,进程管理相关的代码为:

.............
synchronized (this) {
    //创建进程管理对应的结构ProcessRecord
    ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);

    //由于此时创建的是SystemServer进程对应ProcessRecord
    //因此设定了一些特殊值
    app.persistent = true;
    app.pid = MY_PID;
    app.maxAdj = ProcessList.SYSTEM_ADJ;

    //将SystemServer对应的ApplicationThread保存到ProcessRecord中
    app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);

    synchronized (mPidsSelfLocked) {
        //按pid将ProcessRecord保存到mPidsSelfLocked中
        mPidsSelfLocked.put(app.pid, app);
    }

    //updateLruProcessLocked来调整进程在mLruProcess列表的位置
    //在这个列表中,最近活动过得进程总是位于前列,同时拥有Activity的进程位置总是前于只有Service的进程
    updateLruProcessLocked(app, false, null);

    //更新进程对应的oom_adj值(oom_adj将决定进程是否被kill掉)
    updateOomAdjLocked();
}
...............

这里我们仅分析一下创建进程管理结构的函数newProcessRecordLocked。
updateLruProcessLocked和updateOomAdjLocked函数比较复杂,等对AMS有更多的了解后,再做分析。

final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
        boolean isolated, int isolatedUid) {
    //进程的名称
    String proc = customProcess != null ? customProcess : info.processName;

    //将用于创建该进程的电源统计项
    BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();

    final int userId = UserHandle.getUserId(info.uid);
    //isolated此时为false
    if (isolated) {
        ..........
    }
    //创建出对应的存储结构
    final ProcessRecord r = new ProcessRecord(stats, info, proc, uid);

    //判断进程是否常驻
    if (!mBooted && !mBooting
            && userId == UserHandle.USER_SYSTEM
            && (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
        r.persistent = true;
    }

    //按进程名将ProcessRecord存入到AMS的变量mProcessNames中
    //该变量的类型为ProcessMap<ProcessRecord> 
    //结合前面的代码,我们知道AMS有两种方式可以取到ProcessRecord
    //一是根据进程名,二是根据进程名称
    addProcessNameLocked(r);
    return r;
}

跟进一下ProcessRecord的构造函数:

ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info,
        String _processName, int _uid) {
    mBatteryStats = _batteryStats; //用于电量统计
    info = _info;  //保存ApplicationInfo
    ...........
    processName = _processName;  //保存进程名

    //一个进程能运行多个Package,pkgList用于保存package名
    pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.versionCode));

    //以下变量和进程调度优先级有关
    maxAdj = ProcessList.UNKNOWN_ADJ;
    curRawAdj = setRawAdj = ProcessList.INVALID_ADJ;
    curAdj = setAdj = verifiedAdj = ProcessList.INVALID_ADJ;

    //决定进程是否常驻内存(即使被杀掉,系统也会重启它)
    persistent = false;

    removed = false;
    lastStateTime = lastPssTime = nextPssTime = SystemClock.uptimeMillis();
}

总结
至此,我们对AMS的setSystemProcess函数分析告一段落。
从上面的代码可以看出,在这个函数中除了发布一些服务外,主要是:
将framework-res.apk的信息加入到SystemServer对应的LoadedApk中,同时构建SystemServer进程对应的ProcessRecord,
以将SystemServer进程纳入到AMS的管理中。

2、AMS的installSystemProviders
接下来AMS启动相关的操作,定义于SystemServer的startOtherServices函数中。

private void startOtherServices() {
    ...........
    mActivityManagerService.installSystemProviders();
    ...........
}

我们跟进一下AMS的installSystemProviders函数:

public final void installSystemProviders() {
    List<ProviderInfo> providers;
    synchronized (this) {
        //AMS根据进程名取出SystemServer对应的ProcessRecord
        ProcessRecord app = mProcessNames.get("system", Process.SYSTEM_UID);

        //1、得到该ProcessRecord对应的ProviderInfo
        providers = generateApplicationProvidersLocked(app);

        //这里仅处理系统级的Provider
        if (providers != null) {
            for (int i=providers.size()-1; i>=0; i--) {
                ProviderInfo pi = (ProviderInfo)providers.get(i);
                    if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
                        Slog.w(TAG, "Not installing system proc provider " + pi.name
                                + ": not system .apk");
                        providers.remove(i);
                    }
                }
            }
        }
    }

    if (providers != null) {
        //2、安装Provider
        mSystemThread.installSystemProviders(providers);
    }

    //创建ContentObserver监控Settings数据库中Secure、System和Global表的变化
    mCoreSettingsObserver = new CoreSettingsObserver(this);

    //创建ContentObserver监控Settings数据库中字体大小的变化
    mFontScaleSettingObserver = new FontScaleSettingObserver();
}

从上面的代码可以看出,installSystemProviders主要是加载运行在SystemServer进程中的ContentProvider,即SettingsProvider.apk (定义于frameworks/base/packages/SettingsProvider)。

上面有两个比较重要的函数:
1、generateApplicationProvidersLocked返回一个进程对应的ProviderInfo List。
2、ActivityThread可以看做是进程的Android运行环境,因此它的installSystemProviders表示为对应进程安装ContentProvider。

当SettingsProvider被加载到SystemServer进程中运行后,AMS就注册了两个ContentObserver监控SettingsProvider中的字段变化。
AMS监控的字段影响范围比较广,例如字体发生变化时,很多应用的显示界面都需要做出调整。
这也许就是让AMS来负责监控这些字段的原因。

接下来,我们分别看看上述比较重要的两个函数。

2.1 generateApplicationProvidersLocked

private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
    List<ProviderInfo> providers = null;
    try {
        //利用PKMS根据进程名及权限,从数据结构中得到进程对应ProviderInfo
        providers = AppGlobals.getPackageManager()
                .queryContentProviders(app.processName, app.uid,
                        STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS
                            | MATCH_DEBUG_TRIAGED_MISSING)
                .getList();
    } catch (RemoteException ex) {
    }
    .............
    int userId = app.userId;
    if (providers != null) {
        int N = providers.size();
        //写这行代码的人,一定是个极客!!!!
        //通常而言,我们逐渐向容器加入数据时,容器只有在数据超出当前存储空间时
        //才会进行内存的重新分配(一般是乘2)和数据的拷贝
        //因此若待加入数据总量很大,在逐步向容器加入数据的过程中,容器将会有多次重新分配和拷贝的过程
        //或许整体的开销并不是很惊人,但事先将内存一次分配到位,体现了对极致的追求 (情不自禁的写了这段话。。。)
        app.pubProviders.ensureCapacity(N + app.pubProviders.size());

        for (int i=0; i<N; i++) {
            ProviderInfo cpi = (ProviderInfo)providers.get(i);

            //判断Provider是否为单例的
            boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
                    cpi.name, cpi.flags);

            //这里应该是针对多用户的处理
            //若一个Provider是单例的,但当前进程不属于默认用户,那么这个Provider将不被处理
            //简单来说,就是两个用户都启动一个进程时(有了两个进程),
            //定义于进程Package中单例的Provider仅运行在默认用户启动的进程中
            if (singleton && UserHandle.getUserId(app.uid) != UserHandle.USER_SYSTEM) {
                // This is a singleton provider, but a user besides the
                // default user is asking to initialize a process it runs
                // in...  well, no, it doesn't actually run in this process,
                // it runs in the process of the default user.  Get rid of it.
                providers.remove(i);
                N--;
                i--;
                continue;
            }

            //包名和类名组成ComponentName
            ComponentName comp = new ComponentName(cpi.packageName, cpi.name);

            //创建ContentProvider对应的ContentProviderRecord
            //加入到AMS的mProviderMap中
            ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
            if (cpr == null) {
                cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton);
                mProviderMap.putProviderByClass(comp, cpr);
            }
            ............
            //将ContentProviderRecord保存在ProcessRecord中
            app.pubProviders.put(cpi.name, cpr);

            if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
                // Don't add this if it is a platform component that is marked
                // to run in multiple processes, because this is actually
                // part of the framework so doesn't make sense to track as a
                // separate apk in the process.

                //当ContentProvider仅属于当前进程时,还需要统计该Provider的运行信息
                app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
                        mProcessStats);
            }

            //通知PKMS记录该Provider对应包被使用的时间
            notifyPackageUse(cpi.applicationInfo.packageName,
                    PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER);
        }
    }
    return providers;
}

整体来讲generateApplicationProvidersLocked函数的思想很简单,最主要的功能是:
从PKMS中得到应用对应的ContentProvider,然后利用应用信息和对应的ContentProvider组成ContentProviderRecord,
并按包名存储到AMS的mProviderMap中。

AMS保存ProviderInfo的原因是:它需要管理ContentProvider。
此外,我们看到ProcessRecord也保存了ProviderInfo,这是因为ContentProvider最终要落实到一个进程中。

这也是为了方便AMS的管理,毕竟一个进程退出时,AMS需要将其中运行的ContentProvider信息从系统中移除。

generateApplicationProvidersLocked中唯一稍微绕了点弯的地方是,针对多用户的情况,判断了ContentProvider是否为单例的。
就像我注释里写的,个人觉得在多用户的场景下,“同一个”进程变成了多个,分别运行在每个用户对应的空间中。
假设ContentProvider在对应的xml中定义了,运行在指定进程A中。
那么当多个用户都加载进程A时(此时进程名或许相似,但不是一个进程),
A进程中定义的ContentProvider将被加载多次,分别运行在各个用户的进程A中。
但是当一个ContentProvider是单例的,那么该ContentProvider仅会被加载在系统用户(类似于administrator)启动的进程中。

虽然目前多用户的场景比较少,ContentProvider到底运行在哪个进程中,可能也不是那么重要。
但本着学习的态度,我们还是看看isSingleton的实现:

boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
        String className, int flags) {
    boolean result = false;
    // For apps that don't have pre-defined UIDs, check for permission
    //非系统用户,就是多了个权限检查
    //是否单例还是取决于ServiceInfo.FLAG_SINGLE_USER
    if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
         if ((flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
            if (ActivityManager.checkUidPermission(
                    INTERACT_ACROSS_USERS,
                    aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
                //无权限,则抛出异常
                .............
            }
         }
         // Permission passed
         result = true;
    } else if ("system".equals(componentProcessName)) {
        result = true;
    } else if ((flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
        // Phone app and persistent apps are allowed to export singleuser providers.
        result = UserHandle.isSameApp(aInfo.uid, Process.PHONE_UID)
                 || (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
    }
    .............
    return result;
}

从上面的代码,我们知道了:
对于非系统用户而言,当Provider的flag包含ServiceInfo.FLAG_SINGLE_USER时,它是单例的;
对于系统用户而言,当Provider运行在系统进程中,或者 该Provider运行在(Phone进程或常驻进程)且(包含ServiceInfo.FLAG_SINGLE_USER)时,它是单例的。

至此,我们知道了generateApplicationProvidersLocked函数,主要就是用于得到和保存对应进程的ContentProvider信息。

2.2 installSystemProviders
得到运行在进程中的ContentProvider的信息后,当然要进行安装了。
对于运行在SystemServer中的ContentProvider,AMS将调用ActivityThread的installSystemProviders进行处理。

public final void installSystemProviders(List<ProviderInfo> providers) {
    if (providers != null) {
        //对于SystemServer进程而言,mInitialApplication是framework-res.apk对应的Application
        installContentProviders(mInitialApplication, providers);
    }
}

private void installContentProviders(
        Context context, List<ProviderInfo> providers) {
    final ArrayList<IActivityManager.ContentProviderHolder> results =
            new ArrayList<IActivityManager.ContentProviderHolder>();

    for (ProviderInfo cpi : providers) {
        ..............
        //1、初始化并保存ContentProvider
        IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
                false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
        if (cph != null) {
            cph.noReleaseNeeded = true;
            results.add(cph);
        }
    }

    try {
        //2、向AMS注册ContentProvider
        ActivityManagerNative.getDefault().publishContentProviders(
                getApplicationThread(), results);
    } catch (RemoteException ex) {
        throw ex.rethrowFromSystemServer();
    }
}

installContentProviders是安装ContentProvider时的通用程序,主要包括两方面的工作:
1、调用installProvider得到IActivityManager.ContentProviderHolder对象,其间完成了对应ContentProvider的初始化等工作。
2、向AMS发布这个IActivityManager.ContentProviderHolder。

2.2.1 installProvider

private IActivityManager.ContentProviderHolder installProvider(Context context,
        IActivityManager.ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    if (holder == null || holder.provider == null) {
        //此时holder==null, 进入这个分支
        ............
        Context c = null;
        ApplicationInfo ai = info.applicationInfo;

        //下面判断的作用是:为待安装的ContentProvider找到对应的Application
        //在AndroidManifest.xml中,ContentProvider是Application的子标签,
        //因此ContentProvider与Application有一种对应关系
        //在本次的流程中,传入的Context是mInitialApplication,代表的是framework-res.apk
        //而Provider代表的是SettingsProvider, SettingsProvider.apk所对应的Application还未创建

        if (context.getPackageName().equals(ai.packageName)) {
            c = context;
        } else if (mInitialApplication != null &&
            mInitialApplication.getPackageName().equals(ai.packageName)) {
            c = mInitialApplication;
        } else {
            try {
                //以下将创建一个Context,指向SettingsProvider.apk
                //ai.packageName为com.android.provider.settings

                //利用package对应的LoadedApk信息,创建ContextImpl
                //当前主线程如果加载过这个LoadedApk,将从存储变量中取出LoadedApk
                //否则将通过PKMS得到对应的ApplicationInfo,并以ApplicationInfo构建出LoadedApk,然后保存在存储变量中
                c = context.createPackageContext(ai.packageName,
                        Context.CONTEXT_INCLUDE_CODE);
            } catch (PackageManager.NameNotFoundException e) {
                // Ignore
            }
        }
        ..................
        try {
            //除了ContextProvider与Application的对应关系外,必须先找到ContextProvider对应的Context的另一个原因是:
            //只有正确的Context才能加载对应APK的Java字节码,从而通过反射的方式创建出ContextProvider实例

            //得到对应的ClassLoader
            final java.lang.ClassLoader cl = c.getClassLoader();

            //反射创建实例
            localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();

            //得到ContentProvider的mTransport对象
            //表现类型为接口IContentProvider,实际为ContentProviderNative,即ContentProvider的Binder通信服务端
            provider = localProvider.getIContentProvider();
            ..................
            //初始化ContentProvider,内部会调用ContentProvder的onCreate函数
            localProvider.attachInfo(c, info);
        } catch(java.lang.Exception e) {
            ............
        }
    } else {
        provider = holder.provider;
        if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                + info.name);
    }

    IActivityManager.ContentProviderHolder retHolder;

    synchronized (mProviderMap) {
        ...............
        //调用ContentProviderNative的asBinder
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            ComponentName cname = new ComponentName(info.packageName, info.name);
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);
            if (pr != null) {
                ............
            } else {
                //创建ContentProviderHolder持有ContentProvider
                holder = new IActivityManager.ContentProviderHolder(info);
                holder.provider = provider;
                holder.noReleaseNeeded = true;
                //构造ProviderClientRecord,并按authority将ProviderClientRecord存入mProviderMap
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            ...............
        }
    }

    return retHolder;
}

installProvider的代码较长,但实际思想很简单,就是环环相扣的三步:
1、创建出ContentProvider对应的ContextImpl(代表对应的运行环境);
2、利用ContextImpl得到对应的ClassLoader,完成ContentProvider的初始化和启动;
3、得到与ContentProvider通信的BpBinder,然后按名称和BpBinder,将ContentProvider保存到对应的存储结构中。

ActivityThread与ContentProvider的关系大概如上图所示。
ContentProvider本身只是一个容器,其内部持有的Transport类才能提供对跨进程调用的支持。
Transport类继承自ContentProviderNative类,作为ContentProvider的Binder通信服务端。
ContentProviderNative中定义了ContentProvderProxy类,将作为Binder通信的服务端代理。

如上代码所示,ActivityThread用mLocalProviders保存运行在本地的ContentProvider时,
使用的键值就是ContentProvider的Binder通信服务端。

2.2.2 publishContentProviders
ContentProvider初始化完成后,我们需要向AMS注册它。
如前所述,在ActivityThread的installContentProviders函数中,将通过下面这段代码进行注册:

..........
ActivityManagerNative.getDefault().publishContentProviders(
        getApplicationThread(), results);
.........

这段代码是注册ContentProvider的通用代码,因此即使我们现在的流程运行在AMS中,此处仍然将通过Binder通信进行调用。
ActivityManagerNative.getDefault()将得到ActivityManagerProxy对象,因此上面代码实际上调用的是ActivityManagerProxy.publishContentProviders:

//ActivityThread注册ContentProvider时,传入了自己的ApplicationThread,
//当AMSActivityThread通信时,ApplicationThread作为Binder通信的服务端
public void publishContentProviders(IApplicationThread caller,
        List<ContentProviderHolder> providers) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken(IActivityManager.descriptor);
    data.writeStrongBinder(caller != null ? caller.asBinder() : null);
    data.writeTypedList(providers);
    //消息打包完成后,发往服务端AMS
    mRemote.transact(PUBLISH_CONTENT_PROVIDERS_TRANSACTION, data, reply, 0);
    reply.readException();
    data.recycle();
    reply.recycle();
}

在AMS父类AMN的onTransact函数中:

public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    switch (code) {
        .......
        case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);
            //得到ApplicationThread的BpBinder
            IBinder b = data.readStrongBinder();

            //得到ActivityThread中ApplicationThread的服务端代理
            IApplicationThread app = ApplicationThreadNative.asInterface(b);

            //解析出消息
            ArrayList<ContentProviderHolder> providers =
                    data.createTypedArrayList(ContentProviderHolder.CREATOR);

            //AMN调用子类的函数
            publishContentProviders(app, providers);
            reply.writeNoException();
            return true;
        }
        .......
    }
    .........
}

跟着流程,现在我们看看AMS中的publishContentProviders函数:

public final void publishContentProviders(IApplicationThread caller,
        List<ContentProviderHolder> providers) {
    if (providers == null) {
        return;
    }
    ...........
    synchronized (this) {
        //找到调用者对应的ProcessRecord对象
        final ProcessRecord r = getRecordForAppLocked(caller);
        ...........
        final int N = providers.size();
        for (int i = 0; i < N; i++) {
            ContentProviderHolder src = providers.get(i);
            ............
            //ProcessRecord的pubProviders中保存了ContentProviderRecord信息
            //这是根据PKMS解析出的Package信息生成的
            //此处主要判断将要发布的ContentProvider是否由该Pacakge声明
            ContentProviderRecord dst = r.pubProviders.get(src.info.name);
            ............
            if (dst != null) {
                ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
                //按名称保存到mProviderMap
                mProviderMap.putProviderByClass(comp, dst);
                ...............
                String names[] = dst.info.authority.split(";");
                for (int j = 0; j < names.length; j++) {
                    //按authority保存到mProviderMap
                    mProviderMap.putProviderByName(names[j], dst);
                }
            }

            //mLaunchingProviders保存处于启动状态的Provider
            int launchingCount = mLaunchingProviders.size();
            int j;
            boolean wasInLaunchingProviders = false;
            for (j = 0; j < launchingCount; j++) {
                if (mLaunchingProviders.get(j) == dst) {
                    //这个ContentProvider完成启动,从队列中移除
                    mLaunchingProviders.remove(j);
                    wasInLaunchingProviders = true;
                    j--;
                    launchingCount--;
                }
            }
            if (wasInLaunchingProviders) {
                //取消启动超时的消息
                mHandler.removeMessages(CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG, r);
            }
            synchronized (dst) {
                dst.provider = src.provider;
                dst.proc = r;
                //通知等待那些等待ContentProvider所在进程启动的客户端进程
                dst.notifyAll();
            }
            //每发布一个ContentProvder,均调整对应进程的oom_adj
            updateOomAdjLocked(r);
            //判断,并在需要时更新ContentProvider相关的统计信息
            maybeUpdateProviderUsageStatsLocked(r, src.info.packageName,
                    src.info.authority);
        }
    }
    ..........
}

publishContentProviders函数结束后,一个ContentProvider就算正式在系统中注册了。
在AMS的启动过程中,此处注册的是SettingsProvider。
此后,Settings数据库相关的操作均由它来管理。

注意到上面的ContentProvider注册到AMS后,进行了notifyAll的操作。
举例来说:进程A需要查询一个数据库,需要通过进程B中的某个ContentProvider来实施。
如果B还未启动,那么AMS就需要先启动B。在这段时间内,A需要等待B启动并注册对应的ContentProvider。
B一旦完成ContentProvider的注册,就需要告知A退出等待以继续后续的查询工作。

五、AMS的systemReady
接下来,我们看看AMS启动的最后一部分:systemReady。
该函数在SystemServer中startOtherServices的最后被调用:

private void startOtherServices() {
    ............
    // We now tell the activity manager it is okay to run third party
    // code.  It will call back into us once it has gotten to the state
    // where third party code can really run (but before it has actually
    // started launching the initial applications), for us to complete our
    // initialization.
    mActivityManagerService.systemReady(new Runnable() {
        ..............
    });
}

我们分段看看AMS中systemReady的处理流程。
此处的分段并没有实际的意义,只是代码确实太长了,并且连续性不够,因此分开描述。

1 阶段一

public void systemReady(final Runnable goingCallback) {
    synchronized(this) {
        ..........
        //这一部分主要是调用一些关键服务SystemReady相关的函数,
        //进行一些等待AMS初始完,才能进行的工作

        // Make sure we have the current profile info, since it is needed for security checks.
        mUserController.onSystemReady();

        mRecentTasks.onSystemReadyLocked();
        mAppOpsService.systemReady();
        mSystemReady = true;
    }

    ArrayList<ProcessRecord> procsToKill = null;
    synchronized(mPidsSelfLocked) {
        //mPidsSelfLocked中保存当前正在运行的所有进程的信息
        for (int i=mPidsSelfLocked.size()-1; i>=0; i--) {
            ProcessRecord proc = mPidsSelfLocked.valueAt(i);

            //在AMS启动完成前,如果没有FLAG_PERSISTENT标志的进程已经启动了,
            //就将这个进程加入到procsToKill中
            if (!isAllowedWhileBooting(proc.info)){
                if (procsToKill == null) {
                    procsToKill = new ArrayList<ProcessRecord>();
                }
                procsToKill.add(proc);
            }
        }
    }

    synchronized(this) {
        //利用removeProcessLocked关闭procsToKill中的进程
        if (procsToKill != null) {
            for (int i=procsToKill.size()-1; i>=0; i--) {
                ProcessRecord proc = procsToKill.get(i);
                Slog.i(TAG, "Removing system update proc: " + proc);
                removeProcessLocked(proc, true, false, "system update done");
            }
        }

        // Now that we have cleaned up any update processes, we
        // are ready to start launching real processes and know that
        // we won't trample on them any more.

        //至此系统准备完毕
        mProcessesReady = true;
    }
    ............
    //根据数据库和资源文件,获取一些配置参数
    retrieveSettings();

    final int currentUserId;
    synchronized (this) {
        //得到当前的用户ID
        currentUserId = mUserController.getCurrentUserIdLocked();

        //读取urigrants.xml,为其中定义的ContentProvider配置对指定Uri数据的访问/修改权限
        //原生代码中,似乎没有urigrants.xml文件
        //实际使用的grant-uri-permission是分布式定义的
        readGrantedUriPermissionsLocked();
    }
    ..........

这一部分的工作主要是调用一些关键服务的初始化函数,
然后杀死那些没有FLAG_PERSISTENT却在AMS启动完成前已经存在的进程,
同时获取一些配置参数。
需要注意的是,由于只有Java进程才会向AMS注册,而一般的Native进程不会向AMS注册,因此此处杀死的进程是Java进程。

2 阶段二

//1、调用参数传入的runnable对象,SystemServer中有具体的定义
if (goingCallback != null) goingCallback.run();
..............
//调用所有系统服务的onStartUser接口
mSystemServiceManager.startUser(currentUserId);
.............
synchronized (this) {
    // Only start up encryption-aware persistent apps; once user is
    // unlocked we'll come back around and start unaware apps
    2、启动persistent为1的application所在的进程
    startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_AWARE);

    // Start up initial activity.
    mBooting = true;

    // Enable home activity for system user, so that the system can always boot
    //当isSplitSystemUser返回true时,意味者system user和primary user是分离的
    //这里应该是让system user也有启动home activity的权限吧
    if (UserManager.isSplitSystemUser()) {
        ComponentName cName = new ComponentName(mContext, SystemUserHomeActivity.class);
        try {
            AppGlobals.getPackageManager().setComponentEnabledSetting(cName,
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0,
                    UserHandle.USER_SYSTEM);
        } catch (RemoteException e) {
            throw e.rethrowAsRuntimeException();
        }
    }

    //3、启动Home
    startHomeActivityLocked(currentUserId, "systemReady");

    try {
        //发送消息,触发处理Uid错误的Application
        if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
            ..........
            mUiHandler.obtainMessage(SHOW_UID_ERROR_UI_MSG).sendToTarget();
        }
    } catch (RemoteException e) {
    }
    //发送一些广播信息
    ............
    //这里暂时先不深入,等进一步了解Activity的启动过程后,再做了解
    mStackSupervisor.resumeFocusedStackTopActivityLocked();
    ............
}
.............

从部分代码来看,主要的工作就是通知一些服务可以进行systemReady相关的工作,并进行启动服务或应用进程的工作。

2.1 调用回调接口
回调接口的具体内容定义与SystemServer.java中,其中会调用大量服务的onBootPhase函数、一些对象的systemReady函数或systemRunning函数。
此处,我们仅截取一些比较特别的内容:

public void run() {
    ............
    try {
        //启动NativeCrashListener监听"/data/system/ndebugsocket"中的信息
        //实际上是监听debuggerd传入的信息
        mActivityManagerService.startObservingNativeCrashes();
    } catch (Throwable e) {
        reportWtf("observing native crashes", e);
    }
    ............
    try {
        //启动SystemUi
        startSystemUi(context);
    } catch (Throwable e) {
        reportWtf("starting System UI", e);
    }
    ............
    //这个以前分析过,启动Watchdog
    Watchdog.getInstance().start();
    ....................
}

回调接口中的内容较多,不做一一分析。

2.2 启动persistent标志的进程
我们看看startPersistentApps对应的内容:

private void startPersistentApps(int matchFlags) {
    .............

    synchronized (this) {
        try {
            //从PKMS中得到persistent为1的ApplicationInfo
            final List<ApplicationInfo> apps = AppGlobals.getPackageManager()
                    .getPersistentApplications(STOCK_PM_FLAGS | matchFlags).getList();
            for (ApplicationInfo app : apps) {
                //由于framework-res.apk已经由系统启动,所以此处不再启动它
                if (!"android".equals(app.packageName)) {
                    //addAppLocked中将启动application所在进程
                    addAppLocked(app, false, null /* ABI override */);
                }
            }
        } catch (RemoteException ex) {
        }
    }
}

跟进一下addAppLocked函数:

final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated,
        String abiOverride) {
    //以下是取出或构造出ApplicationInfo对应的ProcessRecord
    ProcessRecord app;
    if (!isolated) {
        app = getProcessRecordLocked(info.processName, info.uid, true);
    } else {
        app = null;
    }

    if (app == null) {
        app = newProcessRecordLocked(info, null, isolated, 0);
        updateLruProcessLocked(app, false, null);
        updateOomAdjLocked();
    }
    ...........
    // This package really, really can not be stopped.
    try {
        //通过PKMS将package对应数据结构的StoppedState置为fasle
        AppGlobals.getPackageManager().setPackageStoppedState(
                info.packageName, false, UserHandle.getUserId(app.uid));
    } catch (RemoteException e) {
    } catch (IllegalArgumentException e) {
        Slog.w(TAG, "Failed trying to unstop package "
                + info.packageName + ": " + e);
    }

    if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
        app.persistent = true;
        app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
    }

    if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
        mPersistentStartingProcesses.add(app);
        //启动应用所在进程,将发送消息给zygote,后者fork出进程
        startProcessLocked(app, "added application", app.processName, abiOverride,
                null /* entryPoint */, null /* entryPointArgs */);
    }

    return app;
}

这里最终将通过startProcessLocked函数,启动实际的应用进程。
正如之前分析zygote进程时,提过的一样,zygote中的server socket将接收消息,然后为应用fork出进程。

2.3 启动Home Activity
看看启动Home Activity对应的startHomeActivityLocked函数:

boolean startHomeActivityLocked(int userId, String reason) {
    ..............
    Intent intent = getHomeIntent();
    //根据intent中携带的ComponentName,利用PKMS得到ActivityInfo
    ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
    if (aInfo != null) {
        intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        aInfo = new ActivityInfo(aInfo);
        aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);

        //此时home对应进程应该还没启动,app为null
        ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                aInfo.applicationInfo.uid, true);
        if (app == null || app.instrumentationClass == null) {
            intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
            //启动home
            mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
        }
    } else {
        ..........
    }
    return true;
}

这里暂时先不深究Home Activity启动的具体过程。
从手头的资料来看,当Home Activity启动后,
ActivityStackSupervisor中的activityIdleInternalLocked函数将被调用(具体调用过程,还需要研究):

final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
        Configuration config) {
    ...........
    if (isFocusedStack(r.task.stack) || fromTimeout) {
        booting = checkFinishBootingLocked();
    }
    ............
}

在checkFinishBootingLocked函数中:

private boolean checkFinishBootingLocked() {
    //mService为AMS,mBooting变量在AMS回调SystemServer中定义的Runnable时,置为了true
    final boolean booting = mService.mBooting;
    boolean enableScreen = false;
    mService.mBooting = false;
    if (!mService.mBooted) {
        mService.mBooted = true;
        enableScreen = true;
    }
    if (booting || enableScreen) {、
        //调用AMS的接口,发送消息
        mService.postFinishBooting(booting, enableScreen);
    }
    return booting;
}

最终,AMS的finishBooting函数将被调用:

final void finishBooting() {
    .........
    //以下是注册广播接收器,用于处理需要重启的package
    IntentFilter pkgFilter = new IntentFilter();
    pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
    pkgFilter.addDataScheme("package");
    mContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
            if (pkgs != null) {
                for (String pkg : pkgs) {
                    synchronized (ActivityManagerService.this) {
                        if (forceStopPackageLocked(pkg, -1, false, false, false, false, false,
                                0, "query restart")) {
                            setResultCode(Activity.RESULT_OK);
                            return;
                        }
                    }
                }
            }
       }
    }, pkgFilter);
    ...........
    // Let system services know.
    mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);

    //以下是启动那些等待启动的进程
    synchronized (this) {
        // Ensure that any processes we had put on hold are now started
        // up.
        final int NP = mProcessesOnHold.size();
            if (NP > 0) {
                ArrayList<ProcessRecord> procs =
                        new ArrayList<ProcessRecord>(mProcessesOnHold);
                for (int ip=0; ip<NP; ip++) {
                    .................
                    startProcessLocked(procs.get(ip), "on-hold", null);
                }
            }
        }
    }
    ..............
    if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
        // Start looking for apps that are abusing wake locks.
        //每15min检查一次系统各应用进程使用电量的情况,如果某个进程使用WakeLock的时间过长
        //AMS将关闭该进程
        Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
        mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);

        // Tell anyone interested that we are done booting!
        SystemProperties.set("sys.boot_completed", "1");
        .................
        //此处从代码来看发送的是ACTION_LOCKED_BOOT_COMPLETED广播
        //在进行unlock相关的工作后,mUserController将调用finishUserUnlocking,发送SYSTEM_USER_UNLOCK_MSG消息给AMS
        //AMS收到消息后,调用mUserController的finishUserUnlocked函数,经过相应的处理后,
        //在mUserController的finishUserUnlockedCompleted中,最终将会发送ACTION_BOOT_COMPLETED广播
        mUserController.sendBootCompletedLocked(.........);
        .................
    }
}

最终,当AMS启动Home Activity结束,并发送ACTION_BOOT_COMPLETED广播时,AMS的启动过程告一段落。

总结
对于整个AMS启动过程而言,博客中涉及的内容可能只是极小的一部分。
但即使我们尽可能的简化,整个过程的内容还是非常多。


大图链接

不过我们回头看看整个过程,还是能比较清晰地将AMS的启动过程分为四步,如上图所示:
1、创建出SystemServer进程的Android运行环境。
在这一部分,SystemServer进程主要创建出对应的ActivityThread和ContextImpl,构成Android运行环境。
AMS的后续工作依赖于SystemServer在此创建出的运行环境。

2、完成AMS的初始化和启动。
在这一部分,单纯地调用AMS的构造函数和start函数,完成AMS的一些初始化工作。

3、将SystemServer进程纳入到AMS的管理体系中。
AMS作为Java世界的进程管理和调度中心,要对所有Java进程一视同仁,因此SystemServer进程也必须被AMS管理。
在这个过程中,AMS加载了SystemServer中framework-res.apk的信息,并启动和注册了SettingsProvider.apk。

4、开始执行AMS启动完毕后才能进行的工作。
系统中的一些服务和进程,必须等待AMS完成启动后,才能展开后续工作。
在这一部分,AMS通过调用systemReady函数,通知系统中的其它服务和进程,可以进行对应工作了。
在这个过程中,值得我们关注的是:Home Activity被启动了。当该Activity被加载完成后,最终会触发ACTION_BOOT_COMPLETED广播。

作者:Gaugamela 发表于2016/11/15 19:44:24 原文链接
阅读:50 评论:0 查看评论

linux驱动开发:mma7660 sensor的配置

$
0
0

上一章节有介绍过这颗IC,是freescale的一个低精度的重力传感器吧。我认为,可以区分8个方向。x,y,z三轴。精度6bit,采样值范围[-32,+32].还有测量shake和tap等数据。
这边的驱动没有自己写.只是有看懂它原先的驱动是如何实现和工作的.
它的驱动引入了一个 hwmon device的注册。具体的作用是在sys/class/hwmon下生成了一个目录hwmonx。
它可以添加你设备的一些属性attr,以此来方便用户去read那些属性的状态。网上也有说借此来导出内核的信息非常方便,几乎所有的sensor都喜欢这样用。我们可以借用内核导出x,y,z三轴的值,来方便查看数据.介于此,我们可以添加相关的属性,当访问这些属性文件时,实则是在调用对应的接口函数。比如读相应的寄存器,再显示给用户界面。
另外一个:传感器元件,都有一个特性。周期性的采样数据并上报数据。
我们再实现触摸屏驱动的时候,当你有手触摸时,立即便会上报一次数据,否则则没有.按键也是一样。
像有的温度传感器,基本上是每次间隔一定的时间,才能采样一次准确的数据。并不是立刻就能获得数据的.需要一定的时间。
所以这边引入了一个input子系统的扩展:input_polled_dev.

我们依旧把它作为输入子系统来实现。上报x,y,z三轴的绝对坐标.

所以整个驱动的框架没变。新多出来的功能是 属性导出功能,hwmon设备的引入,和input_polled_dev的引入。

程序我都有注释过,初始化完毕后,当有触发过中断时,程序便会更新一次TILT状态寄存器。此时我们可以去读我们需要的数据。根据读到的数据做出相应的判断.比如说是否旋转屏幕。想一想手机的自动旋转功能,当我们把屏幕横着放时,画面会对应做旋转,这里面也是通过sensor来识别当前的位置,根据位置做出画面的相应调整。

常用的传感器很多很多。温度传感器,压力传感器,靠近传感器,声音传感器,加速度传感器等等。
我们今天实现的只是很小的一类中的特定一个传感器而已。

驱动实例:

/*
 * Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifndef __LINUX_MMA7660_H
#define __LINUX_MMA7660_H

#include <linux/types.h>

#ifdef __KERNEL__

/* MMA7760 Registers */
#define MMA7660_XOUT            0x00    // 6-bit output value X
#define MMA7660_YOUT            0x01    // 6-bit output value Y
#define MMA7660_ZOUT            0x02    // 6-bit output value Z
#define MMA7660_TILT            0x03    // Tilt status
#define MMA7660_SRST            0x04    // Sampling Rate Status
#define MMA7660_SPCNT           0x05    // Sleep Count
#define MMA7660_INTSU           0x06    // Interrupt Setup
#define MMA7660_MODE            0x07    // Mode
#define MMA7660_SR              0x08    // Auto-Wake/Sleep and Debounce Filter
#define MMA7660_PDET            0x09    // Tap Detection
#define MMA7660_PD              0x0a    // Tap Debounce Count

struct mma7660_platform_data {
    /* eint connected to chip */
    int irq;

    /* parameters for input device */
    int poll_interval;
    int input_fuzz;
    int input_flat;
};

#endif /* __KERNEL__ */

#endif /* __LINUX_MMA7660_H */

这边因为是挂在IIC总线上,同样,IIC设备的实例化我们还是按照老方法来做:

static struct mma7660_platform_data mma7660_pdata = {
    .irq            = IRQ_EINT(11),
    .poll_interval  = 100,
    .input_fuzz     = 4,
    .input_flat     = 4,
};
static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
    { I2C_BOARD_INFO("24c08", 0x50), },  
    {
        I2C_BOARD_INFO("mma7660", 0x4c),
        .platform_data = &mma7660_pdata,
    },

};

同样,我们可以看到,在iic0上挂了两个设备,一个是24c08,一个是mma7660,因为slave addr的不同,我们可以访问到不同的从设备。
驱动代码:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/input-polldev.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mma7660.h>
#include <mach/hardware.h>



#define MMA7660_NAME                "mma7660"   

#define POLL_INTERVAL               100
#define INPUT_FUZZ                  4
#define INPUT_FLAT                  4


#define __need_retry(__v)   (__v & (1 << 6))
#define __is_negative(__v)  (__v & (1 << 5))

static const char *mma7660_bafro[] = {
    "Unknown", "Front", "Back"
};
static const char *mma7660_pola[] = {
    "Unknown",
    "Left", "Right",
    "Rsvd", "Rsvd",
    "Down", "Up",
    "Rsvd",
};

static const struct i2c_device_id mma7660_id[] =
{
    {"mma7660",0},
    {}
};

struct mma7660_dev
{
    int poll_interval;
    int input_fuzz;
    int input_flat;
    struct device       *hwmon_dev;     //hwmon dev
    struct input_polled_dev *ip_dev;    //input poll dev
    struct i2c_client   * mma_iic_client; //iic client
    struct mma7660_platform_data *pdata;  // platform data
};

static int                  last_tilt = 0;
static int                  oper_mode;

static struct input_dev *i_dev = NULL;
static struct mma7660_dev *mma7660_struct = NULL;

static int mma7660_read_tilt(struct i2c_client *client, int *tilt);

static void mma7660_worker(struct work_struct *work)
{
#if 0
    int bafro, pola, shake, tap;
#endif
    int val = 0;

    mma7660_read_tilt(mma7660_struct->mma_iic_client, &val);

#if 0
    bafro = val & 0x03;
    if(bafro != (last_tilt & 0x03)) 
    {
        printk("%s\n", mma7660_bafro[bafro]);
    }

    pola = (val >> 2) & 0x07;
    if(pola != ((last_tilt >> 2) & 0x07))
    {
        printk("%s\n", mma7660_pola[pola]);
    }

    shake = (val >> 5) & 0x01;
    if(shake && shake != ((last_tilt >> 5) & 0x01))
    {
        printk("Shake\n");
    }

    tap = (val >> 7) & 0x01;
    if(tap && tap != ((last_tilt >> 7) & 0x01))
    {
        printk("Tap\n");
    }
#endif
    /* Save current status */
    last_tilt = val;
}

DECLARE_WORK(mma7660_work, mma7660_worker);

static irqreturn_t mma7660_interrupt(int irq, void *dev_id) 
{
    schedule_work(&mma7660_work);
    return IRQ_HANDLED;
}

/*read the data from  reg TILT */
static int mma7660_read_tilt(struct i2c_client *client, int *tilt)
{
    int val;

    do 
    {
        val = i2c_smbus_read_byte_data(client, MMA7660_TILT);
        if(val < 0)
        {
            dev_err(&client->dev, "Read register %02x failed, %d\n",MMA7660_TILT, val);
            return -EIO;
        }
    }while(__need_retry(val));//when bit6 is 1,can't read value,so do this to wait the TILT reg ready to read

    *tilt = (val & 0xff);

    return 0;
}
static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz)
{
    int val;

    do 
    {
        val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT);
        if(val < 0) 
        {
            dev_err(&client->dev, "Read register %02x failed, %d\n",idx + MMA7660_XOUT, val);
            return -EIO;
        }
    }while(__need_retry(val));

    *xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f);

    return 0;
}


static int mma7660_initial(struct i2c_client *client)
{
    int val;

    //1.set mode to stand by mode first by set bit0 (mode bit)to 0
    i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
    mdelay(10);
    // 2.set mode to test mode,it should  set in stand by mode by set the bit 2(ton bit) to 1 
    i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);
    mdelay(10);
    //3.in test mode can set the reg MMA7660_XOUT,MMA7660_YOUT,MMA7660_ZOUT
    //3.1 clear the bit6(alert bit)
    i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);
    //3.2 clear the bit6(alert bit)
    i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x3f);
    //3.3 clear the bit6(alert bit)
    i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x3f);

    val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);

    if(val != 0x3f) 
    {
        printk("2.test write and read reg 0x02 but get wrong data: %d, no dev!!!\n",val);
        return -ENODEV;
    }

    //reset to stand by mode
    i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
    mdelay(10);


    //AMSR[2:0]bit0-bit2 :samples rate: 001----64 samples/sec  in Active and Auto-Sleep Mode
    //AWSR[1:0]:bit4-bit3: samples rate: 01-----16 Samples/Second  Auto-Wake Mode
    //bit7-bit5 :samples rate 001----- 3samples/sec TILT
    i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));

    //sleep count:160,when reach to the timeout time,will go in auto wake mode,ps
    i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);

    //Tap detection threshold is ±4 counts
    //X-axis,Y-axis,Z-axis  is enabled for tap detection
    i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);

    //Tap detection debounce filtering requires 16 adjacent tap detection tests to be the same to trigger a tap event and set the Tap
    //bit in the TILT (0x03) register, and optionally set an interrupt if PDINT is set in the INTSU (0x06) register. Tap detection
    //response time is nominally 1.04 ms.
    i2c_smbus_write_byte_data(client, MMA7660_PD, 15);

    /* Enable interrupt except exiting Auto-Sleep */
    //FBINT bit 0 ---1: Front/Back position change causes an interrupt
    //PLINT bit 1---1:Up/Down/Right/Left position change causes an interrupt
    //PDINT bit 2---1: Successful tap detection causes an interrupt
    //ASINT bit 3---0:  Exiting Auto-Sleep does not cause an interrupt
    //GINT bit4---0:There is not an automatic interrupt after everymeasurement
    //SHINTX bit 5---1:Shake detected on the X-axis causes an interrupt, and sets the Shake bit in the TILT register
    //SHINTY bit6---1: Shake detected on the Y-axis causes an interrupt, and sets the Shake bit in the TILT register
    //SHINTZ bit7---1: Shake detected on the Z-axis causes an interrupt, and sets the Shake bit in the TILT register.
    i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);

    /* IPP, Auto-wake, auto-sleep and standby */
    //Active mode: bit0 set to 1 ,bit 2 set o 0
    //  AWE bit3----1:Auto-Wake is enabled
    //ASE bit4----1: Auto-Sleep is enabled
    //SCPS bit5---0: The prescaler is divide-by-1. The 8-bit internal Sleep Counter input clock is the samples per second set by
    //AMSR[2:0], so the clock range is 120 Hz to 1 Hz depending on AMSR[2:0] setting. Sleep Counter timeout range is
    //256 times the prescaled clock
    //IPP bit6---1: Interrupt output INT is push-pull
    //IAH bit7---0: Interrupt output INT is active low
    i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);
    mdelay(10);

    /* Save current tilt status */
    mma7660_read_tilt(client, &last_tilt);

    return 0;
}


/* attr*/
static ssize_t mma7660_show_regs(struct device *dev,struct device_attribute *attr, char *buf)
{
    int reg, val;
    int i, len = 0;

    for(reg = 0; reg < 0x0b; reg++)
    {
        val = i2c_smbus_read_byte_data(mma7660_struct->mma_iic_client, reg);

        len += sprintf(buf + len, "REG: 0x%02x = 0x%02x ...... [ ", reg, val);
        for(i = 7; i >= 0; i--)
        {
            len += sprintf(buf + len, "%d", (val >> i) & 1);

            if((i % 4) == 0)
            {
                len += sprintf(buf + len, " ");
            }
        }
        len += sprintf(buf + len, "]\n");
    }

    return len;
}

static ssize_t mma7660_write_reg(struct device *dev,struct device_attribute *attr, const char *buf, size_t count)
{
    unsigned int reg, val;
    int ret;

    ret = sscanf(buf, "%x %x", &reg, &val);
    if(ret == 2)
    {
        if (reg >= 0 && reg <= 0x0a)
        {
            i2c_smbus_write_byte_data(mma7660_struct->mma_iic_client, reg, val);
            val = i2c_smbus_read_byte_data(mma7660_struct->mma_iic_client, reg);
            printk("REG: 0x%02x = 0x%02x\n", reg, val);
        }
    }

    return count;
}

static ssize_t mma7660_show_xyz_g(struct device *dev,struct device_attribute *attr, char *buf)
{
    int axis[3];
    int i;

    for (i = 0; i < 3; i++)
    {
        mma7660_read_xyz(mma7660_struct->mma_iic_client, i, &axis[i]);
    }

    return sprintf(buf, "%3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}

static ssize_t mma7660_show_axis_g(struct device *dev,struct device_attribute *attr, char *buf)
{
    int n = to_sensor_dev_attr(attr)->index;
    int val;

    mma7660_read_xyz(mma7660_struct->mma_iic_client, n, &val);

    return sprintf(buf, "%3d\n", val);
}

static ssize_t mma7660_show_tilt(struct device *dev,struct device_attribute *attr, char *buf)
{
    int val = 0, len = 0;

    mma7660_read_tilt(mma7660_struct->mma_iic_client, &val);

    len += sprintf(buf + len, "%s", mma7660_bafro[val & 0x03]);
    len += sprintf(buf + len, ", %s", mma7660_pola[(val >> 2) & 0x07]);

    if(val & (1 << 5))
    {
        len += sprintf(buf + len, ", Tap");
    }
    if(val & (1 << 7))
    {
        len += sprintf(buf + len, ", Shake");
    }
    len += sprintf(buf + len, "\n");

    return len;
}

static SENSOR_DEVICE_ATTR(registers, S_IRUGO | S_IWUGO,mma7660_show_regs, mma7660_write_reg, 0);
static SENSOR_DEVICE_ATTR(x_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 0);
static SENSOR_DEVICE_ATTR(y_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 1);
static SENSOR_DEVICE_ATTR(z_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 2);
static SENSOR_DEVICE_ATTR(all_axis_g, S_IRUGO, mma7660_show_xyz_g, NULL, 0);
static SENSOR_DEVICE_ATTR(tilt_status, S_IRUGO, mma7660_show_tilt, NULL, 0);

static struct attribute* mma7660_attrs[] = 
{
    &sensor_dev_attr_registers.dev_attr.attr,
    &sensor_dev_attr_x_axis_g.dev_attr.attr,
    &sensor_dev_attr_y_axis_g.dev_attr.attr,
    &sensor_dev_attr_z_axis_g.dev_attr.attr,
    &sensor_dev_attr_all_axis_g.dev_attr.attr,
    &sensor_dev_attr_tilt_status.dev_attr.attr,
    NULL
};

static const struct attribute_group mma7660_group = {
    .attrs      = mma7660_attrs,
};
//Input interfaces
static void mma7660_report_abs(void)
{
    int axis[3];
    int i;

    for(i = 0; i < 3; i++)
    {
        mma7660_read_xyz(mma7660_struct->mma_iic_client, i, &axis[i]);
    }

    input_report_abs(mma7660_struct->ip_dev->input, ABS_X, axis[0]);
    input_report_abs(mma7660_struct->ip_dev->input, ABS_Y, axis[1]);
    input_report_abs(mma7660_struct->ip_dev->input, ABS_Z, axis[2]);
    input_sync(mma7660_struct->ip_dev->input);

    //printk("3-Axis ... %3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}

static void mma7660_dev_poll(struct input_polled_dev *dev)
{
    mma7660_report_abs();
}

/*****int (*probe)(struct i2c_client *, const struct i2c_device_id *)*****/

static int mma7660_probe(struct i2c_client *uc_i2c_client, const struct i2c_device_id * uc_i2c_id_table)
{
    int err=0;
    //check iic
    if(!i2c_check_functionality(uc_i2c_client->adapter,I2C_FUNC_I2C)) 
    {
        err = -ENODEV;
        goto FAIL_CHECK_FUNC;
    }

    //check platdata
    if(!uc_i2c_client->dev.platform_data)
    {   
        err = -ENODATA;
        goto FAIL_NO_PLATFORM_DATA;
    }

    //allooc buf  
    mma7660_struct = kzalloc(sizeof(struct mma7660_dev),GFP_KERNEL);
    if(!mma7660_struct)
    {
        err=-ENOMEM;
        goto FAIL_KZALLOC;
    }
    //initial start
    mma7660_struct->mma_iic_client = uc_i2c_client;
    mma7660_struct->pdata = uc_i2c_client->dev.platform_data;

    mma7660_struct->poll_interval =((mma7660_struct->pdata->poll_interval >0) ? mma7660_struct->pdata->poll_interval : POLL_INTERVAL);
    mma7660_struct->input_flat =((mma7660_struct->pdata->input_flat >0) ? mma7660_struct->pdata->input_flat : INPUT_FLAT);
    mma7660_struct->input_fuzz =((mma7660_struct->pdata->input_fuzz >0) ? mma7660_struct->pdata->input_fuzz : INPUT_FUZZ);

    if(mma7660_struct->pdata->irq)
    {
        mma7660_struct->mma_iic_client->irq = mma7660_struct->pdata->irq;
    }
    else
    {
        err = -ENODATA;
        printk("3.the platformdata no irq data\n");
        goto FAIL_NO_IRQ_DATA;
    }
    //initial the dev
    err = mma7660_initial(mma7660_struct->mma_iic_client);
    if(err < 0)
    {
        goto FAIL_MMA7660_INIT;
    }

    err = sysfs_create_group(&mma7660_struct->mma_iic_client->dev.kobj, &mma7660_group);
    if(err)
    {
        printk("4.create sysfs group failed!\n");
        goto FAIL_CREATE_GROUP;
    }

    // register to hwmon device 
    mma7660_struct->hwmon_dev = hwmon_device_register(&mma7660_struct->mma_iic_client->dev);
    if(IS_ERR(mma7660_struct->hwmon_dev))
    {
        err = PTR_ERR(mma7660_struct->hwmon_dev);
        printk("5.hwmon register failed!\n");
        goto FAIL_HWMON_REGISTER;
    }

    //alloc the input poll device  
    mma7660_struct->ip_dev = input_allocate_polled_device();
    if(!mma7660_struct->ip_dev)
    {
        err = -ENOMEM;
        printk("6.alloc poll device failed!\n");
        goto FAIL_ALLLOC_INPUT_PDEV;
    }

    mma7660_struct->ip_dev->poll = mma7660_dev_poll;
    mma7660_struct->ip_dev->poll_interval =mma7660_struct->pdata->poll_interval;

    i_dev = mma7660_struct->ip_dev->input;

    i_dev->name = MMA7660_NAME;
    i_dev->id.bustype = BUS_I2C;
    i_dev->id.vendor = 0x12FA;
    i_dev->id.product = 0x7660;
    i_dev->id.version = 0x0100;
    i_dev->dev.parent = &mma7660_struct->mma_iic_client->dev; 

    set_bit(EV_ABS, i_dev->evbit);

    set_bit(ABS_X, i_dev->absbit);
    set_bit(ABS_Y, i_dev->absbit);
    set_bit(ABS_Z, i_dev->absbit);

    input_set_abs_params(i_dev, ABS_X, -512, 512, mma7660_struct->input_fuzz, mma7660_struct->input_flat);
    input_set_abs_params(i_dev, ABS_Y, -512, 512, mma7660_struct->input_fuzz, mma7660_struct->input_flat);
    input_set_abs_params(i_dev, ABS_Z, -512, 512, mma7660_struct->input_fuzz, mma7660_struct->input_flat);

    err = input_register_polled_device(mma7660_struct->ip_dev);
    if(err)
    {       
        printk("7.register poll device failed!");
        goto FAIL_INPUT_REGISTER_PDEV;
    }

    //register interrupt handle 
    err = request_irq(mma7660_struct->pdata->irq, mma7660_interrupt,IRQF_TRIGGER_FALLING, MMA7660_NAME, NULL);
    if(err)
    {
        printk("8.request irq failed!\n");
        goto FAIL_REQUEST_IRQ;
    }

    printk("mma7660 driver probe success!\n");

    return 0;

FAIL_REQUEST_IRQ:
    input_unregister_polled_device(mma7660_struct->ip_dev);
FAIL_INPUT_REGISTER_PDEV:
    input_free_polled_device(mma7660_struct->ip_dev);
FAIL_ALLLOC_INPUT_PDEV:
    hwmon_device_unregister(mma7660_struct->hwmon_dev);
FAIL_HWMON_REGISTER:
    sysfs_remove_group(&mma7660_struct->mma_iic_client->dev.kobj,&mma7660_group);
FAIL_CREATE_GROUP:
FAIL_MMA7660_INIT:
FAIL_NO_IRQ_DATA:
    kfree(mma7660_struct);
FAIL_KZALLOC:
FAIL_NO_PLATFORM_DATA:
FAIL_CHECK_FUNC:

    return err;

}
/*****int (*remove)(struct i2c_client *)*****/
static int mma7660_remove(struct i2c_client *uc_i2c_client)
{
    disable_irq(mma7660_struct->pdata->irq);

    free_irq(mma7660_struct->pdata->irq,NULL);

    input_unregister_polled_device(mma7660_struct->ip_dev);
    input_free_polled_device(mma7660_struct->ip_dev);
    hwmon_device_unregister(mma7660_struct->hwmon_dev);
    sysfs_remove_group(&mma7660_struct->mma_iic_client->dev.kobj,&mma7660_group);

    return 0;
}
static int mma7660_suspend(struct i2c_client *client, pm_message_t state)
{
    int ret;

    oper_mode = i2c_smbus_read_byte_data(client, MMA7660_MODE);

    ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, 0);
    if(ret)
    {
        printk("%s: set mode (0) for suspend failed, ret = %d\n",MMA7660_NAME, ret);
    }

    return 0;
}

static int mma7660_resume(struct i2c_client *client)
{
    int ret;

    ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, oper_mode);
    if (ret) 
    {
        printk("%s: set mode (%d) for resume failed, ret = %d\n",MMA7660_NAME, oper_mode, ret);
    }
    return 0;
}

static struct i2c_driver mma7660_drv =
{
    .driver =
    {
        .name = MMA7660_NAME,   
        .owner= THIS_MODULE,
    },
    .probe   = mma7660_probe,
    .remove  = mma7660_remove,
    .suspend = mma7660_suspend,
    .resume  = mma7660_resume,
    //match use
    .id_table = mma7660_id,
};

int __init  mma7660_init(void)
{
    i2c_add_driver(&mma7660_drv);   
    return 0;
}

void __exit mma7660_exit(void)
{
    i2c_del_driver(&mma7660_drv);
}






module_init(mma7660_init);
module_exit(mma7660_exit);
MODULE_LICENSE("GPL");

整个驱动最难理解的地方应该还是在probe函数里面。当初始化完成以后,大部分的函数都是在做读操作而已。我们可以根据芯片手册来了解它的配置。同时比较难理解的是它本次采用的驱动框架。其他没什么难的,和之气的输入子系统都是一样的。

实验现象:

作者:changliang7731 发表于2016/11/15 19:56:36 原文链接
阅读:8 评论:0 查看评论

Dialog自定义布局展示

$
0
0

一、Dialog布局实现反馈


1,布局文件 dialog.xml


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="266dp"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:gravity="center_horizontal">

    <ScrollView
        android:id="@+id/scroll_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="266dp"
            android:layout_alignParentBottom="true"
            android:background="@color/page_bg_color"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="15dp"
                android:gravity="center"
                android:text="@string/feed_back_title"
                android:textColor="@color/comm_dlg_text_color"
                android:textSize="15sp" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="3dp"
                android:gravity="center"
                android:orientation="horizontal">

                <RadioButton
                    android:id="@+id/feed_back_happy_bt"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@null"
                    android:button="@null"
                    android:drawablePadding="12dp"
                    android:drawableTop="@drawable/feed_back_happy"
                    android:orientation="vertical"
                    android:paddingTop="12sp"
                    android:text="@string/feed_back_happy"
                    android:textColor="@color/feed_back_selector" />

                <RadioButton
                    android:id="@+id/feed_back_normal_bt"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="55dp"
                    android:layout_marginRight="55dp"
                    android:background="@null"
                    android:button="@null"
                    android:drawablePadding="12dp"
                    android:drawableTop="@drawable/feed_back_normal"
                    android:orientation="vertical"
                    android:paddingTop="12dp"
                    android:text="@string/feed_back_normal"
                    android:textColor="@color/feed_back_selector" />

                <RadioButton
                    android:id="@+id/feed_back_sad_bt"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@null"
                    android:button="@null"
                    android:drawablePadding="12sp"
                    android:drawableTop="@drawable/feed_back_sad"
                    android:orientation="vertical"
                    android:paddingTop="12sp"
                    android:text="@string/feed_back_sad"
                    android:textColor="@color/feed_back_selector" />

            </LinearLayout>

            <FrameLayout
                android:layout_width="match_parent"
                android:layout_height="67dp"
                android:layout_marginTop="14dp">

                <EditText
                    android:id="@+id/feed_back_saying"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="30dp"
                    android:layout_marginRight="30dp"
                    android:background="@color/edit_text_bg_color"
                    android:gravity="top|left"
                    android:hint="@string/feed_back_desc"
                    android:paddingLeft="10dp"
                    android:paddingRight="10dp"
                    android:paddingTop="3dp"
                    android:textColorHint="@color/input_hint_color"
                    android:textSize="15dp" />

                <LinearLayout
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="end|bottom"
                    android:layout_marginBottom="9dp"
                    android:layout_marginRight="30dp"
                    android:orientation="horizontal">

                    <TextView
                        android:id="@+id/feed_back_desc_limit"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="0"
                        android:textColor="@color/input_hint_color" />

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:paddingRight="5dp"
                        android:text="/20"
                        android:textColor="@color/input_hint_color" />

                </LinearLayout>
            </FrameLayout>

            <Button
                android:id="@+id/feed_back_submit"
                android:layout_width="match_parent"
                android:layout_height="54dp"
                android:layout_marginTop="10dp"
                android:background="@color/white"
                android:enabled="false"
                android:text="@string/feed_back_submit"
                android:textColor="@color/submit_selector" />

        </LinearLayout>
    </ScrollView>
</RelativeLayout>


2,主类实现 MainActivity.java


public void showDialog(View view1) {
        LayoutInflater inflater = LayoutInflater.from(MainActivity.this);
        View view = inflater.inflate(R.layout.dialog, null);
        final Dialog dialog = new Dialog(MainActivity.this, R.style.dialog_theme);
        // 设置宽度为屏宽, 靠近屏幕底部。
        final Window window = dialog.getWindow();
        window.setWindowAnimations(R.style.dialog_theme);
        final WindowManager.LayoutParams lp = window.getAttributes();
        lp.gravity = Gravity.BOTTOM; // 紧贴底部
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        window.setAttributes(lp);

        final ScrollView scrollView = (ScrollView) view.findViewById(R.id.scroll_view);
        final RadioButton happyBt = (RadioButton) view.findViewById(R.id.feed_back_happy_bt);
        final RadioButton normalBt = (RadioButton) view.findViewById(R.id.feed_back_normal_bt);
        final RadioButton sadBt = (RadioButton) view.findViewById(R.id.feed_back_sad_bt);
        final Button submitBt = (Button) view.findViewById(R.id.feed_back_submit);
        happyBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                happyBt.setChecked(true);
                normalBt.setChecked(false);
                sadBt.setChecked(false);
                state = 2;

                submitBt.setEnabled(true);
            }
        });
        normalBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                happyBt.setChecked(false);
                normalBt.setChecked(true);
                sadBt.setChecked(false);
                state = 1;
                submitBt.setEnabled(true);
            }
        });
        sadBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                happyBt.setChecked(false);
                normalBt.setChecked(false);
                sadBt.setChecked(true);
                state = 0;
                submitBt.setEnabled(true);
            }
        });

        final TextView feedBackLimitTv = (TextView) view.findViewById(R.id.feed_back_desc_limit);
        final EditText feedBackEt = (EditText) view.findViewById(R.id.feed_back_saying);
        feedBackEt.postDelayed(new Runnable() {
            @Override
            public void run() {
                InputMethodManager imm = (InputMethodManager) feedBackEt.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.showSoftInput(feedBackEt, 0);
            }
        }, 200);
        feedBackEt.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                String desc = feedBackEt.getText().toString();
                if (desc.equalsIgnoreCase("")) {
                    feedBackLimitTv.setText("0");
                } else {
                    if (desc.length() > 20) {
                        desc = desc.substring(0, 20);
                        feedBackEt.setText(desc);
                        feedBackEt.setSelection(20);
                        feedBackLimitTv.setText("20");
                        Toast.makeText(MainActivity.this, R.string.feed_back_desc_limit, Toast.LENGTH_LONG).show();
                        return;
                    }
                    feedBackLimitTv.setText(desc.length() + "");
                }
            }
        });
        submitBt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (NetworkUtil.isNetworkAvailable(MainActivity.this)) {
                    /**
                     * 网络请求
                     */
                    String descFeedBack = feedBackEt.getText().toString();
                    Toast.makeText(MainActivity.this, descFeedBack, Toast.LENGTH_LONG).show();
                    dialog.dismiss();
                } else {
                    Toast.makeText(MainActivity.this, R.string.network_bad, Toast.LENGTH_LONG).show();
                }
            }
        });
//        dialog.addContentView(view, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
        dialog.addContentView(view, new RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT));
        dialog.show();
        dialog.setCanceledOnTouchOutside(true);
    }


3,展示效果




二、细节优化


1,view宽度


(1)

        // 设置宽度为屏宽, 靠近屏幕底部。
        final Window window = dialog.getWindow();
        window.setWindowAnimations(R.style.dialog_theme);
        final WindowManager.LayoutParams lp = window.getAttributes();
        lp.gravity = Gravity.BOTTOM; // 紧贴底部
        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
        window.setAttributes(lp);

设置展示出的控件与物理硬件边框没有距离。

(2)

//        dialog.addContentView(view, new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT));
        dialog.addContentView(view, new RelativeLayout.LayoutParams(width, RelativeLayout.LayoutParams.WRAP_CONTENT));
使用第一种方式,会出现控件左右两边离边框依旧有距离。使用第二种方式,将物理硬件的宽度设置为控件的宽度,实现控件填充整个布局。

2,软键盘位置


在EditText获取焦点时,软件盘会默认弹出。

        feedBackEt.postDelayed(new Runnable() {
            @Override
            public void run() {
                InputMethodManager imm = (InputMethodManager) feedBackEt.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.showSoftInput(feedBackEt, 0);
            }
        }, 200);
用于设置控件弹出时,软件盘自动弹出。优化用户操作。


在整体布局外面嵌套ScrollView。在没有嵌套时,Edittext获取焦点,软件盘弹出,会覆盖位于Edittext下部的Button布局。添加布局后,软件盘位置会在整个布局的下部,更符合实际使用的情形。


3,实际效果优化


为EditText添加内容输入监听,便于直观反应当前控件的限制,属于更合理的交互体验。

在研发的路上,最开始,只是基于Android现有的控件,配合基本的业务流程,实现业务。自我追求中,控件的更优化处理与使用;APP的性能与稳定;APP的大小及应用程序框架都是接下来的发展方向。


源码





心中依旧还有一些担忧,对于当前的技术,总是只是实现基本的功能,对于其对应技术的深层,依旧挖掘的不是很深。甚至于,在某些方面,当前实现了,在新的需求下,新的bug,自己或许又会无所适从。对于自己的信心,还是差了很大一截。未来的路,一步步去走~~~

打破自己!

什么时候,或许也可以考虑给自己先一点鼓励~    ~_~ ~_~

作者:u013205623 发表于2016/11/15 19:57:33 原文链接
阅读:39 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>