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

Android:IPC,Messenger,AIDL

$
0
0

简介

IPC,即进程间通信。常见的IPC场景有两种,一种是单个应用开启多个进程,这些进程间需要通信;另外一种是不同应用间的进程间通信。

单个应用开启多个进程并不复杂,只需要为四大组件声明一个android:process属性,这个组件便会运行在该声明的进程上。而这个属性的声明方式有两种:

  1. 以:号开头,比如android:process=":remote",这时这个组件便运行在package_name:remote这个进程上。这种方式称为私有进程,虽然名为私有进程,但是可以为其再声明一个android:exported="true"属性,然后增加intent-filter属性(如果没有intent-filter属性,只能在本应用内使用),其他应用也可以关联到这个进程。

  2. 以单个.号分割,比如android:process="com.leelit",这时这个组件便会运行在com.leelit这个进程上。这种方式称为全局进程,假设有多个应用都有声明了com.leelit全局进程的组件,也会产生多个名字相同但PID并不相同的进程,所以全局进程并非全局唯一的意思。全局进程的作用是,其他应用可以通过ShareUID的方式和这个全局进程跑在同一个进程上,从而共享资源。

IPC的基础是数据结构的序列化与反序列化。Java平台简单地实现Serializable,通过ObjectOutputStream以及ObjectInputStream即可完成对象的序列与反序列化。而Android平台上新增了一种效率更高的方式,Parcelable,其实现方式比较固定,所以也有一些插件可以直接生成代码。如果说Serializable和Parcelable是实现IPC的“原料”,那么Binder则可以视为“桥梁”,Binder是客户端和服务端进行通信的媒介,当bindService时,客户端可以得到一个可以“操纵”服务端的Binder对象。

Android平台上实现IPC的方式有很多种,比如常见的:

  • Intent和bundle
  • 文件共享
  • Messenger
  • AIDL
  • ContentProvider
  • Socket

这篇文章主要关注:Messenger和AIDL


Messenger

Messenger是系统为我们封装好的一套IPC方案,它的底层是基于AIDL与Handler。如果了解Handler,Looper那一套东西就会知道,Handler每次只会处理一个Message,使用Messenger是不需要也无法考虑并发的情况的。

Messenger的构造方法有两个:

    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }

由构造方法也能看出它基于AIDL和Handler。

使用套路:

1、Service服务端实例一个Messenger来接收客户端Messenger发来的message,onBind方法返回这个Messenger的Binder。如果有需要还可以通过客户端发来的message携带的客户端Messenger接收对象,返回message给客户端。

2、客户端bind到服务端的Service后,实例一个Messenger对象,便可以通过这个Messenger对象发送message到服务端。如果有需要还可以实例另外一个Messenger对象来接收服务端返回的message。

按照这个套路一共需要实例三个Messenger,客户端两个,一个用于发送给服务端,一个用于接收服务端;服务端一个用于接收客户端,而返回给客户端的Messenger在客户端发来的message中携带。

服务端进程:

public class MessengerService extends Service {

    private static final String Messenger_TAG = "Messenger";

    private static class ServerMessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    // 接收到客户端的信息
                    Log.i(Messenger_TAG, msg.getData().getString("client-data") + " " + Thread.currentThread().toString());

                    // 接收服务端返回信息的客户端Messenger
                    Messenger replyMessenger = msg.replyTo;

                    // 返回信息给客户端
                    Message message = Message.obtain();
                    message.what = 1;
                    Bundle data = new Bundle();
                    data.putString("server-data", "hello client");
                    message.setData(data);
                    try {
                        replyMessenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    // 服务端Messenger
    private Messenger serverMessenger = new Messenger(new ServerMessengerHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // 返回两个Messenger沟通的Binder桥梁
        return serverMessenger.getBinder();
    }

}

客户端进程:

public class MainActivity extends AppCompatActivity {
    private static final String Messenger_TAG = "Messenger";

    private Messenger clientMessenger;

    private Button button;

    // 接收服务端返回信息的Messenger
    private Messenger replyMessenger = new Messenger(new ClientMessengerHandler());

    private static class ClientMessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    // 接收到服务端的信息
                    Log.i(Messenger_TAG, msg.getData().getString("server-data") + " " + Thread.currentThread().toString());
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 客户端Messenger
            clientMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.messenger);

        // bind服务端Service
        Intent intent = new Intent(this, MessengerService.class);
        bindService(intent, serviceConnection, BIND_AUTO_CREATE);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发送消息到服务端
                Message message = Message.obtain();
                message.what = 0;
                Bundle data = new Bundle();
                data.putString("client-data", "hello server");
                message.setData(data);

                // 指定接收服务端返回信息的Messenger
                message.replyTo = replyMessenger;

                try {
                    clientMessenger.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}

点击Button后打印的信息如下:

10-21 21:44:28.557 29650-29650/com.example.kenjxli.ipc:messenger I/Messenger: hello server Thread[main,5,main]
10-21 21:44:28.567 29615-29615/com.example.kenjxli.ipc I/Messenger: hello client Thread[main,5,main]

处理的线程是Handler实例时所在的线程,所以两个进程的Message处理都是在主线程。

这里补充两点bindService相关的内容:

  1. bindService()是异步调用,会立即返回;
  2. 系统会在与服务连接上时回调onServiceConnected()方法,并传递服务的onBind() 方法返回的 IBinder;与服务的连接意外中断时(例如当服务崩溃或被终止时)回调onServiceDisconnected()方法。当客户端主动取消绑定时,系统“绝对不会”调用该方法。

AIDL

根据官方文档,可以看出,底层AIDL和经过封装的Messenger相比,最大的不同就是,AIDL是具备多线程处理能力的。

Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。

使用AIDL的基本步骤:
1、确定服务端提供的服务,定义AIDL接口;
2、编写服务端Service,onBind方法返回Binder;
3、客户端bindService,在回调处将Binder转化为AIDL接口,后续即可使用这个接口调用服务端的方法。
可以参考之前的一篇文章,链接

这里再补充几个内容。

  1. AIDL传递对象;
  2. 服务端回调客户端;
  3. 调用时同步异步状态及所在线程;

AIDL传递对象

如果使用AIDL传递基本数据类型,就比较简单,只需要服务端定义好AIDL接口文件,并在Service中实现AIDL.Stub接口,返回binder。客户端bindService后将binder重新转化为AIDL接口,即可IPC调用。但是要传递对象,就需要额外的功夫。

需要用AIDL传递对象时,除了实际使用的AIDL接口之外,还需要该对象的类实现Parcelable接口,在同一个包内声明该类的AIDL文件,并且AIDL接口中必须import该类,即便处在同一个包。

比如服务端AIDL接口,需要返回一个User对象。

interface MyAidl {
    User getUser();
}

首先需要创建一个实现Parcelable的User类;
然后在同一个包,新建一个AIDL文件;

// User.aidl
package com.example.kenjxli.ipc.aidl;

parcelable User;

最后需要在AIDL接口处import该类,就算处在同一个包也必须import。

// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User; //必须import

interface MyAidl {
    User getUser();
}

以上步骤缺一不可,否则编译都将失败。

另外,AIDL接口中的方法有一些非基本类型的参数,需要指定其“方向”。

All non-primitive parameters require a directional tag indicating which way the data goes. Either in, out, or inout (see the example below).
Primitives are in by default, and cannot be otherwise.

假设有一个AIDL接口如下:

interface MyAidl {
    void inUser(in User user);
    void outUser(out User user);
    void inOutUser(inout User user);
}

则三个参数的含义如下:

in:客户端输入参数,此时服务端可以得到这个参数对象的内容,但是修改后无法同步回给客户端。
out:客户端输入参数,即便这个对象是有内容的,到了服务端也会变成空值,服务端修改后能同步回给客户端。
inout:则是上面两者的集合,服务端既能得到客户端对象的内容,修改后也能同步回给客户端。

服务端回调

AIDL一般是客户端进程调用服务端进程,但是有些时候是可能需要服务端回调客户端,这时需要使用一个系统接口,RemoteCallbackList<E extends IInterface>

具体使用步骤如下:
1、定义一个客户端AIDL回调接口
2、在原有的AIDL接口增添注册回调接口的方法
3、客户端注册时,将该接口添加到callbackList中;
4、回调时使用固定的代码格式。

部分代码如下:
1、定义一个AIDL回调接口

// OnServerCallBack.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User;
// Declare any non-default types here with import statements

interface OnServerCallBack {
    void onServerCallBack(in User user); // 此时我们的客户端相当于是服务端的服务端了!
}

2、原有AIDL接口增添注册回调的方法

// MyAidl.aidl
package com.example.kenjxli.ipc.aidl;

import com.example.kenjxli.ipc.aidl.User;
import com.example.kenjxli.ipc.aidl.OnServerCallBack;
// Declare any non-default types here with import statements

interface MyAidl {
    User getUser();
    void inUser(in User user);
    void outUser(out User user);
    void inOutUser(inout User user);
    void registerCallBack(in OnServerCallBack callbcak);
}

并在服务端Service中的AIDL.Stub中实现该方法

@Override
        public void registerCallBack(final OnServerCallBack callbcak) throws RemoteException {
        // 这里可以添加一个cookie对象,方便回调时识别是哪个回调对象
            callbackList.register(callbcak, "cookie1");
        }

3、客户端注册

  myAidl.registerCallBack(new OnServerCallBack.Stub() {
                        @Override
                        public void onServerCallBack(User user) throws RemoteException {
                            Log.e("tag", "call back " + user.toString());
                        }

                    });

4、服务端回调代码

private void callback() {
        int size = callbackList.beginBroadcast();
        for (int i = 0; i < size; i++) {
            OnServerCallBack callBack = callbackList.getBroadcastItem(i);
            if (callBack != null) {
                try {
                    Log.e("tag", (String) callbackList.getBroadcastCookie(i));
                    callBack.onServerCallBack(new User("callback user", 10000));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        callbackList.finishBroadcast();
    }

这样就是一个完整的服务端回调客户端的流程了。上面没有涉及到反注册,其实也是大同小异的!值得一提的是,如果客户端进程退出后,服务端会自动移除这个回调对象。


调用时同步异步状态及所在线程

这一小节直接说明结论:

1、ServiceConnection的回调线程始终是在主线程;
2、AIDL调用是在Binder线程池,不管是客户端调用服务端,还是服务端回调客户端,方法体执行的地方都是在Binder线程池中;
3、AIDL调用是同步调用,不管是客户端调用服务端,还是服务端回调客户端。当调用某个远程方法后,本地进程当前的线程会挂起,直到远程方法返回后,本地进程的线程才能唤醒。

作者:leelit 发表于2016/10/22 20:46:42 原文链接
阅读:54 评论:0 查看评论

JAVA进阶案例 TCP编程之网络聊天工具(服务端)

$
0
0

实现分析、

1.开启服务端

客户端选择‘登录以后’后,提示输入用户名和密码,验证成功则进入好友列表界面

2.用户聊天

双击好友,进入好友聊天界面。在信息框编辑信息 点击发送 

当客户端向服务端发送数据时  根据指令的不同来处理不同的业务。

如果是登录,服务端向数据库查询用户信息。然后把处理后的信息发送给服务端。


如果是聊天业务,首先判断当前好友是否在线

如果好友不在线,处理后的信息发送给发送者,如果好友在线 处理后的信息发送给接收者(朋友)


服务端界面:


简单介绍一下 服务端的运行流程

当点击开启服务后 新建一个线程开启服务器循环监听客户端的连接

try {
			ServerSocket ss = new ServerSocket(4096);
			Socket socket = null;
			// 循环监听客户端的连接 每连接一个客户端 就为其实例化一个线程
			// 在该线程 I/O阻塞监听客户端发送的信息 不然你只能收到一次信息~
			while (true) {
				socket = ss.accept();
				ServiceThread thread = new ServiceThread(socket);
				thread.start();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

至于为何要新建线程开启服务,归结于while循环里面的ss.accept();  如果一直没接收到客户端的连接,会导致线程阻塞。

如果你使用的还是main线程,那么关闭服务会无法点击。

而为了实现多客户端的连接  所以在上面我们为每一个客户端都开启一个线程。

线程的run方法为

	@Override
	public void run() {
		ObjectInputStream ois = null;
		ObjectOutputStream oos = null;
		// 时刻监听 客户端发送来的数据
		while (socket != null) {
			try {
				ois = new ObjectInputStream(socket.getInputStream());
				CommandTranser msg = (CommandTranser) ois.readObject();
				// 处理客户端发送来的信息
				msg = execute(msg);
				if ("message".equals(msg.getCmd())) {
					/*
					 * 如果 msg.ifFlag即 服务器处理成功 可以向朋友发送信息 如果服务器处理信息失败 信息发送给发送者本人
					 */
					if (msg.isFlag()) {
						oos = new ObjectOutputStream(SocketList.getSocket(
								msg.getReceiver()).getOutputStream());
					} else {
						oos = new ObjectOutputStream(socket.getOutputStream());
					}
				}
				// 如果是登录请求 发送给发送者本人
				if ("login".equals(msg.getCmd())) {
					oos = new ObjectOutputStream(socket.getOutputStream());
				}
				oos.writeObject(msg);
			} catch (IOException e) {
				socket = null;
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

	}
也是一个while循环  I/O阻塞接收客户端的信息。因为你不能只接收客户端一次的信息吧  

如果你发现你只能接受一次信息  问题就出在这里了   因为我也在这里错了~~~~~~
在这里出现了CommandTranser类,execute(CommandTranser msg)方法和SocketList类里面的静态方法getSocket

SocketList类很简单  主要是保存已经和服务器成功连接的客户端

package cn.edu.xynu.util;

import java.net.Socket;
import java.util.HashMap;

import cn.edu.xynu.entity.SocketThread;



/**
 * @author scx
 *	所有已经成功登录服务器的socket和昵称
 */
public class SocketList {
	private static HashMap<String, Socket> map=new HashMap<String, Socket>();
	//将SocketThread入集合
	public static void addSocket(SocketThread socketThread){
		map.put(socketThread.getName(), socketThread.getSocket());
	}
	//通过昵称返回socket
	public static Socket getSocket(String name){
		return map.get(name);
	}
}
SocketThread实体类

所有成功连接客户端的socket实体类 包括一个socket (客户端)和昵称

package cn.edu.xynu.entity;

import java.net.Socket;

/**
 * @author scx 所有成功连接客户端的socket实体类 包括一个socket 一个昵称
 */
public class SocketThread {
	private Socket socket;
	private String name;

	public SocketThread() {
		super();
		// TODO Auto-generated constructor stub
	}

	public SocketThread(Socket socket, String name) {
		super();
		this.socket = socket;
		this.name = name;
	}

	public Socket getSocket() {
		return socket;
	}

	public void setSocket(Socket socket) {
		this.socket = socket;
	}

	public String getName() {
		return name;
	}

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

}

CommandTranser这个类主要负责服务端和客户端的通信数据。

具体代码及注释如下:

package cn.edu.xynu.util;

import java.io.Serializable;

/**
 * @author scx
 *	客户端与服务器交互的数据
 */
public class CommandTranser implements Serializable{
	private static final long serialVersionUID = 1L;
	private String sender=null;//发送者
	private String receiver=null;//接受者
	private Object data=null;//传递的数据
	private boolean flag=false;//指令的处理结果
	private String cmd=null;//服务端要做的指令
	private String result=null;//处理结果
	
	public String getSender() {
		return sender;
	}
	public void setSender(String sender) {
		this.sender = sender;
	}
	public String getReceiver() {
		return receiver;
	}
	public void setReceiver(String receiver) {
		this.receiver = receiver;
	}
	public Object getData() {
		return data;
	}
	public void setData(Object data) {
		this.data = data;
	}
	public boolean isFlag() {
		return flag;
	}
	public void setFlag(boolean flag) {
		this.flag = flag;
	}
	public String getResult() {
		return result;
	}
	public void setResult(String result) {
		this.result = result;
	}
	public String getCmd() {
		return cmd;
	}
	public void setCmd(String cmd) {
		this.cmd = cmd;
	}
	
}
execute(CommandTranser msg)方法主要处理客户端向服务端发送的数据

由于我们只实现了两个功能  即登录 和聊天 

所以execute方法主要代码如下

// 如果是登录请求
		if ("login".equals(msg.getCmd())) {
			UserService userService = new UserService();
			User user = (User) msg.getData();
			msg.setFlag(userService.checkUser(user));
			/*
			 * 如果登陆成功,将该客户端加入已经连接成功的map集合里面 并且开启此用户的接受线程
			 */
			if (msg.isFlag()) {
				// 将该线程加入连接成功的map集合
				SocketThread socketThread = new SocketThread();
				socketThread.setName(msg.getSender());
				socketThread.setSocket(socket);
				SocketList.addSocket(socketThread);
				msg.setResult("登陆成功");
			} else {
				msg.setResult("登录失败");
			}
		}

		// 如果是发送消息的指令 判断当前用户是否在线

		if ("message".equals(msg.getCmd())) {
			// 如果要发送的用户在线 发送信息
			if (SocketList.getSocket(msg.getReceiver()) != null) {
				msg.setFlag(true);
			} else {
				msg.setFlag(false);
				msg.setResult("当前用户不在线");
			}
		}
		return msg;
	}

当用户为login请求时,通过userservice查询数据库中是否有此用户,并返回结果

package cn.edu.xynu.service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import cn.edu.xynu.entity.User;
import cn.edu.xynu.util.DBHelper;

/**
 * @author scx 客户的业务处理 只查询用户是否在数据库中存在
 */
public class UserService {
	public boolean checkUser(User user) {
		PreparedStatement stmt = null;
		Connection conn = null;
		ResultSet rs = null;
		conn = DBHelper.getConnection();
		String sql = "select * from user where username=? and password =?";
		try {
			stmt = conn.prepareStatement(sql);
			stmt.setString(1, user.getUsername());
			stmt.setString(2, user.getPassword());
			rs = stmt.executeQuery();
			if (rs.next()) {
				return true;
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				if (rs != null)
					rs.close();
				if (stmt != null)
					stmt.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return false;
	}
}
User类用户的实体类,只包括帐号 密码信息

package cn.edu.xynu.entity;

import java.io.Serializable;

/**
 * @author scx
 *	用户信息的实体类
 */
public class User implements Serializable{
	private static final long serialVersionUID = 1L;
	private String username;
	private String password;
	
	public User() {
		super();
	}

	public User(String username, String password) {
		super();
		this.username = username;
		this.password = password;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
	
}

而DBhelpher为连接数据库工具类

package cn.edu.xynu.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @author scx
 *	数据库
 */
public class DBHelper {
	private static final String driver="com.mysql.jdbc.Driver";
	private static final String url="jdbc:mysql://localhost:3306/myqquser?useUnicode=true&charcterEncoding=UTF-8";
	private static final String username="root";
	private static final String password="";
	private static  Connection con=null;
	//静态块代码负责加载驱动
	static
	{
		try {
			Class.forName(driver);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	public static Connection getConnection(){

		if(con==null){
			try {
				con=DriverManager.getConnection(url, username, password);
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return con;
	}
}	
下一篇  
JAVA进阶案例 TCP编程之网络聊天工具(客户端)

全部源码点击下载



作者:su20145104009 发表于2016/10/22 21:04:45 原文链接
阅读:115 评论:0 查看评论

OpenGL ES 着色器

$
0
0

OpenGL ES 着色器

1.着色器语言

着色器语言是一种高级图形编程语言,和C/C++语言很类似,但存在很大差别,比如,不支持double,byte

,short,不支持unin,enum,unsigned以及位运算等,但其加入了很多原生的数据类型,如向量,矩阵等。

数据类型可分为标量、向量、矩阵、采样器、结构体、数组等

Variable Class Types Description
Scalars float, int, bool 标量数据类型浮点数、整形数、布尔值
Floating-point Vectors float, vec2, vec3, vec4 浮点型向量,1、2、3、4 维
Integer vector int, ivec2, ivec3, ivec4 整形向量,1、2、3、4 维
Boolean vector bool,bvec2,bvec3,bvec4 布尔向量,1、2、3、4维
Matrices mat2, mat3, mat4 浮点类型矩阵 2×2,3×3,4×4

向量

向量传递参数,如果只提供一个标量,这个值用于设置所有向量的值;如果输入是多个标量或者是矢量,从左到右设置矢量变量的参数,如果多个矢量作为参数,那么至少要有和变量一样多的分量

vec4 myVec4 = vec4(1.0); // myVec4 = {1.0, 1.0, 1.0, 1.0}
vec3 myVec3 = vec3(1.0, 0.0, 0.5); // myVec3 = {1.0, 0.0, 0.5}
vec3 temp = vec3(myVec3); // temp = myVec3
vec2 myVec2 = vec2(myVec3); // myVec2 = {myVec3.x, myVec3.y}
myVec4 = vec4(myVec2, temp); // myVec4 = {myVec2.x, myVec2.y, temp.x, temp.y}

矩阵

矩阵操作在OpenGL ES中的使用非常广泛,涉及到图形的平移缩放旋转等操作都是由矩阵来实现的.

向矩阵传递参数:

  • 提供的是一个标量,那么标量复制给与矩阵的主对角线
  • 一个矩阵能被多个向量赋值,如,mat2可以用两个vec2赋值
  • 一个的矩阵被多个标量赋值,按列赋值

向量和矩阵的分量

向量一般用来存储位置、颜色纹理坐标等包含不止一个的量,访问向量中某个分量的方法为:<向量名.分量名>

  • 将向量看做颜色对待,四个分量为r、g、b、a,分别代表红、绿、蓝、透明度
  • 将向量看做位置对待,四个分量为x、y、z、w,分别代表x轴、y轴、z轴、w
  • 将向量看做纹理坐标对待,四个分量为s、t、p、q,分别代表纹理坐标的不同分量

这三种不同的命名方案不能混合使用,除此之外还可以将向量当做数组看待,用下表来访问。

vec3 myVec3 = vec3(0.0, 1.0, 2.0); // myVec3 = {0.0, 1.0, 2.0}
float x = myVec3.x;
vec3 temp;
temp = myVec3.xyz; // temp = {0.0, 1.0, 2.0}
temp = myVec3.xxx; // temp = {0.0, 0.0, 0.0}
temp = myVec3.zyx; // temp = {2.0, 1.0, 0.0}
vec4 temp2 = myVec3.xxyz; // temp2 = {0.0, 0.0, 1.0, 2.0}

对矩阵的访问当成一个二维数组即可,矩阵可以认为是由多个向量组成的

mat4 myMat4 = mat4(1.0); // Initialize diagonal to 1.0 (identity)
vec4 col0 = myMat4[0]; // Get col0 vector out of the matrix
float m1_1 = myMat4[1][1]; // Get element at [1][1] in matrix
float m2_2 = myMat4[2].z; // Get element at [2][2] in matrix

采样器

采样器专门用于进行纹理采样的相关操作,一般情况下一个采样器变量代表了衣服纹理切贴图。

sampler2D/sampler3D/samplerCube

采样器变量不是在着色器中初始化的,一般是由主程序传递进来的。

数组

声明数组时指定数组大小,反之,访问数组时的下表必须是编译时常量,这样的话,编译器会自动创建适当大小的数组

类型转换

着色器语言没有自动提升的功能,也不能强制转换,只能用构造器完成类型转换,每中内建变量类型都有一组相关的构造器。

float f = 1; // error
int i = 0.0; // error
float f = 1.0 // ok
bool b = bool(f) // ok,非0当做true
float f = float(b) // ok,bool转为浮点数,true转为1.0,false转为0.0
int i = 0; //ok
bool b = bool(i) // ok,int转为bool

变量限定符

const:常量,编译时常量,其值不可变,可以提高运行效率

attribute:属性变量,仅仅用在顶点着色器,用该限定符修饰的变量用来接受从宿主程序传进渲染管线的变量。一般用于每个顶点都不相同的量,比如顶点位置,颜色,法线等

uniform:统一变量,一般用于对同一组顶点组成的一个物体所有顶点都相同的量,比如光源位置,转换矩阵,颜色,光照等

varying:变量被用来存储顶点着色器的输出和片元着色器的输入,每个顶点着色器把输出数据转变成一个或更多片元着色器的输入,在光栅化阶段就会插值生成一系列变量

varying变量的原理

在线段上进行混合插值
线段线性插值
在三角形上进行混合插值
三角形线性插值

获取着色器变量

获取attribute类型变量。对于attribute限定符修饰的变量的值是由宿主程序传入渲染管线的,使用glGetAttribLocation函数获得着色器中某属性变量的引用

public static native int glGetAttribLocation(
        int program, // 创建的程序对象
        String name // 着色器中变量名
    );

然后使用glVertexAttribPointer函数将数据传递到glGetAttribLocation返回的着色器变量引用所代表的变量中去

public static void glVertexAttribPointer(
        int indx, // 属性变量的引用
        int size, //每个顶点的数据个数,比如x、y、z就是3
        int type, // 数据类型,如GLES20.GL_FLOAT
        boolean normalized, // 是否规格化,只有使用整形数据才有意义
        int stride, // 跨距,一个数组存储多个属性才有意义,指的是两个点之间有多少个字节
        java.nio.Buffer ptr // 存放顶点数据缓冲
    )

获取uniform类型的变量。使用glGetUniformLocation函数获得着色器中某统一变量的引用

public static native int glGetUniformLocation(
        int program,
        String name
    );

然后使用glUniformXXX函数将数据传递到着色器中,比如glUniformMatrix4fv函数

public static native void glUniformMatrix4fv(
        int location, // 统一变量的引用
        int count, // 指明要更改的元素个数。如果变量不是数组,这个值应该设为1
        boolean transpose, // 是否要转置矩阵,并将它作为uniform变量的值。必须为false
        float[] value, // 传递给统一变量的数组元素
        int offset // 偏移,取0
    );

glUniformNf/glUniformNfv:将N个浮点数传入管线

glUniformNi/glUniformNiv:将N个整数传入管线

glUniformMatrixNfv:将N个整数传入管线,将N*N矩阵传入管线

内建变量

内建变量不需要声明即可使用,内建变量分为两种,输入输出变量。

输入变量负责将渲染管线中固定功能部分生成的信息传递进着色器以供程序员使用,输出变量负责将着色器产生的信息传递给渲染管线中的固定功能。

顶点着色器

顶点着色器的内建变量主要是输出变量,即将着色器产生的值传递给渲染管线,因此在顶点着色器中要对这些内建变量赋值,包括gl_Position、gl_PointSize等。

  • gl_Position:在顶点着色器对获取到的定点原始数据进行平移缩放旋转等变换后,生成新的位置,新的顶点位置通过该变量传递给渲染管线的后续操作。
  • gl_PointSize:顶点着色器中可以计算一个点的大小,单位为像素,默认值为1,一般对点绘制方式有意义。

片元着色器

片元着色器中的内建输入变量,gl_FragCoord、gl_FrontFacing,并且还是只读的,是由渲染管线片元着色器之前阶段生成的。

  • gl_FragCoord:vec4类型数据,含有当前片元相对窗口位置的坐标。
  • gl_FrontFacing:bool类型的内建输入变量,该值表明当前正在处理的片元是否属于在光栅化阶段生成此片元对应图元的正面。点、线段没有正反面之分的图元。其生成的偏远都会被默认为是正面,三角形图元其正面取决于程序中队卷绕的设置及图元中顶点的具体卷绕情况。

片元着色器中的内建输出变量gl_FragColor、gl_FragData,在片元着色器中给这两个内建变量写入值。

  • gl_FragColo:vec4变量,用来传入由片元着色器计算出来的片元颜色值。

函数

和其他语言一样,差别在于参数可以指定用途,具体的有in,out,inout修饰符表明该参数是入参还是出参。

片元着色器浮点变量精度

片元着色器中的浮点类型数据必须制定精度,不指定精度可能引起编译错误。有三种精度类型:lowp、mediump、highp,一般使用mediump类型即可。如果在开发中同一个片元着色器中浮点类型变凉都是同一种精度类型,可以整个指定着色器中浮点类型默认精度。

precision <精度> <类型>
precision mediump float;

2.着色器程序

需要创建两个对象才能用着色器进行渲染:着色器对象和程序对象。

着色器源代码被编译成一个目标形式(类似obj文件),编译之后,着色器对象可以连接到一个程序对象,程序对象可以连接多个着色器对象。

获得连接后的着色器对象的过程:

  1. 创建一个顶点着色器和一个片元着色器:
  2. 将源代码连接到每个着色器对象
  3. 编译着色器对象
  4. 创建一个程序对象
  5. 将编译后的着色器对象连接到程序对象
  6. 连接程序对象

如果没有出错,就可以在后面使用这个程序了,如从程序获取某个着色器变量,接下来为其传递值等操作。

  • 创建着色器对象
public static native int glCreateShader(
  int type // 着色器类型,GLES20.GL_VERTEX_SHADER或GLES20.GL_FRAGMENT_SHADER
);
  • 连接源代码到着色器对象
public static native void glShaderSource(
        int shader,
        String string // 着色器源码
    );
  • 编译着色器对象
public static native void glCompileShader(
        int shader
    );
  • 创建程序对象
mProgram = GLES20.glCreateProgram();
  • 将编译后的着色器对象连接到程序对象
public static native void glAttachShader(
        int program,
        int shader
    );
  • 连接程序对象
public static native void glLinkProgram(
        int program
    );
作者:cauchyweierstrass 发表于2016/10/22 21:36:31 原文链接
阅读:131 评论:0 查看评论

iOS 获取通讯录的4种方式详解

$
0
0

使用场景

一些App通过手机号码来推荐好友,如 微博、支付宝

首先客户端会获取通讯录中的所有手机号然后将这些手机号提交到App服务器中,服务器会查找每个手机号对应的App账号如QQ号码返回到客户端,然后客户端根据服务器返回的账号列表来推荐好友。

获取联系人方式

  • 方案一:AddressBookUI.framework框架
    • 提供了联系人列表界面、联系人详情界面、添加联系人界面等
    • 一般用于选择联系人
  • 方案二:AddressBook.framework框架:
    • 没有提供UI界面,需要自己搭建联系人界面
    • 纯C语言的API, 仅仅是获得联系人数据
    • 大部分数据类型是Core Foundation
    • 从iOS6 开始,需要得到用户的授权才能访问通讯录
  • 方案三:第三方框架:RHAddressBook
    • 对 AddressBook.framework 进行封装
  • 方案四:iOS9.0最新通讯录框架
    • ContactsUI.framework : 方案1的替代品,特点: 面向对象,使用简单,有界面
    • Contacts.framework: 方案2的替代品, 特点:面向对象,使用简单,五界面

方案一:AddressBookUI.framework

实现步骤:

  1. 创建选择联系人的控制器
  2. 设置代理:用来接收用户选择的联系人信息
  3. 弹出联系人控制器
  4. 实现代理方法
  5. 在对应的代理方法中获取联系人信息

AddressBook.frame

实现步骤:

  1. 请求授权
  2. 判断授权状态如果已授权则继续,如果未授权则提示用户
  3. 创建通讯录对象
  4. 从通讯录中获取所有的联系人
  5. 遍历所有的联系人
  6. 释放不再使用的对象

AddreesBook.framework具体实现:

1. AppDelegate 应用启动时请求授权

#import "AppDelegate.h"
#import <AddressBook/AddressBook.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    [self requestAuthorizationAddressBook];
    return YES;
}


- (void)requestAuthorizationAddressBook {
    // 判断是否授权
    ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
    if (authorizationStatus == kABAuthorizationStatusNotDetermined) {
        // 请求授权
        ABAddressBookRef addressBookRef =  ABAddressBookCreate();
        ABAddressBookRequestAccessWithCompletion(addressBookRef, ^(bool granted, CFErrorRef error) {
            if (granted) {  // 授权成功

            } else {        // 授权失败
                NSLog(@"授权失败!");
            }
        });
    }
}
@end

2. iOS10 需要在Info.plist配置NSContactsUsageDescription

<key>NSContactsUsageDescription</key>
<string>请求访问通讯录</string>     

3. ViewController

#import "ViewController.h"
#import <AddressBook/AddressBook.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 1. 判读授权
    ABAuthorizationStatus authorizationStatus = ABAddressBookGetAuthorizationStatus();
    if (authorizationStatus != kABAuthorizationStatusAuthorized) {

        NSLog(@"没有授权");
        return;
    }

    // 2. 获取所有联系人
    ABAddressBookRef addressBookRef = ABAddressBookCreate();
    CFArrayRef arrayRef = ABAddressBookCopyArrayOfAllPeople(addressBookRef);
    long count = CFArrayGetCount(arrayRef);
    for (int i = 0; i < count; i++) {
        //获取联系人对象的引用
        ABRecordRef people = CFArrayGetValueAtIndex(arrayRef, i);

        //获取当前联系人名字
        NSString *firstName=(__bridge NSString *)(ABRecordCopyValue(people, kABPersonFirstNameProperty));

        //获取当前联系人姓氏
        NSString *lastName=(__bridge NSString *)(ABRecordCopyValue(people, kABPersonLastNameProperty));
        NSLog(@"--------------------------------------------------");
        NSLog(@"firstName=%@, lastName=%@", firstName, lastName);

        //获取当前联系人的电话 数组
        NSMutaleArray *phoneArray = [[NSMutableArray alloc]init];
        ABMultiValueRef phones = ABRecordCopyValue(people, kABPersonPhoneProperty);
        for (NSInteger j=0; j<ABMultiValueGetCount(phones); j++) {
            NSString *phone = (__bridge NSString *)(ABMultiValueCopyValueAtIndex(phones, j));
            NSLog(@"phone=%@", phone);
            [phoneArray addObject:phone];
        }

        //获取当前联系人的邮箱 注意是数组
        NSMutableArray *emailArray = [[NSMutableArray alloc]init];
        ABMultiValueRef emails= ABRecordCopyValue(people, kABPersonEmailProperty);
        for (NSInteger j=0; j<ABMultiValueGetCount(emails); j++) {
            NSString *email = (__bridge NSString *)(ABMultiValueCopyValueAtIndex(emails, j));
            NSLog(@"email=%@", email);
            [emailArray addObject:email];
        }
//获取当前联系人中间名
        NSString *middleName=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonMiddleNameProperty));
        //获取当前联系人的名字前缀
        NSString *prefix=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonPrefixProperty));

        //获取当前联系人的名字后缀
        NSString *suffix=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonSuffixProperty));

        //获取当前联系人的昵称
        NSString *nickName=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonNicknameProperty));

        //获取当前联系人的名字拼音
        NSString *firstNamePhoneic=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonFirstNamePhoneticProperty));

        //获取当前联系人的姓氏拼音
        NSString *lastNamePhoneic=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonLastNamePhoneticProperty));

        //获取当前联系人的中间名拼音
        NSString *middleNamePhoneic=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonMiddleNamePhoneticProperty));

        //获取当前联系人的公司
        NSString *organization=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonOrganizationProperty));

        //获取当前联系人的职位
        NSString *job=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonJobTitleProperty));

        //获取当前联系人的部门
        NSString *department=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonDepartmentProperty));

        //获取当前联系人的生日
        NSString *birthday=(__bridge NSDate*)(ABRecordCopyValue(people, kABPersonBirthdayProperty));

        //获取当前联系人的备注
        NSString *notes=(__bridge NSString*)(ABRecordCopyValue(people, kABPersonNoteProperty));

        //获取创建当前联系人的时间 注意是NSDate
        NSDate *creatTime=(__bridge NSDate*)(ABRecordCopyValue(people, kABPersonCreationDateProperty));

        //获取最近修改当前联系人的时间
        NSDate *alterTime=(__bridge NSDate*)(ABRecordCopyValue(people, kABPersonModificationDateProperty));

        //获取地址
        ABMultiValueRef address = ABRecordCopyValue(people, kABPersonAddressProperty);
        for (int j=0; j<ABMultiValueGetCount(address); j++) {
            //地址类型
            NSString *type = (__bridge NSString *)(ABMultiValueCopyLabelAtIndex(address, j));
            NSDictionary * tempDic = (__bridge NSDictionary *)(ABMultiValueCopyValueAtIndex(address, j));
            //地址字符串,可以按需求格式化
            NSString *adress = [NSString stringWithFormat:@"国家:%@\n省:%@\n市:%@\n街道:%@\n邮编:%@",[temDic valueForKey:(NSString*)kABPersonAddressCountryKey],[tempDic valueForKey:(NSString*)kABPersonAddressStateKey],[tempDic valueForKey:(NSString*)kABPersonAddressCityKey],[tempDic valueForKey:(NSString*)kABPersonAddressStreetKey],[tempDic valueForKey:(NSString*)kABPersonAddressZIPKey]];
        }

        //获取当前联系人头像图片
        NSData *userImage=(__bridge NSData*)(ABPersonCopyImageData(people));

        //获取当前联系人纪念日
        NSMutableArray *dateArr = [[NSMutableArray alloc]init];
        ABMultiValueRef dates= ABRecordCopyValue(people, kABPersonDateProperty);
        for (NSInteger j=0; j<ABMultiValueGetCount(dates); j++) {
            //获取纪念日日期
            NSDate *data =(__bridge NSDate*)(ABMultiValueCopyValueAtIndex(dates, j));
            //获取纪念日名称
            NSString *str =(__bridge NSString*)(ABMultiValueCopyLabelAtIndex(dates, j));
            NSDictionary *tempDic = [NSDictionary dictionaryWithObject:data forKey:str];
            [dateArr addObject:tempDic];
        }
    }
}

@end

4. 运行结果

这里写图片描述

这里写图片描述


第三方框架:RHAddressBook


https://github.com/heardrwt/RHAddressBook

该框架使用的MRC来管理内存的,如果直接将源代码拖入进去需要为每个文件设置编译标记:-fno-objc-arc, 设置完还会报错,该项目使用的一些方法过于古老,很多都不支持了,所以这种方式不采用; 可以将该项目打成静态库的方式;也可以直接将项目拖入到自己的工程中作为一个依赖

  1. 直接将RHAddressBook.xcodeproj拖入到工程中
    这里写图片描述

  2. 添加Target Dependencies和Link Binary With Libraries
    这里写图片描述

  3. Build Settings—> Other Linker Flags : -ObjC
    用于解决系统分类找不到方法的错误
    这里写图片描述

  4. iOS10 需要在Info.plist配置NSContactsUsageDescription

<key>NSContactsUsageDescription</key>
<string>请求访问通讯录</string>     
  1. App启动时请求授权访问通讯录
#import "AppDelegate.h"
#import <RHAddressBook/RHAddressBook.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    [self requestAuthorizationForAddressBook];
    return YES;
}

- (void)requestAuthorizationForAddressBook {
    RHAddressBook *ab = [[RHAddressBook alloc] init];
    if ([RHAddressBook authorizationStatus] == RHAuthorizationStatusNotDetermined){

        [ab requestAuthorizationWithCompletion:^(bool granted, NSError *error) {
            if (granted) {

            } else {
                NSLog(@"请求授权拒绝");
            }
        }];
    }
}
@end
  1. 获取所有联系人的信息:姓名、手机号等
#import "ViewController.h"
#import <RHAddressBook/RHAddressBook.h>
#import <RHAddressBook/AddressBook.h>

@interface ViewController ()

@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    RHAddressBook *addressBook = [[RHAddressBook alloc] init];
    if ([RHAddressBook authorizationStatus] != RHAuthorizationStatusAuthorized){
        NSLog(@"没有授权");
        return;
    }

    NSArray *peopleArray= addressBook.people;
    for (int i = 0; i < peopleArray.count; i++) {
        RHPerson *people = (RHPerson *)peopleArray[i];
        NSLog(@"%@", people.name);

        RHMultiStringValue *phoneNumbers = people.phoneNumbers;
        for (int i = 0; i < phoneNumbers.count; i++) {
            NSString* label= [phoneNumbers labelAtIndex:i];
            NSString* value= [phoneNumbers valueAtIndex:i];

            NSLog(@"label=%@, value=%@", label, value);
        }

        NSLog(@"----------------------------------------------");
    }
}
@end
  1. 运行结果:
    这里写图片描述

ContactsUI.framework


#import "ViewController.h"
#import <ContactsUI/ContactsUI.h>

@interface ViewController () <CNContactPickerDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    CNContactPickerViewController *contactPickerViewController = [[CNContactPickerViewController alloc] init];
    contactPickerViewController.delegate = self;

    [self presentViewController:contactPickerViewController animated:YES completion:nil];
}


// 如果实现该方法当选中联系人时就不会再出现联系人详情界面, 如果需要看到联系人详情界面只能不实现这个方法,
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact {
    NSLog(@"选中某一个联系人时调用---------------------------------");

    [self printContactInfo:contact];
}

// 同时选中多个联系人
- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContacts:(NSArray<CNContact *> *)contacts {
    for (CNContact *contact in contacts) {
        NSLog(@"================================================");
        [self printContactInfo:contact];
    }
}

- (void)printContactInfo:(CNContact *)contact {
    NSString *givenName = contact.givenName;
    NSString *familyName = contact.familyName;
    NSLog(@"givenName=%@, familyName=%@", givenName, familyName);
    NSArray * phoneNumbers = contact.phoneNumbers;
    for (CNLabeledValue<CNPhoneNumber*>*phone in phoneNumbers) {
        NSString *label = phone.label;
        CNPhoneNumber *phonNumber = (CNPhoneNumber *)phone.value;
        NSLog(@"label=%@, value=%@", label, phonNumber.stringValue);
    }
}
// 注意:如果实现该方法,上面那个方法就不能实现了,这两个方法只能实现一个
//- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContactProperty:(CNContactProperty *)contactProperty {
//    NSLog(@"选中某个联系人的某个属性时调用");
//}

@end

选择单个联系人时运行效果:
这里写图片描述

这里写图片描述

选择多个联系人的界面:
这里写图片描述
这里写图片描述


Contact.framework


iOS10 需要在Info.plist配置NSContactsUsageDescription

<key>NSContactsUsageDescription</key>
<string>请求访问通讯录</string>     

应用启动时请求授权:

#import "AppDelegate.h"
#import <Contacts/Contacts.h>

@interface AppDelegate ()

@end

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    [self requestAuthorizationForAddressBook];
    return YES;
}

- (void)requestAuthorizationForAddressBook {
    CNAuthorizationStatus authorizationStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
    if (authorizationStatus == CNAuthorizationStatusNotDetermined) {
        CNContactStore *contactStore = [[CNContactStore alloc] init];
        [contactStore requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (granted) {

            } else {
                NSLog(@"授权失败, error=%@", error);
            }
        }];
    }
}

@end

获取通讯录信息

#import "ViewController.h"
#import <Contacts/Contacts.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    CNAuthorizationStatus authorizationStatus = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
    if (authorizationStatus == CNAuthorizationStatusAuthorized) {
        NSLog(@"没有授权...");
    }

    // 获取指定的字段,并不是要获取所有字段,需要指定具体的字段
    NSArray *keysToFetch = @[CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey];
    CNContactFetchRequest *fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:keysToFetch];
    CNContactStore *contactStore = [[CNContactStore alloc] init];
    [contactStore enumerateContactsWithFetchRequest:fetchRequest error:nil usingBlock:^(CNContact * _Nonnull contact, BOOL * _Nonnull stop) {
        NSLog(@"-------------------------------------------------------");
        NSString *givenName = contact.givenName;
        NSString *familyName = contact.familyName;
        NSLog(@"givenName=%@, familyName=%@", givenName, familyName);


        NSArray *phoneNumbers = contact.phoneNumbers;
        for (CNLabeledValue *labelValue in phoneNumbers) {
            NSString *label = labelValue.label;
            CNPhoneNumber *phoneNumber = labelValue.value;

            NSLog(@"label=%@, phone=%@", label, phoneNumber.stringValue);
        }

//        *stop = YES;  // 停止循环,相当于break;
    }];

}
@end

运行效果:
这里写图片描述

这里写图片描述

作者:vbirdbest 发表于2016/10/22 21:38:57 原文链接
阅读:86 评论:0 查看评论

JAVA进阶案例 TCP编程之网络聊天工具(客户端)

$
0
0

首先附上登录操作


登录界面就不说了,直说业务处理。当点击登录之后

/*
		 * 如果点击了登录按钮 首先判断帐号或者密码是否为空 然后封装为CommandTranser对象 向服务器发送数据 服务器通过与数据库的比对
		 * 来验证帐号密码
		 */
		if (e.getSource() == login) {
			String username = text_name.getText().trim();
			String password = new String(text_pwd.getPassword()).trim();
			if ("".equals(username) || username == null) {
				JOptionPane.showMessageDialog(null, "请输入帐号!!");
				return;
			}
			if ("".equals(password) || password == null) {
				JOptionPane.showMessageDialog(null, "请输入密码!!");
				return;
			}
			User user = new User(username, password);
			CommandTranser msg = new CommandTranser();
			msg.setCmd("login");
			msg.setData(user);
			msg.setReceiver(username);
			msg.setSender(username);
			// 实例化客户端 并且发送数据 这个client客户端 直到进程死亡 否则一直存在
			Client client = new Client();
			client.sendData(msg);
			msg = client.getData();
			if (msg != null) {
				if (msg.isFlag()) {
					this.dispose();
					JOptionPane.showMessageDialog(null, "登陆成功!");
					// 显示好友列表界面
					new FriendsUI(username, client);
				} else {
					JOptionPane.showMessageDialog(this, msg.getResult());
				}
			}

		} else if (e.getSource() == sweep) {
			text_name.setText(null);
			text_pwd.setText(null);
		}

	}
把帐号密码 登录指令等封装为CommandTranser对象  然后实例化一个客户端  通过sendData(CommandTranser msg) 方法向服务器发送数据 然后通过getData方法获得服务器返回的数据。

其中CommandTranser类在服务端已经介绍 主要变量如下

private String sender = null;// 发送者
	private String receiver = null;// 接受者
	private Object data = null;// 传递的数据
	private boolean flag = false;// 指令的处理结果
	private String cmd = null;// 服务端要做的指令
	private String result = null;// 处理结果

客户端Client代码主要负责向服务端发送数据 sendData(CommandTranser msg) 和接收服务端的数据 getData()

构造方法

// 实例化时 建立连接
	public Client() {
		try {
			socket = new Socket(address, port);
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			JOptionPane.showMessageDialog(null, "服务端未开启");
		} catch (IOException e) {
			// TODO Auto-generated catch block
			JOptionPane.showMessageDialog(null, "服务端未开启");
		}
	}

sendData(CommandTranser msg) 方法

	// 向服务端发送数据
	public void sendData(CommandTranser msg) {
		ObjectOutputStream oos = null;
		try {
			if (socket == null)
				return;
			oos = new ObjectOutputStream(socket.getOutputStream());
			oos.writeObject(msg);
		} catch (UnknownHostException e1) {
			JOptionPane.showMessageDialog(null, "服务端未开启");
		} catch (IOException e1) {
			JOptionPane.showMessageDialog(null, "服务端未开启");
		}
	}

 getData()方法

// 向服务端接收数据
	public CommandTranser getData() {
		ObjectInputStream ois = null;
		CommandTranser msg = null;
		if (socket == null)
			return null;
		try {
			ois = new ObjectInputStream(socket.getInputStream());
			msg = (CommandTranser) ois.readObject();
		} catch (IOException e) {
			return null;
		} catch (ClassNotFoundException e) {
			return null;
		}
		if ("message".equals(msg.getCmd()))
			System.out.println((String) msg.getData());
		return msg;
	}

如果服务器返回登录成功的消息 则进入好友列表界面


在登录界面 所有的好友都是我自己创建的  。陌生人知识一个摆设。。当然也可以通过服务端的数据库来加载好友列表。。以后自己慢慢更新  

这两天出现的各种各样的错误 头都炸了。。

而在好友界面  有一个鼠标双击事件  和鼠标进入好友列表,离开好友列表事件


当鼠标进入好友列表 改变背景色

// 如果鼠标进入我的好友列表 背景色变色
		@Override
		public void mouseEntered(MouseEvent e) {
			// TODO Auto-generated method stub
			JLabel label = (JLabel) e.getSource();
			label.setOpaque(true);
			label.setBackground(new Color(255, 240, 230));
		}

修改背景色之前  一定要设置为不透明  label.setOpaque(true);

不然看不到背景色

鼠标离开好友列表

		// 如果鼠标退出我的好友列表 背景色变色
		@Override
		public void mouseExited(MouseEvent e) {
			// TODO Auto-generated method stub
			JLabel label = (JLabel) e.getSource();
			label.setOpaque(false);
			label.setBackground(Color.WHITE);
		}

当双击好友后 出现聊天框

		@Override
		public void mouseClicked(MouseEvent e) {
			// TODO Auto-generated method stub
			// 如果双击了两次 我的好友 弹出与这个好友的聊天框
			if (e.getClickCount() == 2) {
				JLabel label = (JLabel) e.getSource();
				new ChatUI(owner, label.getText(), client);
			}
		}

一旦打开聊天框  就要新建一个线程  时刻监听服务器发送的消息

thread = new ClientThread(client, chat_txt);
		thread.start();
//当前 对话框存在
		while (isOnline) {
			//I/O阻塞  接收服务端发送的数据
			CommandTranser msg = client.getData();
			if (msg != null) {
				/*
				 * 如果服务端处理数据成功  接收信息
				 * 否则   弹出对方不在线的对话框
				 */
				if (msg.isFlag()) {
					//发送时间
					Date date = new Date();
					SimpleDateFormat sdf = new SimpleDateFormat(
							"yy-MM-dd hh:mm:ss a");
					String message = msg.getSender() + "说:"
							+ (String) msg.getData() + "\t" + sdf.format(date);
					// 在聊天框添加收到的信息
					chat_txt.append(message+"\n");
				} else {
					JOptionPane.showMessageDialog(chat_txt, msg.getResult());
				}

			}
		}

而当点击了对话框的发送按钮时 将内容封装为CommandTrander对象  客户端向服务器发送数据

// 如果点击了发送按钮
		if (e.getSource() == send_btn) {
			Date date = new Date();
			SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd hh:mm:ss a");

			String message = "你说:" + message_txt.getText() + "\t"
					+ sdf.format(date);
			// 在本地文本区追加发送的信息
			chat_txt.append(message+"\n");
			// msg为客户端向服务器发送的数据
			CommandTranser msg = new CommandTranser();
			msg.setCmd("message");
			msg.setSender(owner);
			msg.setReceiver(friend);
			msg.setData(message_txt.getText());

			client.sendData(msg);
			// 发送信息完毕 写信息的文本框设空
			message_txt.setText(null);
		}


好啦  代码 就这么多。聊天过程如下


全部源码点击下载


作者:su20145104009 发表于2016/10/22 22:07:39 原文链接
阅读:109 评论:0 查看评论

Java 基于TCP的Socket网络编程的入门及示例

$
0
0

前言:各位周末愉快,因匿名台风来袭,窗外又是一波风雨交加,所以又是哪都不想去……话说给我天晴还不是一样窝在家里。。。好了,这篇是总结了tcp的socket,各种例子,应该是比较全了,记在这里,用到时可以过来瞄一眼。
原文出处:http://blog.csdn.net/u014158743/article/details/52895401

Socket网络这块很重要的是指定ip地址,端口号等基本信息。这些信息被封装在InetAddress中,所以先上一个小Demo介绍InetAddress的几个基本方法。

InetAddress入门Demo

    public static void main(String[] args) throws UnknownHostException {
        // TODO Auto-generated method stub

        // 获取本地主机的IP地址对象
        //InetAddress inet = InetAddress.getLocalHost();

        //获取任意一台主机的ip地址对象
        InetAddress  inet = InetAddress.getByName("www.baidu.com");

        // 获取ip地址
        String address = inet.getHostAddress();

        // 获取主机名
        String name = inet.getHostName();

        System.out.println(name + ":" + address);

    }

使用 Tcp 协议实现客户端给服务器端发送数据

TcpClient

    /**
     * 使用 Tcp 协议实现客户端给服务器端发送数据
     * 1:创建Socket端点,同时指明连接的服务器和端口
     * 2:使用Socket的发送功能发送数据
     * @throws IOException 
     * @throws UnknownHostException 
     */
    public static void main(String[] args) throws UnknownHostException, IOException {
        System.out.println("客户端启动......");
        //创建Socket客户端端点,同时指明连接的服务器和端口
        //这条语句执行成功,说明客户端对象创建成功,同时说明和服务器端连接成功
        //如果连接成功,说明和服务器端建立了一条通道
        //这条通道就是 Socket流(就是Socket客户端对象),这个Socket流中既有字节输入流,也有字节输出流
        Socket s = new Socket(InetAddress.getByName("192.168.1.212"),55555);


        //向服务器端发送数据
        //发送数据就是输出---从 Socket流中获取输出流
        OutputStream out = s.getOutputStream();

        //发送数据
        out.write("哥们,你好".getBytes());

        //接收数据
        InputStream in = s.getInputStream();
        byte[] arr = new byte[1024];
        int len =in.read(arr);
        System.out.println(new String(arr,0,len));

        s.close();
    }

TcpServer

    /**
     * 使用 Tcp实现服务器端
     * 1:创建 Socket端点,同时监听端口
     * 2:
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        System.out.println("服务器端启动......");
        //1:创建 Socket端点,同时监听端口
        ServerSocket  ss = new ServerSocket(55555);

        //得到客户端对象,也就是得到客户端使用的 Socket流,从而和客户端使用同一个流
        Socket s = ss.accept();
        System.out.println(s.getInetAddress().getHostAddress()+"连接到服务器端......");

        //接收客户端发送的数据
        //接收就是读取
        InputStream in = s.getInputStream();

        byte[] arr = new byte[1024];
        int len = in.read(arr);
        System.out.println(new String(arr,0,len));

        //向客户端发送数据
        OutputStream out = s.getOutputStream();
        out.write("你好,哥们".getBytes());

        s.close();
        ss.close();
    }

一个大写转换服务器Demo

客户端接收键盘输入的小写字符串,发送给服务器端,服务器端接收到小写字符串,转成大写,再发送给客户端

TextChangeClient

     /** 
     * 客户端:
     *  1:接收键盘输入的小写字符串
     *  2:发送小写字符串
     *  3:接收大写字符串
     * @throws IOException 
     * @throws UnknownHostException 
     */
    public static void main(String[] args) throws UnknownHostException, IOException {
        System.out.println("客户端启动....");
         //创建客户端对象
        Socket s = new Socket(InetAddress.getByName("192.168.1.212"),23333);

        //创建读取键盘输入的小写字符串的字符读取流
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        //创建发送小写字符串的字符输出流
        OutputStream out = s.getOutputStream();
        PrintWriter pw = new PrintWriter(out,true);

        //创建接收大写字符串的字符读取流 
        InputStream in = s.getInputStream();
        BufferedReader bin = new BufferedReader(new InputStreamReader(in));


        //循环读取键盘输入的数据,发送给服务器端,并接收返回的大写字符串
        String line = null;
        while((line = br.readLine())!=null) {
            if("over".equals(line))
                break;
            pw.println(line);//发送给服务器端
            System.out.println(bin.readLine());//接收返回的大写字符串
        }

        br.close();
        s.close();

    }

TextChangeServer

    /**               
     * 服务器端:
     * 接收小写字符串
     * 发送大写字符串
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        System.out.println("服务器端启动....");
        ServerSocket ss = new ServerSocket(23333);

        //得到客户端对象
        Socket s = ss.accept();
        System.out.println(s.getInetAddress().getHostAddress()+"连接到 服务器端.....");

        //创建接收小写字符串的字符读取流
        InputStream in = s.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(in));

        //创建发送大写字符串的字符输出流
        OutputStream out = s.getOutputStream();
        PrintWriter pw = new PrintWriter(out,true);

        String line = null;
        while((line = br.readLine())!=null)//循环读取客户端
        {
            pw.println(line.toUpperCase());
        }

        s.close();
    }

文本文件的上传的客户端Demo

客户端读取本地文件发送给服务器端,服务器端读取客户端,写入到服务器端的某个文件,服务器端返回”上传成功”

FileUploadClient

    /**
     * 客户端:
     * 1:读取本地文件
     * 2:发送给服务器端
     * 3:读取服务器端返回的"上传成功"
     * @throws IOException 
     * @throws UnknownHostException 
     */
    public static void main(String[] args) throws UnknownHostException, IOException {

        System.out.println("客户端启动.....");

        Socket s = new Socket(InetAddress.getByName("192.168.1.212"),24444);

        //创建读取本地文件的字符读取流
        BufferedReader br = new BufferedReader(new FileReader("temp\\tcptext.txt"));

        //创建 向服务器端发送数据的字符输出流
        OutputStream out = s.getOutputStream();
        PrintWriter pw = new PrintWriter(out,true);

        //创建读取服务器端返回的"上传成功"的字符读取流
        InputStream in = s.getInputStream();
        BufferedReader brr =  new BufferedReader(new InputStreamReader(in));

        //循环读取文件,发送到服务器端
        String line = null;
        while((line = br.readLine())!=null) {
            pw.println(line);
        }
        //向服务器端写入结束标记
        s.shutdownOutput();

        //读取服务器端返回的"上传成功"
        String result = brr.readLine();

        System.out.println(result);

        br.close();
        s.close();

    }

FileUploadServer

    /**          
     * 服务器端:
     * 接收客户端发送的数据
     * 写入到本地文件
     * 发送“上传成功”
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {
        System.out.println("服务器端启动....");
        ServerSocket ss = new ServerSocket(24444);

        Socket s = ss.accept();
        System.out.println(s.getInetAddress().getHostAddress()+"连接到服务器端.....");

        //创建接收客户端发送的数据字符读取流
        InputStream in = s.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(in));

        //创建写入到本地文件的字符输出流
        PrintWriter pw = new PrintWriter(new FileWriter("temp\\tcptext_copy.txt"),true);

        //创建发送“上传成功”的字符输出流
        OutputStream out = s.getOutputStream();
        PrintWriter pww = new PrintWriter(out,true);

        //循环读取客户端,写入到本地文件
        String line = null;
        while((line = br.readLine())!=null) {//读不到null
            pw.println(line);
        }

        pw.close();

        //发送上传成功
        pww.println("上传成功");

        s.close();
    }

图片上传的客户端Demo

PicUploadClient

    /**
     * 图片上传的客户端:
     * @throws IOException 
     * @throws UnknownHostException 
     * 
     */
    public static void main(String[] args) throws UnknownHostException, IOException {

          System.out.println("客户端启动.....");

          Socket s = new Socket(InetAddress.getByName("192.168.1.212"),25555);

          FileInputStream fis = new FileInputStream("temp\\tu.jpg");

          OutputStream out = s.getOutputStream();

          InputStream in = s.getInputStream();

          byte[] arr = new byte[1024];
          int len = 0;
          while((len = fis.read(arr))!=-1) {
              out.write(arr,0,len);
          }

          s.shutdownOutput();//写入结束标记

          //读取“上传成功”
          byte[] b = new byte[1024];
          int num = in.read(b);
          System.out.println(new String(b,0,num));

          fis.close();
          s.close(); 
    }

PicUploadServer

    /**
     *  图片上传的服务器端:
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {

        System.out.println("服务器端启动....");
        ServerSocket ss = new ServerSocket(25555);

        Socket s = ss.accept();
        System.out.println(s.getInetAddress().getHostAddress()+"连接到服务器端......");

        InputStream in = s.getInputStream();

        FileOutputStream fos = new FileOutputStream("temp\\tu_copy.jpg");

        OutputStream out = s.getOutputStream();

        //循环读取客户端数据,写入到本地文件
        byte[] arr = new byte[1024];
        int len = 0;
        while((len = in.read(arr))!=-1)
        {
            fos.write(arr,0,len);
        }
        fos.close();

        out.write("上传成功".getBytes());

        s.close();

    }

图片的并发上传Demo

PicUploadClient

同上

PicUploads

public class PicUploads implements Runnable {

    private Socket socket;
    public PicUploads(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {

          //所有上传的图片放到同一个目录下
          File dir = new File("f:\\tupian");
          if(!dir.exists())
              dir.mkdir();

          String ip = socket.getInetAddress().getHostAddress();
          System.out.println(ip+"连接到服务器端....");

          int num = 0;
          File file = new File(dir,ip+"("+(++num)+")"+".jpg");

          while(file.exists()) {
              file = new File(dir,ip+"("+(++num)+")"+".jpg");
          }

          try {

            InputStream in = socket.getInputStream();
            FileOutputStream fos = new FileOutputStream(file);

            byte[] arr = new byte[1024];
            int len = 0;
            while((len = in.read(arr))!=-1) {
                fos.write(arr,0,len);
            }
            fos.close();

            OutputStream out = socket.getOutputStream();
            out.write("上传成功".getBytes());

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

PicUploadServer

    /**
     * @param args
     * @throws IOException 
     */
    public static void main(String[] args) throws IOException {

        ServerSocket ss = new ServerSocket(25555);
        while(true) {
            Socket socket = ss.accept();
            new Thread(new PicUploads(socket)).start();
        }
    }

结束,祝成长。。。入门的同学可以实现图片的并发下载来做练习。
晚安。

作者:u014158743 发表于2016/10/22 22:17:17 原文链接
阅读:166 评论:0 查看评论

EditText控件的TextWatcher接口实现和其他控件的联动

$
0
0

使用TextWathcer限制EditText控件输入字符个数。

页面布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="pp.org.vincent.glibdemo.MainActivity">



    <!--   使用TextWathcer实现EditeText和TextView同步   -->
    <LinearLayout
        android:id="@+id/ll_synsa"
        android:layout_below="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="true">
        <EditText
            android:id="@+id/ET"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/IV"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>

    <!--   使用TextWathcer限制输入字符个数   -->
    <LinearLayout
        android:id="@+id/ll_limit"
        android:layout_below="@+id/ll_synsa"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true">
        <EditText
            android:id="@+id/ET_limit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <TextView
            android:id="@+id/IV_limit_show"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
    </LinearLayout>
</RelativeLayout>
editText_limit= (EditText) findViewById(R.id.ET_limit);
        textView_limit=(TextView)findViewById(R.id.IV_limit_show);

        //限定这个edittext的字符数量为10个
        editText_limit.addTextChangedListener(new TextWatcher() {
            public CharSequence oldString;
            private int editStart ;
            private int editEnd ;

            /**
             * s 是edittext更改前的字符串引用(注意是引用),参数表示从下标start的count个字符串被被后来的after个字符代替
             * 从s中可以寻找到被替换的字符串
             * @param s    代表更改前的字符串 S
             * @param start  字符串更改的位置
             * @param count  更改字符个数
             * @param after  代替的新字符个数
             */
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                Toast.makeText(MainActivity.this,"beforeTextChanged:"+s,Toast.LENGTH_SHORT).show();
                oldString =  s;
                //注意这个oldString只是指向edittext文本实例的一个引用,并不是一个new出来的String实例
                //所以当edittext原来是空,输入一个字符这里输出的oldString为空,length()为0;但是
                //afterTextChanged接口方法里面他会获得更改后的文本,所以oldString所指向的实例就被改变了。可以看到
                //length()为1
                Toast.makeText(MainActivity.this,"oldString.length()"+oldString.length()+"",Toast.LENGTH_LONG).show();
            }

            /**
             * 这个方法在edittext文本框文本改变后调用
             *  s是更改后的字符串,表示从start下标开始的count个字符代替原来的before个字符
             *  从这个回调函数可以获取新插入字符串内容
             * @param s   s 表示更改后的新字符
             * @param start   插入的字符串在s串中start下标开始的一个字符串,有count个长度
             * @param before
             * @param count
             */

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                /*Toast.makeText(MainActivity.this,
                        "new string: "+s.subSequence(start,start+count) +"start :"+start+"count: "+count+"before: "+before, Toast.LENGTH_SHORT)
                        .show();*/
                textView_limit.setText(s);
            }

            /**
             * 这个方法在edittext文本框文本改变后调用
             * 任何edittext文本的改变都会回调这个方法,在这个方法里面可以改变edittext的文本,但是要注意
             * 防止死循环。
             * @param s
             */
            @Override
            public void afterTextChanged(Editable s) {

                editStart = editText_limit.getSelectionStart();//返回光标当前的起止位置

                editEnd = editText_limit.getSelectionEnd();//返回光标当前的结束位置,但是和上面的一样啊,
                Toast.makeText(MainActivity.this,"aftTeChang oldString.length()"+oldString.length()+""+":"+oldString.toString(),Toast.LENGTH_LONG).show();
                //限制不能超过10个字符,oldString指向edittext文本框文本实例的引用
                if (oldString.length() > 10) {
                    Toast.makeText(MainActivity.this,
                            "你输入的字数已经超过了限制!"+"editStart: "+editStart+"editEnd: "+editEnd, Toast.LENGTH_SHORT)
                            .show();
                    s=s.delete(editStart-1, editEnd);//删除多出来的那个字符
                    int tempSelection = editStart;
                    editText_limit.setText(s);
                    editText_limit.setSelection(tempSelection);
                }
            }
        });

将edittext中的内容同步到textview

 //edittext同步到textview
        editText.addTextChangedListener(new TextWatcher() {


            /**
             *
             * @param s  改变字符串的原来情况
             * @param start 内容被改变的开始位置
             * @param count 原始文字被删除的个数   总start和count可以获取到删除的字符串,这个删除的字符串被after个字符替代
             * @param after 新添加的字符串
             */
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                Log.d(TAG,"string: "+s+"Start: "+start+"count: "+count+"after: "+after);
            }

            /**
             *
             * @param s 改变后的新文本
             * @param start 改变的开始位置
             * @param before 原来文字删除的字符
             * @param count 总共增加了 几个字符,总start开始的count个字符替换了原来的before个字符
             *              start和count结合从s中获取新添加的字符内容
             */
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d(TAG,s.toString());
                //用新的文本同步到textview控件
                if (!TextUtils.isEmpty(editText.getText()))
                {
                    textView.setText(editText.getText().toString());
                }
                else {                    //Toast.makeText(MainActivity.this,"edittext is null",Toast.LENGTH_LONG).show();
                    textView.setText("");
                }

            }

            @Override
            public void afterTextChanged(Editable s) {
                //s 改变后的最终内容
                Log.d(TAG,"afterTextChanged :"+s.toString());
                if (TextUtils.isEmpty(editText.getText())){
                    textView.setText("");
                }
            }
        });
作者:JQ_AK47 发表于2016/10/22 22:17:35 原文链接
阅读:84 评论:0 查看评论

android那些事--混淆语法

$
0
0

初步了解手动混淆配置的规则

手动添加的配置,一般以”-keep”开头,常用的配置命令分别示例如下:
假设android工程中有一个接口和一个类:

package com.ticktick.example;

public interface TestInterface {
    public void test();
}

public class Test {

    private String mTestString;
    private final int mMinValue;
    private final int mMaxValue;

    public Test( int min, int max){
        mMinValue = min;
        mMaxValue = max;
    }

    public int getMinValue() {
        return mMinValue;
    }

    public int getMaxValue() {
        return mMaxValue;
    }

    public void setTestString(String testStr ) {
        mTestString = testStr;
    }
}

1.不混淆某个类的构造函数

例如不混淆Test类的构造函数

-keepcalssmembers clsss com.ticktick.example.Text{
    public <init>(int,int)
}

2.不混淆某个包下所有的类或者指定的类

例如不混淆package com.ticktick.example下的所有的类/接口

#不混淆这个包下的所有的类
-keep class com.ticktick.example.**{*;}

例如不混淆com.ticktick.example.Text类

-keep class com.ticktick.example.Text{*;}

如果希望不混淆某个接口,则把上述命令的class替换为interface即可.

3.不混淆某个类的特定的函数

例如不混淆com.ticktick.example.Test类中的setTestString函数

-keepclassmembers class com.ticktick.example.Test{
    public void setTestString(java.lang.String);
}

4.不混淆某个类的子类,某个接口的实现

例如不混淆com.ticktick.example.Test的子类

-keep public class * extend com.ticktick.example.Test

例如不混淆com.ticktick.example.TestInterface的实现

-keep class * implements com.ticktick.example.TestInterface{
    public static final com.ticktick.example.TestInterface$Creator *;
}

5.注意第三方依赖包

例如添加android-support-v4.jar依赖包

-libraryjars libs/android-support-v4.jar

-dontwarn android.support.v4.**{*;}
-keep class android.support.v4.**{*;}
-keep interface android.support.v4.**{*;}

注意添加dontwarn,因为默认情况下proguard会检查每一个引用是否正确,但是第三方库里往往有些不会用到的类,没有正确引用,所有如果不配置的话,系统会报错.

ProGuard常用语法

枯涩的语法总是避免不了的.上面的小例子对简单的配置进行了一下描述,下面是详细的语法.

保留 keep的配置

  • -keep {Modifier(修饰符)}{class_specification(类规范)} 保护指定的类和类的成员
#例如对一个可执行jar包来说,需要保护main的入口类,对一个类库来说需要保护它的所有public元素
-keep public class MyMain{
    public static void main(java.lang.String[]);
}
  • -keepclassmembers {Modifier}{class_specification}
    保护指定类的成员,如果此类也受到保护他们会保护的更好
#例如指定继承了Serizalizable的类的如下成员不被混淆,成员属性和方法,非私有的属性非私有的方法.
-keepclassmembers class * implements java.io.Serializable{
    static final long seriaVersionUID;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOuputStream);
}
  • -keepclasseswithmembers{Modifier}{class_specification}保护指定成员的类,根据成员确定一些将要被保护的类

就是说如果一个类含有以下的成员,那么这个类会被保护

#保护含有main方法的类以及这个类的main方法
-keepclasseswithmembers public class * {
    public static void main(java.lang.String[]);
}
  • -keepnames {Modifier}{class_specification} 这个是-keep,allowshrinking {Modifier}{class_specification}的简写.意思是说允许压缩操作,如果在压缩过程中没有被删除,那么类名和该类中的成员的名字会被保护
  • -keepclassmembernames {Modifier}{class_specification}如果在压缩过程中没有被删除在保护这个类中的成员
  • -keepclasseswithmembers {Modifier}{class_specification}如果在压缩过程中该类没有被删除,同样如果该类中有某个成员字段或者方法,那么保护这个类和这个方法.

  • -printseeds{filename}将keep的成员输出到文件或者打印到标准输出流.

代码压缩配置 shrinker配置

  • -dontshrink 声明不压缩文件.默认情况下,除了-keep相关指定的类,其它所有的没有被引用到的类都会被删除,每次优化(optimizate)操作之后也会执行一次压缩操作,因为每次优化操作可能删除一部分不在需要的类.
  • -printusage{filename} 将被删除的元素输出到文件,或者打印到保准输出流
  • whyareyoukeeping {class_specification} 打印为什么吗一个类或类的成员被保护,这对检查一个输出文件中的类的结果有帮助.

代码优化配置 optimizate配置

  • -dontoptimize 声明不优化的文件.默认情况下,优化选项是开启的,并且所有的优化都是在字节码层进行的.
  • -optimizations 更加细粒度的声明优化开启或者关闭.
  • -optimizationpasses n 指定执行几次优化,默认情况只执行一次优化,执行多次优化可以提高优化的效果.但是如果执行一次优化之后没有效果,就会停止优化,剩下的设置次数不在执行.

混淆配置 obfuscate配置

  • -dontobfuscate 声明不混淆 默认情况下,混淆是开启的.除了keep配置中的类其它的类或者类的成员混淆后会改成随机简短的名字
  • printmapping{filename} 指定输出新旧元素名的对照表的文件,可以通过在build.gradle中的配置,进行自动保存.这个在异常追踪的时候非常有用可以参考这个粗暴接触混淆
  • -applymapping {filename}指定重用一个已经写好的map文件作为新旧元素名的映射.元素名已经存在的就按照该文件中的配置进行,对于没存在的就重新赋一个新的名字
  • obfuscationdictionary {filename}指定一个文本文件用来生成混淆后的名字.默认情况下混淆后的名字一般为a,b,c.通过这个选项配置的字典文件,可以使用一些非英文字符做为类名,成员名,方法名.需要注意的是添加了字典并不会显著提高混淆的效果,只不过是更不利与人类的阅读。正常的编译器会自动处理他们,并且输出出来的jar包也可以轻易的换个字典再重新混淆一次。最有用的做法一般是选择已经在类文件中存在的字符串做字典,这样可以稍微压缩包的体积。
#查找字典文件的格式:一行一个单词,空行忽略,重复忽略
# 这里巧妙地使用java中的关键字作字典,混淆之后的代码更加不利于阅读
#
# This obfuscation dictionary contains reserved Java keywords. They can't
# be used in Java source files, but they can be used in compiled class files.
# Note that this hardly improves the obfuscation. Decent decompilers can
# automatically replace reserved keywords, and the effect can fairly simply be
# undone by obfuscating again with simpler names.
# Usage:
#     java -jar proguard.jar ..... -obfuscationdictionary keywords.txt
#
do
if
for
int
new
try
byte
case
char
else
goto
long
this
void
break
catch
class
const
final
float
short
super
throw
while
double
import
native
public
return
static
switch
throws
boolean
default
extends
finally
package
private
abstract
continue
strictfp
volatile
interface
protected
transient
implements
instanceof
synchronized
  • -classobfuscationdictionary {filename}指定一个混淆类名的字典,字典格式与-obfuscationdictionary相同
  • -packageobfuscationdictionary {filename}指定一个混淆包名的字段
  • -oberloadaggressively 混淆的时候大量使用重载,多个方法名使用同一个混淆名,但是它们的方法签名不同,这可以使包的体积减小一部分也可以加大理解难度.

不过这个配置有一定的限制

Sun的JDK1.2上会报异常
Sun JRE 1.4上重载一些方法之后会导致序列化失败
Sun JRE 1.5上pack200 tool重载一些类之后会报错
java.lang.reflect.Proxy类不能处理重载的方法
  • -useuniqueclassmembernames 指定相同的混淆名对应相同的方法名,不同的混淆名对应不同的方法名.如果不设置这个选项,同一个类中将会有很多方法映射到相同的方法名.这项配置会稍微增加输出文件中的代码,但是它能够保证 保存下来的mapping文件能够在随后的增量混淆中继续被遵守,避免重新命名.比如说两个接口拥有同名的方法和相同的签名,如果没有这个配置,在第一次打包混淆之后它们两个方法可能会被赋予不同的混淆名.如果下一次添加代码有一个类同时实现了这两个接口,那么混淆的时候必然会将两个混淆后的方法名统一起来.这样就必须要改混淆文件其中一处的配置,也就不能保证前后两次混淆的mapping文件一致了(如果想用一份mappping文件迭代更新的话,这项配置比较有用).
  • -dontusemixedcaseclassnames 指定在混淆的时候不使用大小写混用的类名.默认情况下混淆后的类名可能同时包含大写字母和小写字母.这样的jar并没有什么问题,只有在大小写不敏感的window系统上解压时,才会涉及问题.因为大小写不区分,可能会导致部分文件在解压的时候相互覆盖.
  • -keeppackagenames{package_filter} 声明不混淆指定的包名,配置的过滤器是逗号隔开的一组包名.包名可以包含? *通配符,并且可以在前面加!否定符
  • -flatternpackagenames{packagename} 所有重命名的包都重新打包,并把所有的类移动到packagename包下面.如果没有指定packagename或者packagename为”“,那么所有的类都会被移动到根目录下.
  • -repackageclasses{package_name} 所有重新命名类都重新打包,并把它们移动到指定的packagename目录下.这项配置会覆盖-flatternpackagehierarchy的配置.他可以使代码体积更小,并且更加难以理解.

需要注意的是如果需要从目录中读取资源文件,移动包的位置可能会导致异常.如果出现这个问题,就不要用这个配置了.

  • -keepattributes{attribute_filter} 指定受保护的属性.可以有一个或者多个该配置项,每个配置后面跟随的是java虚拟机和proguard支持的attribute(具体支持的属性先看这里),两个属性之间用逗号分隔.属性名可以使用通配符,或者! 将某个属性排除在外.当混淆一个类库的时候至少要保持InnerClasses,Exceptions,Signature属性.为了跟踪异常信息,需要把SourceFile,LineNumberTable两个属性保留.如果代码中有用到注解,需要Annotion属性.
#可以分开写
-keepattributes SourceFile, LineNumberTable
-keepattributes *Annotation*
-keepattributes EnclosingMethod
# 可以直接写在一行
-keepattributes Exceptions, InnerClasses, Signature, Deprecated,
                SourceFile, LineNumberTable, *Annotation*, EnclosingMethod
  • -keepparameternames 指定被保护的方法的参数类型和参数名不被混淆.这项配置在混淆一些类库的时候特别有用,因为根据IDE提示的参数名和参数类型,开发者可以根据语意获得一些信息.
  • -adaptclassstrings{classfilter}指定字符串常量如果与类名相同,也需要被混淆.如果没有加classfilter,所有的符合要求的字符串常量都会被混淆;如果带classfilter,只有在匹配的类中的字符串常量才会受此影响.例如在你的代码中有大量的类名对应的字符串的hard-code,并且不想保留它们的本名,那就可以利用哦这项配置来完成.
  • -adaptresourcefileanmes{file_filter} 如果资源文件与某类同名,那么混淆后资源文件被命名为与之对应的类的混淆名.不加file_filter的情况下,所有资源文件都受此影响,加了file_filter的情况只有匹配的类才受此类的影响.
  • -adaptresourcefilecontents{file_filter}指定资源文件的中的类名随混淆后的名字更新.根据被混淆的名字的前后映射关系,更新文件中对应的包名和类名.

普通配置

  • -berbose 声明在处理过程中输出更多信息,添加这项配置之后,如果处理过程中出现异常,会输出整个StackTrace而不是一条简单的异常说明.
  • -dontwarn {class_filter}声明不输出那些未找到的引用和一些错误,继续混淆.配置中的class_filter是一串正则表达式.

通配符匹配规则

?      匹配单个字符
*      匹配类名中的任何部分,但不包含额外的包名
**    匹配类名中的任何部分,并且可以包含额外的包名
%    匹配任何基础类型的类型名
...    匹配认识数量 任意类型的参数
<init>   匹配任何构造器
<ifield>  匹配任何字段名
*(当用在类内部时)   匹配任何字段和方法
$     指内部类 
作者:lucky9322 发表于2016/10/22 23:11:45 原文链接
阅读:138 评论:0 查看评论

Activity的生命周期

$
0
0

Activity生命周期可分为两部分,一个是典型情况下的生命周期,两一个是异常情况下的生命周期。典型的情况为:有用户参与的情况下activity所经历的生命周期,异常情况为:activity被系统回收或者当前设备的Configuration发生改变到时activity被销毁。

概念介绍:

一、典型情况下的生命周期分析。
该情况下的生命周期有七个,分别是:
1) onCreate(),表示activity被创建,这是生命周期的第一个方法,一般用来初始化工作。初始化所需要的数据和布局资源等等。
2) onRestart(),表示activity被重新启动,一般当前activity从不可见变为可见状态时调用。比如用户按下home键切换到桌面,或者打开新的activity之后用户有返回到该activity,
3) onStart(),表示activity正被启动,该activity已经可见,但是还没有出现在前台,还无法和用户进行交互。只是我们肉眼看不见该activity而已。
4) onResume(),表示该activity已经出现并且我们肉眼已经可以看见。
5) onPause(),表示activity正在停止。
6) onStop(),表示activity即将停止,可以做一些稍微重量级的回收工作,但不能进行耗时操作。
7) onDestroy(),表示activity即将被销毁,这是activity生命周期的最后的一个回调。我们可以进行回收工作和一些必要的资源释放。

Activity的生命周期图如下:

针对与上图分为如下几种情况:

1) 当一个特定的Activity首次启动时,回调方法如下:onCreate -> onStart -> onResume。
2) 当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause -> onStop。有一种特出情况,就是当Activity采用了透明主题时,当前Activity将不会回调onStop。
3) 当用户再次回到原Activity时 方法回调如下:onRestart -> onStart -> onResume。
4) 当back键回退时方法调用如下:onPause -> onStop -> onDestroy。

提出Activity的问题:

假设Activity为A,用户再打开一个ActivityB,那么B的onResume和A的onPause那个先执行。
     在启动Activity的请求会有Instrumentation来处理,然后通过Binder向AMS(ActivityManagerService)发送请求。AMS维护着一个ActivityStack并负责栈内的Activity的状态同步,AMS通过ActivityThread去同步Activity的状态从而完成生命周期方法的调用。在ActivityStack中resumeTopActivityInnerLocked方法中有源码表明,在新的Activity启动之前栈顶的Activity需要先onPause后,新的Activity才能启动,所以A先onPause然后B在onResume。

我们写一个简单的例子来验证一下,demo源码如下:

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

    private String tag = "MainActivity ------ ";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,ActivityB.class);
                startActivity(intent);
            }
        });
        Log.e(tag,"onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e(tag,"onStart");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(tag,"onPause");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(tag,"onDestroy");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.e(tag,"onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(tag,"onResume");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e(tag,"onStop");
    }
}
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

/**
 * Created by lwp940118 on 2016/10/22.
 */
public class ActivityB extends Activity{

    private String tag = "ActivityB ------ ";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activvityb);
        Log.e(tag,"onCreate");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.e(tag,"onStart");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(tag,"onPause");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e(tag,"onDestroy");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.e(tag,"onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(tag,"onResume");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.e(tag,"onStop");
    }
}

demo的Log打印截图如下:

二、异常情况下的上面周期分析
1) 资源相关的系统配置发生改变导致Activity被杀死并重新创建。比如手机现在处于竖屏状态,突然变为横屏状态,在默认的情况下,系统配置将发生改变,Activity就会被销毁并且重新创建。我们也可以选择阻止系统创建Activity。当系统配置发生变化后Activity会被其销毁,其onPause、onStop、onDestroy均会被调用,由于Activity为异常终止的则系统会调用onSaveInstanceState来保存当前的Activity状态,当Activity被重新创建之后系统调用onRestoreInstanceState并把Activity销毁时的onSaveInstanceState方法所保存的Bundle对象座位参数传递给onRestoreInstanceState和onCreate方法来恢复数据和Activity的状态。
2) 资源内存不足导致优先级底的Activity被杀死。Activity按照优先级高低可以分为一下三种情况:
(1) 前台Activity—正在和用户交互的Activity优先级最高
(2) 可见但非前台的Activity — 比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户进行交互。
(3) 后台Activity— 已经被暂停的Activity,比如执行了onStop,优先级最低。
当系统内存不足时,系统就会根据优先级高低去杀死Activity,并在后序通过onSaveInstanceState、onRestoreInstanceState方法来存储和恢复数据。如果一个进程中没有四大组件子啊执行,那么这个进程很快就会被杀死。所以后天工作不适合脱离四大组件,一般都是将后台工作放在Service中并保证有一定的优先级。这样就不容易被杀死。
demo下载地址:(http://download.csdn.net/detail/qq_30000411/9661130)
Activity的启动模式将在以后推出。

作者:qq_30000411 发表于2016/10/22 23:16:27 原文链接
阅读:179 评论:0 查看评论

Android 悬浮窗各机型各系统适配大全

$
0
0

  这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时,感兴趣或者找到其他机型适配方法的请留言告诉我,或者加群544645972一起交流一下,非常感谢~
  相关权限请看我的另一篇博客:android permission权限与安全机制解析(下),或者关于权限的案例使用:android WindowManager解析与骗取QQ密码案例分析
  转载请注明出处:http://blog.csdn.net/self_study/article/details/52859790

悬浮窗适配

  悬浮窗适配有两种方法:第一种是按照正规的流程,如果系统没有赋予 APP 弹出悬浮窗的权限,就先跳转到权限授权界面,等用户打开该权限之后,再去弹出悬浮窗,比如 QQ 等一些主流应用就是这么做得;第二种就是利用系统的漏洞,绕过权限的申请,简单粗暴,这种方法我不是特别建议,但是现在貌似有些应用就是这样,比如 UC 和有道词典,这样适配在大多数手机上都是 OK 的,但是在一些特殊的机型不行,比如某米的 miui8。

正常适配流程

  在 4.4~5.1.1 版本之间,和 6.0~最新版本之间的适配方法是不一样的,之前的版本由于 google 并没有对这个权限进行单独处理,所以是各家手机厂商根据需要定制的,所以每个权限的授权界面都各不一样,适配起来难度较大,6.0 之后适配起来就相对简单很多了。

Android 4.4 ~ Android 5.1.1

  由于判断权限的类 AppOpsManager 是 API19 版本添加,所以Android 4.4 之前的版本(不包括4.4)就不用去判断了,直接调用 WindowManager 的 addView 方法弹出即可,但是貌似有些特殊的手机厂商在 API19 版本之前就已经自定义了悬浮窗权限,如果有发现的,请联系我。
  众所周知,国产手机的种类实在是过于丰富,而且一个品牌的不同版本还有不一样的适配方法,比如某米(嫌弃脸),所以我在实际适配的过程中总结了几种通用的方法, 大家可以参考一下:

  • 直接百度一下,搜索关键词“小米手机悬浮窗适配”等;
  • 看看 QQ 或者其他的大公司 APP 是否已经适配,如果已经适配,跳转到相关权限授权页面之后,或者自己能够直接在设置里找到悬浮窗权限授权页面也是一个道理,使用 adb shell dumpsys activity 命令,找到相关的信息,如下图所示这里写图片描述
    可以清楚看到授权 acitivity 页面的包名和 activity 名,而且可以清楚地知道跳转的 intent 是否带了 extra,如果没有 extra 就可以直接跳入,如果带上了 extra,百度一下该 activity 的名字,看能否找到有用信息,比如适配方案或者源码 APK 之类的;
  • 依旧利用上面的方法,找到 activity 的名字,然后 root 准备适配的手机,直接在相关目录 /system/app 下把源码 APK 拷贝出来,反编译,根据 activity 的名字找到相关代码,之后的事情就简单了;
  • 还有一个方法就是发动人力资源去找,看看已经适配该手机机型的 app 公司是否有自己认识的人,或者干脆点,直接找这个手机公司里面是否有自己认识的手机开发朋友,直接询问,方便快捷。

常规手机

  由于 6.0 之前的版本常规手机并没有把悬浮窗权限单独拿出来,所以正常情况下是可以直接使用 WindowManager.addView 方法直接弹出悬浮窗。

小米

  首先需要适配的就应该是小米了,而且比较麻烦的事情是,miui 的每个版本适配方法都是不一样的,所以只能每个版本去单独适配,不过还好由于使用的人数多,网上的资料也比较全。首先第一步当然是判断是否赋予了悬浮窗权限,这个时候就需要使用到 AppOpsManager 这个类了,它里面有一个 checkop 方法:

/**
 * Do a quick check for whether an application might be able to perform an operation.
 * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
 * or {@link #startOp(int, int, String)} for your actual security checks, which also
 * ensure that the given uid and package name are consistent.  This function can just be
 * used for a quick check to see if an operation has been disabled for the application,
 * as an early reject of some work.  This does not modify the time stamp or other data
 * about the operation.
 * @param op The operation to check.  One of the OP_* constants.
 * @param uid The user id of the application attempting to perform the operation.
 * @param packageName The name of the application attempting to perform the operation.
 * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
 * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
 * causing the app to crash).
 * @throws SecurityException If the app has been configured to crash on this op.
 * @hide
 */
public int checkOp(int op, int uid, String packageName) {
    try {
        int mode = mService.checkOperation(op, uid, packageName);
        if (mode == MODE_ERRORED) {
            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
        }
        return mode;
    } catch (RemoteException e) {
    }
    return MODE_IGNORED;
}

找到悬浮窗权限的 op 值是:

/** @hide */
public static final int OP_SYSTEM_ALERT_WINDOW = 24;

注意到这个函数和这个值其实都是 hide 的,所以没办法,你懂的,只能用反射:

/**
 * 检测 miui 悬浮窗权限
 */
public static boolean checkFloatWindowPermission(Context context) {
    final int version = Build.VERSION.SDK_INT;

    if (version >= 19) {
        return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
    } else {
//            if ((context.getApplicationInfo().flags & 1 << 27) == 1) {
//                return true;
//            } else {
//                return false;
//            }
        return true;
    }
}

@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean checkOp(Context context, int op) {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 19) {
        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        try {
            Class clazz = AppOpsManager.class;
            Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
            return AppOpsManager.MODE_ALLOWED == (int)method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
        } catch (Exception e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    } else {
        Log.e(TAG, "Below API 19 cannot invoke!");
    }
    return false;
}

检测完成之后就是跳转到授权页面去开启权限了,但是由于 miui 不同版本的权限授权页面不一样,所以需要根据不同版本进行不同处理:

/**
 * 获取小米 rom 版本号,获取失败返回 -1
 *
 * @return miui rom version code, if fail , return -1
 */
public static int getMiuiVersion() {
    String version = RomUtils.getSystemProperty("ro.miui.ui.version.name");
    if (version != null) {
        try {
            return Integer.parseInt(version.substring(1));
        } catch (Exception e) {
            Log.e(TAG, "get miui version code error, version : " + version);
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }
    return -1;
}

/**
 * 小米 ROM 权限申请
 */
public static void applyMiuiPermission(Context context) {
    int versionCode = getMiuiVersion();
    if (versionCode == 5) {
        goToMiuiPermissionActivity_V5(context);
    } else if (versionCode == 6) {
        goToMiuiPermissionActivity_V6(context);
    } else if (versionCode == 7) {
        goToMiuiPermissionActivity_V7(context);
    } else {
        Log.e(TAG, "this is a special MIUI rom version, its version code " + versionCode);
    }
}

private static boolean isIntentAvailable(Intent intent, Context context) {
    if (intent == null) {
        return false;
    }
    return context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}

/**
 * 小米 V5 版本 ROM权限申请
 */
public static void goToMiuiPermissionActivity_V5(Context context) {
    Intent intent = null;
    String packageName = context.getPackageName();
    intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
    Uri uri = Uri.fromParts("package" , packageName, null);
    intent.setData(uri);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    if (isIntentAvailable(intent, context)) {
        context.startActivity(intent);
    } else {
        Log.e(TAG, "intent is not available!");
    }

    //设置页面在应用详情页面
//        Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
//        PackageInfo pInfo = null;
//        try {
//            pInfo = context.getPackageManager().getPackageInfo
//                    (HostInterfaceManager.getHostInterface().getApp().getPackageName(), 0);
//        } catch (PackageManager.NameNotFoundException e) {
//            AVLogUtils.e(TAG, e.getMessage());
//        }
//        intent.setClassName("com.android.settings", "com.miui.securitycenter.permission.AppPermissionsEditor");
//        intent.putExtra("extra_package_uid", pInfo.applicationInfo.uid);
//        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//        if (isIntentAvailable(intent, context)) {
//            context.startActivity(intent);
//        } else {
//            AVLogUtils.e(TAG, "Intent is not available!");
//        }
}

/**
 * 小米 V6 版本 ROM权限申请
 */
public static void goToMiuiPermissionActivity_V6(Context context) {
    Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
    intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
    intent.putExtra("extra_pkgname", context.getPackageName());
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    if (isIntentAvailable(intent, context)) {
        context.startActivity(intent);
    } else {
        Log.e(TAG, "Intent is not available!");
    }
}

/**
 * 小米 V7 版本 ROM权限申请
 */
public static void goToMiuiPermissionActivity_V7(Context context) {
    Intent intent = new Intent("miui.intent.action.APP_PERM_EDITOR");
    intent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
    intent.putExtra("extra_pkgname", context.getPackageName());
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    if (isIntentAvailable(intent, context)) {
        context.startActivity(intent);
    } else {
        Log.e(TAG, "Intent is not available!");
    }
}

getSystemProperty 方法是直接调用 getprop 方法来获取系统信息:

public static String getSystemProperty(String propName) {
    String line;
    BufferedReader input = null;
    try {
        Process p = Runtime.getRuntime().exec("getprop " + propName);
        input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
        line = input.readLine();
        input.close();
    } catch (IOException ex) {
        Log.e(TAG, "Unable to read sysprop " + propName, ex);
        return null;
    } finally {
        if (input != null) {
            try {
                input.close();
            } catch (IOException e) {
                Log.e(TAG, "Exception while closing InputStream", e);
            }
        }
    }
    return line;
}

最新的 V8 版本由于已经是 6.0 ,所以就是下面介绍到 6.0 的适配方法了。

魅族

  魅族的适配,由于我司魅族的机器相对较少,所以只适配了 flyme5.1.1/android 5.1.1 版本 mx4 pro 的系统。和小米一样,首先也要通过 API19 版本添加的 AppOpsManager 类判断是否授予了权限:

/**
 * 检测 meizu 悬浮窗权限
 */
public static boolean checkFloatWindowPermission(Context context) {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 19) {
        return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
    }
    return true;
}

@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean checkOp(Context context, int op) {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 19) {
        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        try {
            Class clazz = AppOpsManager.class;
            Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
            return AppOpsManager.MODE_ALLOWED == (int)method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
        } catch (Exception e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    } else {
        Log.e(TAG, "Below API 19 cannot invoke!");
    }
    return false;
}

然后是跳转去悬浮窗权限授予界面:

/**
 * 去魅族权限申请页面
 */
public static void applyPermission(Context context){
    Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
    intent.setClassName("com.meizu.safe", "com.meizu.safe.security.AppSecActivity");
    intent.putExtra("packageName", context.getPackageName());
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(intent);
}

如果有魅族其他版本的适配方案,请联系我。

华为

  华为的适配是根据网上找的方案,外加自己的一些优化而成,但是由于华为手机的众多机型,所以覆盖的机型和系统版本还不是那么全面,如果有其他机型和版本的适配方案,请联系我,我更新到 github 上。和小米,魅族一样,首先通过 AppOpsManager 来判断权限是否已经授权:

/**
 * 检测 Huawei 悬浮窗权限
 */
public static boolean checkFloatWindowPermission(Context context) {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 19) {
        return checkOp(context, 24); //OP_SYSTEM_ALERT_WINDOW = 24;
    }
    return true;
}

@TargetApi(Build.VERSION_CODES.KITKAT)
private static boolean checkOp(Context context, int op) {
    final int version = Build.VERSION.SDK_INT;
    if (version >= 19) {
        AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        try {
            Class clazz = AppOpsManager.class;
            Method method = clazz.getDeclaredMethod("checkOp", int.class, int.class, String.class);
            return AppOpsManager.MODE_ALLOWED == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());
        } catch (Exception e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    } else {
        Log.e(TAG, "Below API 19 cannot invoke!");
    }
    return false;
}

然后根据不同的机型和版本跳转到不同的页面:

/**
 * 去华为权限申请页面
 */
public static void applyPermission(Context context) {
    try {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//   ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理
//   ComponentName comp = new ComponentName("com.huawei.systemmanager",
//      "com.huawei.permissionmanager.ui.SingleAppActivity");//华为权限管理,跳转到指定app的权限管理位置需要华为接口权限,未解决
        ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面
        intent.setComponent(comp);
        if (RomUtils.getEmuiVersion() == 3.1) {
            //emui 3.1 的适配
            context.startActivity(intent);
        } else {
            //emui 3.0 的适配
            comp = new ComponentName("com.huawei.systemmanager", "com.huawei.notificationmanager.ui.NotificationManagmentActivity");//悬浮窗管理页面
            intent.setComponent(comp);
            context.startActivity(intent);
        }
    } catch (SecurityException e) {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//   ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.permissionmanager.ui.MainActivity");//华为权限管理
        ComponentName comp = new ComponentName("com.huawei.systemmanager",
                "com.huawei.permissionmanager.ui.MainActivity");//华为权限管理,跳转到本app的权限管理页面,这个需要华为接口权限,未解决
//      ComponentName comp = new ComponentName("com.huawei.systemmanager","com.huawei.systemmanager.addviewmonitor.AddViewMonitorActivity");//悬浮窗管理页面
        intent.setComponent(comp);
        context.startActivity(intent);
        Log.e(TAG, Log.getStackTraceString(e));
    } catch (ActivityNotFoundException e) {
        /**
         * 手机管家版本较低 HUAWEI SC-UL10
         */
//   Toast.makeText(MainActivity.this, "act找不到", Toast.LENGTH_LONG).show();
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        ComponentName comp = new ComponentName("com.Android.settings", "com.android.settings.permission.TabItem");//权限管理页面 android4.4
//   ComponentName comp = new ComponentName("com.android.settings","com.android.settings.permission.single_app_activity");//此处可跳转到指定app对应的权限管理页面,但是需要相关权限,未解决
        intent.setComponent(comp);
        context.startActivity(intent);
        e.printStackTrace();
        Log.e(TAG, Log.getStackTraceString(e));
    } catch (Exception e) {
        //抛出异常时提示信息
        Toast.makeText(context, "进入设置页面失败,请手动设置", Toast.LENGTH_LONG).show();
        Log.e(TAG, Log.getStackTraceString(e));
    }
}

emui4 之后就是 6.0 版本了,按照下面介绍的 6.0 适配方案即可。

Android 6.0 及之后版本

  我在博客android permission权限与安全机制解析(下)- SYSTEM_ALERT_WINDOW中已经介绍到了适配方案,悬浮窗权限在 6.0 之后就被 google 单独拿出来管理了,好处就是对我们来说适配就非常方便了,在所有手机和 6.0 以及之后的版本上适配的方法都是一样的,首先要在 Manifest 中静态申请<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />权限,然后在使用时先判断该权限是否已经被授权,如果没有授权使用下面这段代码进行动态申请:

private static final int REQUEST_CODE = 1;

//判断权限
private boolean commonROMPermissionCheck(Context context) {
    Boolean result = true;
    if (Build.VERSION.SDK_INT >= 23) {
        try {
            Class clazz = Settings.class;
            Method canDrawOverlays = clazz.getDeclaredMethod("canDrawOverlays", Context.class);
            result = (Boolean) canDrawOverlays.invoke(null, context);
        } catch (Exception e) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }
    return result;
}

//申请权限
private void requestAlertWindowPermission() {
    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
    intent.setData(Uri.parse("package:" + getPackageName()));
    startActivityForResult(intent, REQUEST_CODE);
}

@Override
//处理回调
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE) {
        if (Settings.canDrawOverlays(this)) {
            Log.i(LOGTAG, "onActivityResult granted");
        }
    }
}

上述代码需要注意的是:

  • 使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION 启动隐式Intent;
  • 使用 “package:” + getPackageName() 携带App的包名信息;
  • 使用 Settings.canDrawOverlays 方法判断授权结果。
在用户开启相关权限之后才能使用 WindowManager.LayoutParams.TYPE_SYSTEM_ERROR ,要不然是会直接崩溃的哦。

特殊适配流程

  如何绕过系统的权限检查,直接弹出悬浮窗?android WindowManager解析与骗取QQ密码案例分析这篇博客中我已经指明出来了,需要使用mParams.type = WindowManager.LayoutParams.TYPE_TOAST; 来取代 mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;,这样就可以达到不申请权限,而直接弹出悬浮窗,至于原因嘛,我们看看 PhoneWindowManager 源码的关键处:

@Override
public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
    ....
    switch (type) {
        case TYPE_TOAST:
            // XXX right now the app process has complete control over
            // this...  should introduce a token to let the system
            // monitor/control what they are doing.
            outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW;
            break;
        case TYPE_DREAM:
        case TYPE_INPUT_METHOD:
        case TYPE_WALLPAPER:
        case TYPE_PRIVATE_PRESENTATION:
        case TYPE_VOICE_INTERACTION:
        case TYPE_ACCESSIBILITY_OVERLAY:
            // The window manager will check these.
            break;
        case TYPE_PHONE:
        case TYPE_PRIORITY_PHONE:
        case TYPE_SYSTEM_ALERT:
        case TYPE_SYSTEM_ERROR:
        case TYPE_SYSTEM_OVERLAY:
            permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
            outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
            break;
        default:
            permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
    }
    if (permission != null) {
        if (permission == android.Manifest.permission.SYSTEM_ALERT_WINDOW) {
            final int callingUid = Binder.getCallingUid();
            // system processes will be automatically allowed privilege to draw
            if (callingUid == Process.SYSTEM_UID) {
                return WindowManagerGlobal.ADD_OKAY;
            }

            // check if user has enabled this operation. SecurityException will be thrown if
            // this app has not been allowed by the user
            final int mode = mAppOpsManager.checkOp(outAppOp[0], callingUid,
                    attrs.packageName);
            switch (mode) {
                case AppOpsManager.MODE_ALLOWED:
                case AppOpsManager.MODE_IGNORED:
                    // although we return ADD_OKAY for MODE_IGNORED, the added window will
                    // actually be hidden in WindowManagerService
                    return WindowManagerGlobal.ADD_OKAY;
                case AppOpsManager.MODE_ERRORED:
                    return WindowManagerGlobal.ADD_PERMISSION_DENIED;
                default:
                    // in the default mode, we will make a decision here based on
                    // checkCallingPermission()
                    if (mContext.checkCallingPermission(permission) !=
                            PackageManager.PERMISSION_GRANTED) {
                        return WindowManagerGlobal.ADD_PERMISSION_DENIED;
                    } else {
                        return WindowManagerGlobal.ADD_OKAY;
                    }
            }
        }

        if (mContext.checkCallingOrSelfPermission(permission)
                != PackageManager.PERMISSION_GRANTED) {
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }
    }
    return WindowManagerGlobal.ADD_OKAY;
}

从源码中可以看到,其实 TYPE_TOAST 没有做权限检查,直接返回了 WindowManagerGlobal.ADD_OKAY,所以呢,这就是为什么可以绕过权限的原因。还有需要注意的一点是 addView 方法中会调用到 mPolicy.adjustWindowParamsLw(win.mAttrs);,这个方法在不同的版本有不同的实现:

//Android 2.0 - 2.3.7 PhoneWindowManager
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
    switch (attrs.type) {
        case TYPE_SYSTEM_OVERLAY:
        case TYPE_SECURE_SYSTEM_OVERLAY:
        case TYPE_TOAST:
            // These types of windows can't receive input events.
            attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            break;
    }
}

//Android 4.0.1 - 4.3.1 PhoneWindowManager
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
    switch (attrs.type) {
        case TYPE_SYSTEM_OVERLAY:
        case TYPE_SECURE_SYSTEM_OVERLAY:
        case TYPE_TOAST:
            // These types of windows can't receive input events.
            attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            break;
    }
}

//Android 4.4 PhoneWindowManager
@Override
public void adjustWindowParamsLw(WindowManager.LayoutParams attrs) {
    switch (attrs.type) {
        case TYPE_SYSTEM_OVERLAY:
        case TYPE_SECURE_SYSTEM_OVERLAY:
            // These types of windows can't receive input events.
            attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
            attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
            break;
    }
}

可以看到,在4.0.1以前, 当我们使用 TYPE_TOAST, Android 会偷偷给我们加上 FLAG_NOT_FOCUSABLE 和 FLAG_NOT_TOUCHABLE,4.0.1 开始,会额外再去掉FLAG_WATCH_OUTSIDE_TOUCH,这样真的是什么事件都没了。而 4.4 开始,TYPE_TOAST 被移除了, 所以从 4.4 开始,使用 TYPE_TOAST 的同时还可以接收触摸事件和按键事件了,而4.4以前只能显示出来,不能交互,所以 API18 及以下使用 TYPE_TOAST 是无法接收触摸事件的,但是幸运的是除了 miui 之外,这些版本可以直接在 Manifest 文件中声明 android.permission.SYSTEM_ALERT_WINDOW权限,然后直接使用 WindowManager.LayoutParams.TYPE_PHONE 或者 WindowManager.LayoutParams.TYPE_SYSTEM_ALERT 都是可以直接弹出悬浮窗的。
  还有一个需要提到的是 TYPE_APPLICATION,这个 type 是配合 Activity 在当前 APP 内部使用的,也就是说,回到 Launcher 界面,这个悬浮窗是会消失的。
  虽然这种方法确确实实可以绕过权限,至于适配的坑呢,有人遇到之后可以联系我,我会持续完善。不过由于这样可以不申请权限就弹出悬浮窗,而且在最新的 6.0+ 系统上也没有修复,所以如果这个漏洞被滥用,就会造成一些意想不到的后果,因此我个人倾向于使用 QQ 的适配方案,也就是上面的正常适配流程去处理这个权限。

源码下载

  https://github.com/zhaozepeng/FloatWindowPermission

引用

http://www.jianshu.com/p/167fd5f47d5c
http://www.liaohuqiu.net/cn/posts/android-windows-manager/
http://blog.csdn.net/mzm489321926/article/details/50542065
http://www.jianshu.com/p/634cd056b90c

作者:zhao_zepeng 发表于2016/10/23 15:23:17 原文链接
阅读:108 评论:0 查看评论

Android -- 系统进程Zygote的启动分析

$
0
0

Android -- 系统进程Zygote的启动分析


我们知道,Android系统是基于Linux内核的。Linux中,所有的进程都是由init进程创建出来的;也就是说,所有的进程都是直接或间接被init进程fork产生的。Android进程的孵化器Zygote进程也是如此,它也是在系统启动过程中,被init进程创建出来的。Android系统启动时,会解析init.rc初始化文件,其中就包含启动Zygote进程的命令:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart media
    onrestart restart netd

关键字service告诉我们要创建一个名为Zygote的进程,它所要执行的应用程序是/system/bin/app_process;之后的是传入的参数:
  1. -Xzygote:jvm使用的参数
  2. /system/bin:一个未被使用的父目录
  3. --zygote、--start--system--server:启动Zygote进程要出使用的参数
socket关键字说明该进程需要创建一个套接字资源用于进程间通信,类型是unix domain socket,权限设置为660。onrestart关键字描述的都是该进程重启时需要执行的命令操作。当init.c文件执行时,解析到这个服务,就会去执行zygote进程对应的应用程序。
它对应的文件是/frameworks/base/cmds/app_process/app_main.cpp;直接看它的main()函数:
int main(int argc, char* const argv[])
{
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
        // Older kernels don't understand PR_SET_NO_NEW_PRIVS and return
        // EINVAL. Don't die on such kernels.
        if (errno != EINVAL) {
            LOG_ALWAYS_FATAL("PR_SET_NO_NEW_PRIVS failed: %s", strerror(errno));
            return 12;
        }
    }
    //AppRuntime是AndroidRuntime的子类,这里初始化runtime对象时Androidruntime中的gCurRuntime变量会被初始化为AppRuntime对象:runtime
    AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));
    // Process command line arguments
    // ignore argv[0]
    argc--;
    argv++;

    // Everything up to '--' or first non '-' arg goes to the vm.
    //
    // The first argument after the VM args is the "parent dir", which
    // is currently unused.
    //
    // After the parent dir, we expect one or more the following internal
    // arguments :
    //
    // --zygote : Start in zygote mode
    // --start-system-server : Start the system server.
    // --application : Start in application (stand alone, non zygote) mode.
    // --nice-name : The nice name for this process.
    //
    // For non zygote starts, these arguments will be followed by
    // the main class name. All remaining arguments are passed to
    // the main method of this class.
    //
    // For zygote starts, all remaining arguments are passed to the zygote.
    // main function.
    //
    // Note that we must copy argument string values since we will rewrite the
    // entire argument block when we apply the nice name to argv0.

    int i;
    for (i = 0; i < argc; i++) {
        if (argv[i][0] != '-') {
            break;
        }
        if (argv[i][1] == '-' && argv[i][2] == 0) {
            ++i; // Skip --.
            break;
        }
        runtime.addOption(strdup(argv[i]));
    }

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    String8 niceName;
    String8 className;

    ++i;  // Skip unused "parent dir" argument.
    while (i < argc) {//忽略第一个参数:-Xzygote
        const char* arg = argv[i++];
        if (strcmp(arg, "--zygote") == 0) { //由参数列表可知,该项成立
            zygote = true;
            niceName = ZYGOTE_NICE_NAME;
        } else if (strcmp(arg, "--start-system-server") == 0) {//由参数列表可知,该项成立
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName.setTo(arg + 12);
        } else if (strncmp(arg, "--", 2) != 0) {
            className.setTo(arg);
            break;
        } else {
            --i;
            break;
        }
    }

    Vector<String8> args;//启动Zygote进程时使用的参数列表
    if (!className.isEmpty()) {
        // We're not in zygote mode, the only argument we need to pass
        // to RuntimeInit is the application argument.
        //
        // The Remainder of args get passed to startup class main(). Make
        // copies of them before we overwrite them with the process name.
        args.add(application ? String8("application") : String8("tool"));
        runtime.setClassNameAndArgs(className, argc - i, argv + i);
    } else {
        // We're in zygote mode.
        maybeCreateDalvikCache();

        if (startSystemServer) {
            args.add(String8("start-system-server"));//添加参数
        }

        char prop[PROP_VALUE_MAX];
        if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {
            LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",
                ABI_LIST_PROPERTY);
            return 11;
        }

        String8 abiFlag("--abi-list=");
        abiFlag.append(prop);
        args.add(abiFlag);//添加参数

        // In zygote mode, pass all remaining arguments to the zygote
        // main() method.
        for (; i < argc; ++i) {
            args.add(String8(argv[i]));//添加剩余的参数
        }
    }

    if (!niceName.isEmpty()) {
        runtime.setArgv0(niceName.string());
        set_process_name(niceName.string());
    }

    if (zygote) {
        runtime.start("com.android.internal.os.ZygoteInit", args, zygote);//附带参数列表,在Zygote模式下,同过AndroidRuntime::start()启动Zygote进程
    } else if (className) {
        runtime.start("com.android.internal.os.RuntimeInit", args, zygote);
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}
进入AndroidRuntime::start()函数:
/*
 * Start the Android runtime.  This involves starting the virtual machine
 * and calling the "static void main(String[] args)" method in the class
 * named by "className".
 *
 * Passes the main function two arguments, the class name and the specified
 * options string.
 */
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
    ALOGD(">>>>>> START %s uid %d <<<<<<\n",
            className != NULL ? className : "(unknown)", getuid());

    static const String8 startSystemServer("start-system-server");

    /*
     * 'startSystemServer == true' means runtime is obsolete and not run from
     * init.rc anymore, so we print out the boot start event here.
     */
    for (size_t i = 0; i < options.size(); ++i) {
        if (options[i] == startSystemServer) {
           /* track our progress through the boot sequence */
           const int LOG_BOOT_PROGRESS_START = 3000;
           LOG_EVENT_LONG(LOG_BOOT_PROGRESS_START,  ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
        }
    }

    const char* rootDir = getenv("ANDROID_ROOT");
    if (rootDir == NULL) {
        rootDir = "/system";
        if (!hasDir("/system")) {
            LOG_FATAL("No root directory specified, and /android does not exist.");
            return;
        }
        setenv("ANDROID_ROOT", rootDir, 1);
    }

    //const char* kernelHack = getenv("LD_ASSUME_KERNEL");
    //ALOGD("Found LD_ASSUME_KERNEL='%s'\n", kernelHack);

    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
    JNIEnv* env;
    if (startVm(&mJavaVM, &env, zygote) != 0) {//1、启动虚拟机
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) { //2、注册所需的JNI函数
        ALOGE("Unable to register all android natives\n");
        return;
    }

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) {
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    }

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    char* slashClassName = toSlashClassName(className);//com.android.internal.os.ZygoteInit
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");//通过JNI获取com.android.internal.os.ZygoteInit类的main()方法的jmethodID值
        if (startMeth == NULL) {
            ALOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);//通过JNI调用com.android.internal.os.ZygoteInit类的main()方法,进入Java层代码

#if 0
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        }
    }
    free(slashClassName);

    ALOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        ALOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        ALOGW("Warning: VM did not shut down cleanly\n");
}
start()函数中会进行启动虚拟机、注册JNI方法的预处理,最后会通过JNI的方式在native代码中调用ZygoteInit.java的main()函数,处理流程转而进入Java层。
ZygoteInit类是zygote进程的启动类,看它的main()函数:
 public static void main(String argv[]) {
        try {
            RuntimeInit.enableDdms();
            // Start profiling the zygote initialization.
            SamplingProfilerIntegration.start();

            boolean startSystemServer = false;
            String socketName = "zygote";
            String abiList = null;
            for (int i = 1; i < argv.length; i++) {
                if ("start-system-server".equals(argv[i])) {
                    startSystemServer = true; //该标志为true
                } else if (argv[i].startsWith(ABI_LIST_ARG)) {
                    abiList = argv[i].substring(ABI_LIST_ARG.length());
                } else if (argv[i].startsWith(SOCKET_NAME_ARG)) {
                    socketName = argv[i].substring(SOCKET_NAME_ARG.length());//值为zygote
                } else {
                    throw new RuntimeException("Unknown command line argument: " + argv[i]);
                }
            }

            if (abiList == null) {
                throw new RuntimeException("No ABI list supplied.");
            }

            registerZygoteSocket(socketName); // 1、创建socket,用来与ActivityManagerService进行通信
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                SystemClock.uptimeMillis());
            preload(); // 2、预加载资源文件
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                SystemClock.uptimeMillis());

            // Finish profiling the zygote initialization.
            SamplingProfilerIntegration.writeZygoteSnapshot();

            // Do an initial gc to clean up after startup
            gcAndFinalize();

            // Disable tracing so that forked processes do not inherit stale tracing tags from
            // Zygote.
            Trace.setTracingEnabled(false);

            if (startSystemServer) {
                startSystemServer(abiList, socketName); // 3、启动system_server进程
            }

            Log.i(TAG, "Accepting command socket connections");
            runSelectLoop(abiList); // 4、开启一个循环,处理ActivityManagerService创建应用进程的请求

            closeServerSocket();  // 程序退出时,清除socket资源
        } catch (MethodAndArgsCaller caller) {
            caller.run(); // 5、注意
        } catch (RuntimeException ex) {
            Log.e(TAG, "Zygote died with exception", ex);
            closeServerSocket();
            throw ex;
        }
    }
代码中共标记出了5个较为重要的处理过程,下面一一分析。

(1)、registerZygoteSocket(socketName)

进入registerZygoteSocket(socketName)函数,查看其代码处理:
 /**
     * Registers a server socket for zygote command connections
     *
     * @throws RuntimeException when open fails
     */
    private static void registerZygoteSocket(String socketName) {
        if (sServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;// fullSocketName:ANDROID_SOCKET_zygote
            try {
                String env = System.getenv(fullSocketName);//获取该环境变量的值,即此socket对应的文件描述符
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
                sServerSocket = new LocalServerSocket(fd);//用该文件描述符创建一个LocalServerSocket对象,并开始监听该socket
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }
从系统环境变量中获取到“ANDROID_SOCKET_zygote”这个socket对应的文件描述符,创建LocalServerSocket对象并监听该socket;此时名为zygote的socket就可以接收消息了。细心地人可能发现了,在我们的分析过程中并没有看到socket和Zygote进程的创建过程。其实这个过程在init.cpp解析init.rc文件时,已经处理完成了。下面来看这一部分内容。
系统启动解析init.rc时,每当碰到一个由service关键字声明的服务,就会给他创建一个进程、并初始化该服务相关的资源;这些资源就包括socket的创建。
在init.cpp中,void service_start(struct service *svc, const char *dynamic_args)函数负责启动每个声明的service服务,我们提出一段重要的处理过程:
pid_t pid = fork();//创建一个进程
    if (pid == 0) {
        struct socketinfo *si;
        struct svcenvinfo *ei;
        char tmp[32];
        int fd, sz;

        umask(077);
        if (properties_initialized()) {
            get_property_workspace(&fd, &sz);
            snprintf(tmp, sizeof(tmp), "%d,%d", dup(fd), sz);
            add_environment("ANDROID_PROPERTY_WORKSPACE", tmp);
        }

        for (ei = svc->envvars; ei; ei = ei->next)
            add_environment(ei->name, ei->value);

        for (si = svc->sockets; si; si = si->next) { //socket创建
            int socket_type = (
                    !strcmp(si->type, "stream") ? SOCK_STREAM :
                        (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));
            int s = create_socket(si->name, socket_type,
                                  si->perm, si->uid, si->gid, si->socketcon ?: scon);
            if (s >= 0) {
                publish_socket(si->name, s);//socket发布
            }
        }

       ...
    }

当系统为每个service通过调用fork()创建进程时,如果发现需要创建socket,它就会通过调用create_socket()创建一个socket:
/*
 * create_socket - creates a Unix domain socket in ANDROID_SOCKET_DIR
 * ("/dev/socket") as dictated in init.rc. This socket is inherited by the
 * daemon. We communicate the file descriptor's value via the environment
 * variable ANDROID_SOCKET_ENV_PREFIX<name> ("ANDROID_SOCKET_foo").
 */
int create_socket(const char *name, int type, mode_t perm, uid_t uid,
                  gid_t gid, const char *socketcon)
{
    struct sockaddr_un addr;
    int fd, ret;
    char *filecon;

    if (socketcon)
        setsockcreatecon(socketcon);

    fd = socket(PF_UNIX, type, 0);
    if (fd < 0) {
        ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
        return -1;
    }

    if (socketcon)
        setsockcreatecon(NULL);

    memset(&addr, 0 , sizeof(addr));
    addr.sun_family = AF_UNIX;
    snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",
             name);//设置此socket的地址

    ret = unlink(addr.sun_path);
    if (ret != 0 && errno != ENOENT) {
        ERROR("Failed to unlink old socket '%s': %s\n", name, strerror(errno));
        goto out_close;
    }

    filecon = NULL;
    if (sehandle) {
        ret = selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK);
        if (ret == 0)
            setfscreatecon(filecon);
    }

    ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));//绑定该socket,启动listen在ZygoteInit::registerZygoteSocket()处理
    if (ret) {
        ERROR("Failed to bind socket '%s': %s\n", name, strerror(errno));
        goto out_unlink;
    }

    setfscreatecon(NULL);
    freecon(filecon);

    chown(addr.sun_path, uid, gid);
    chmod(addr.sun_path, perm);

    INFO("Created socket '%s' with mode '%o', user '%d', group '%d'\n",
         addr.sun_path, perm, uid, gid);

    return fd;//返回该socket的文件描述符

out_unlink:
    unlink(addr.sun_path);
out_close:
    close(fd);
    return -1;
}
socket创建完成后,要以环境变量键值对的形式把它发布到系统中:
static void publish_socket(const char *name, int fd)
{
    char key[64] = ANDROID_SOCKET_ENV_PREFIX;
    char val[64];

    strlcpy(key + sizeof(ANDROID_SOCKET_ENV_PREFIX) - 1,
            name,
            sizeof(key) - sizeof(ANDROID_SOCKET_ENV_PREFIX));
    snprintf(val, sizeof(val), "%d", fd);
    add_environment(key, val);//ANDROID_SOCKET_zygote -- socket的文件描述符

    /* make sure we don't close-on-exec */
    fcntl(fd, F_SETFD, 0);
}
到这里,socket的创建、注册处理流程就联系起来了。
ANDROID_SOCKET_ENV_PREFIX、ANDROID_SOCKET_DIR两个宏定义在/system/core/include/cutils/Socket.h中:
#define ANDROID_SOCKET_ENV_PREFIX	"ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR		"/dev/socket"

(2)、preload()

preload()函数的处理:
    static void preload() {
        Log.d(TAG, "begin preload");
        preloadClasses();//加载/system/etc/preloaded-classes中的类资源
        preloadResources();
        preloadOpenGL();
        preloadSharedLibraries();
        preloadTextResources();
        // Ask the WebViewFactory to do any initialization that must run in the zygote process,
        // for memory sharing purposes.
        WebViewFactory.prepareWebViewInZygote();
        Log.d(TAG, "end preload");
    }
这里调用了5个函数去加载需要使用的类资源、图片资源、库资源等。这几个函数功能单一,我们可以自己阅读代码;这里就不详述了。但由于这部分内容涉及到很多I/O操作,而且加载的资源较多,会影响Android系统启动的时间。一些开机时间优化就是在这一部分处理的。

(3)、startSystemServer()

startSystemServer()函数用于启动system_server进程:
 /**
     * Prepare the arguments and fork for the system server process.
     */
    private static boolean startSystemServer(String abiList, String socketName)
            throws MethodAndArgsCaller, RuntimeException {
        long capabilities = posixCapabilitiesAsBits(
            OsConstants.CAP_BLOCK_SUSPEND,
            OsConstants.CAP_KILL,
            OsConstants.CAP_NET_ADMIN,
            OsConstants.CAP_NET_BIND_SERVICE,
            OsConstants.CAP_NET_BROADCAST,
            OsConstants.CAP_NET_RAW,
            OsConstants.CAP_SYS_MODULE,
            OsConstants.CAP_SYS_NICE,
            OsConstants.CAP_SYS_RESOURCE,
            OsConstants.CAP_SYS_TIME,
            OsConstants.CAP_SYS_TTY_CONFIG
        );
        /* Hardcoded command line to start the system server */
        String args[] = {
            "--setuid=1000",
            "--setgid=1000",
            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,3001,3002,3003,3006,3007",
            "--capabilities=" + capabilities + "," + capabilities,
            "--nice-name=system_server",
            "--runtime-args",
            "com.android.server.SystemServer",
        };//创建system_server的参数列表。设置了进程的uid、gid和进程名
        
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try {
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            /* Request to fork the system server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);//根据参数,为systemserver创建进程
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* For child process */
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }

            handleSystemServerProcess(parsedArgs);//进程创建完毕后,调用该函数进一步处理
        }

        return true;
    }
首先根据设置的参数列表创建system_server进程,然后在子进程中调用handleSystemServerProcess()做进一步处理:
/**
     * Finish remaining work for the newly forked system server process.
     */
    private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws ZygoteInit.MethodAndArgsCaller {

        closeServerSocket();//根据fork()机制,system_server是zygote的子进程,它也拥有zygote这个socket资源;但由于system_server不需要使用socket,这里将它关闭

        // set umask to 0077 so new files and directories will default to owner-only permissions.
        Os.umask(S_IRWXG | S_IRWXO);

        if (parsedArgs.niceName != null) {
            Process.setArgV0(parsedArgs.niceName);//system_server
        }

        final String systemServerClasspath = Os.getenv("SYSTEMSERVERCLASSPATH");
        if (systemServerClasspath != null) {
            performSystemServerDexOpt(systemServerClasspath);//com.android.server.SystemServer
        }

        if (parsedArgs.invokeWith != null) {
            String[] args = parsedArgs.remainingArgs;
            // If we have a non-null system server class path, we'll have to duplicate the
            // existing arguments and append the classpath to it. ART will handle the classpath
            // correctly when we exec a new process.
            if (systemServerClasspath != null) {
                String[] amendedArgs = new String[args.length + 2];
                amendedArgs[0] = "-cp";
                amendedArgs[1] = systemServerClasspath;
                System.arraycopy(parsedArgs.remainingArgs, 0, amendedArgs, 2, parsedArgs.remainingArgs.length);
            }

            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    VMRuntime.getCurrentInstructionSet(), null, args);
        } else {
            ClassLoader cl = null;
            if (systemServerClasspath != null) {
                cl = new PathClassLoader(systemServerClasspath, ClassLoader.getSystemClassLoader());
                Thread.currentThread().setContextClassLoader(cl);
            }

            /*
             * Pass the remaining arguments to SystemServer.
             */
            RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs, cl);//重要
        }

        /* should never reach here */
    }
直接查看RuntimeInit.zygoteInit()函数:
 /**
     * The main function called when started through the zygote process. This
     * could be unified with main(), if the native code in nativeFinishInit()
     * were rationalized with Zygote startup.<p>
     *
     * Current recognized args:
     * <ul>
     *   <li> <code> [--] <start class name>  <args>
     * </ul>
     *
     * @param targetSdkVersion target SDK version
     * @param argv arg strings
     */
    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
        redirectLogStreams();

        commonInit();
        nativeZygoteInit();//调用AppRuntime.cpp::onZygoteInit(),开启线程池,用于Binder通信
        applicationInit(targetSdkVersion, argv, classLoader);//通过反射调用SystemServer.java的main函数
    }
函数主要做了两个处理:native层开启线程池,用于Binder通信;nativeZygoteInit()最终调用:
    virtual void AppRuntime::onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool();
    }
启动线程池,用于Binder通信。然后进入applicationInit():
    private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        // If the application calls System.exit(), terminate the process
        // immediately without running any shutdown hooks.  It is not possible to
        // shutdown an Android application gracefully.  Among other things, the
        // Android runtime shutdown hooks close the Binder driver, which can cause
        // leftover running threads to crash before the process actually exits.
        nativeSetExitWithoutCleanup(true);

        // We want to be fairly aggressive about heap utilization, to avoid
        // holding on to a lot of memory that isn't needed.
        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

        final Arguments args;
        try {
            args = new Arguments(argv);
        } catch (IllegalArgumentException ex) {
            Slog.e(TAG, ex.getMessage());
            // let the process exit
            return;
        }

        // The end of of the RuntimeInit event (see #zygoteInit).
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        // Remaining arguments are passed to the start class's static main
        invokeStaticMain(args.startClass, args.startArgs, classLoader);//通过反射调用SystemServer.java的main方法
    }
再看invokeStaticMain():
/**
     * Invokes a static "main(argv[]) method on class "className".
     * Converts various failing exceptions into RuntimeExceptions, with
     * the assumption that they will then cause the VM instance to exit.
     *
     * @param className Fully-qualified class name
     * @param argv Argument vector for main()
     * @param classLoader the classLoader to load {@className} with
     */
    private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        Class<?> cl;

        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });//获取SystemServer.java的main()函数的域名,但并没有立即调用main函数;
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }

        /*
         * This throw gets caught in ZygoteInit.main(), which responds
         * by invoking the exception's run() method. This arrangement
         * clears up all the stack frames that were required in setting
         * up the process.
         */
        throw new ZygoteInit.MethodAndArgsCaller(m, argv);//在ZygoteInit.java的main()中第5步处理时,调用SystemServer.java的main()函数
    }
通过ZygoteInit.MethodAndArgsCaller异常的处理来调用SystemServer.java的main()函数启动各个系统服务,看MethodAndArgsCaller的定义:
    /**
     * Helper exception class which holds a method and arguments and
     * can call them. This is used as part of a trampoline to get rid of
     * the initial process setup stack frames.
     */
    public static class MethodAndArgsCaller extends Exception
            implements Runnable {
        /** method to call */
        private final Method mMethod;

        /** argument array */
        private final String[] mArgs;

        public MethodAndArgsCaller(Method method, String[] args) {
            mMethod = method;
            mArgs = args;
        }

        public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }
    }
由代码注释可知:这种调用方式会清理堆栈,可以让SystemServer.java的main函数认为自己是system_server进程的入口,虽然这之前已经做了大量的工作。

(4)、 runSelectLoop()

runSelectLoop()函数处理如下:
/**
     * Runs the zygote process's select loop. Accepts new connections as
     * they happen, and reads commands from connections one spawn-request's
     * worth at a time.
     *
     * @throws MethodAndArgsCaller in a child process when a main() should
     * be executed.
     */
    private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(sServerSocket.getFileDescriptor());
        peers.add(null);

        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    boolean done = peers.get(i).runOnce();
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }
ZygoteConnection对象是一个socket连接,它请求要创建一个新应用进程。如果ActivityManagerService发送的请求被收到后,就会进入ZygoteConnection::runOnce()处理。
runOnce()的主要工作就是会为这个请求fork一个新的进程,并做一些其他的处理。

(5)、MethodAndArgsCaller异常处理

我们退回到ZygoteInit::main()函数中,看MethodAndArgsCaller异常的捕获处理过程:
catch (MethodAndArgsCaller caller) {
            caller.run(); // 5、注意
        } catch (RuntimeException ex) {
            Log.e(TAG, "Zygote died with exception", ex);
            closeServerSocket();
            throw ex;
        }
 /**
     * Helper exception class which holds a method and arguments and
     * can call them. This is used as part of a trampoline to get rid of
     * the initial process setup stack frames.
     */
    public static class MethodAndArgsCaller extends Exception
            implements Runnable {
        /** method to call */
        private final Method mMethod;

        /** argument array */
        private final String[] mArgs;

        public MethodAndArgsCaller(Method method, String[] args) {
            mMethod = method;
            mArgs = args;
        }

        public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });//调用mMethod本身代表的方法
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }
    }
run()方法中通过invoke()调用SystemServer.java的main()方法:
    /**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }
SystemServer.run()方法中做了大量的工作,其中就包括启动各种重要的系统服务,如PackageManagerService、PowerManagerService等等。
到此,Zygote进程和SystemServer进程的启动过程就结束了。











作者:csdn_of_coder 发表于2016/10/23 14:41:01 原文链接
阅读:22 评论:0 查看评论

Android懒人框架Android annotation

$
0
0

不知不觉有一个多月没更新了。唉,辞职了,最近一直在找工作没办法。蓝瘦。香菇。

annotation的配置

project的build.gradle

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.5.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}
allprojects {
    repositories {
        jcenter()
    }
}
task clean(type: Delete) {
    delete rootProject.buildDir
}

moudle的build.gradle配置

apply plugin: 'com.android.application'
apply plugin: 'android-apt'
def AAVersion = '4.0+'
repositories {
    jcenter()
    flatDir {
        dirs 'libs'
    }
}
android {
    compileSdkVersion 19
    buildToolsVersion "23.0.2"
    defaultConfig {
        applicationId "source.code.watch.film"
        minSdkVersion 14
        targetSdkVersion 14
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    signingConfigs {
        debug {
            storeFile file("E:\\psd\\debug.keystore")
        }
    }
}
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    apt "org.androidannotations:androidannotations:$AAVersion"
    compile "org.androidannotations:androidannotations-api:$AAVersion"
}
apt {
    arguments {
        androidManifestFile variant.outputs[0].processResources.manifestFile
        resourcePackageName 'source.code.watch.film'
    }
}

今天和大家介绍下Android的懒人框架:annotation。

@EActivity

@EActivity(R.layout.main)
public class MyActivity extends Activity {
}

@EFragment

@EFragment(R.layout.my_fragment_layout)
public class MyFragment extends Fragment {
}
<fragment
        android:id="@+id/myFragment"
        android:name="com.company.MyFragment_"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
MyFragment fragment = new MyFragment_();

@EBean

public class MyClass {
}
- 这个类必须仅仅只能有一个构造函数,参数最多有一个context
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @Bean
  MyClass myClass;
}
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @Bean(MyClass.class)
  MyClassInterface myClassInterface;
}
@EBean
public class MyClass {
  @RootContext
  Context context;
  @RootContext
  Activity activity;
  @RootContext
  Service service;
  @RootContext
  MyActivity myActivity;
}
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @AfterInject
  public void doSomethingAfterInjection() {
  }
}
- 如果想在类创建时期做一些操作可以这么做
@EBean(scope = Scope.Singleton)
public class MySingleton {
}
- 单例类
- 在单例类里面不可以注入view和事件绑定,因为单例的生命周期比Activity和Service的要长,以免发生内存溢出

@EView

@EView
public class CustomButton extends Button {
  @App
  MyApplication application;
  @StringRes
  String someStringResource;
  public CustomButton(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
}
<com.androidannotations.view.CustomButton_
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
CustomButton button = CustomButton_.build(context);

@EViewGroup

@EViewGroup(R.layout.title_with_subtitle)
public class TitleWithSubtitle extends RelativeLayout {
  @ViewById
  protected TextView title, subtitle;
  public TitleWithSubtitle(Context context, AttributeSet attrs) {
    super(context, attrs);
  }
  public void setTexts(String titleText, String subTitleText) {
    title.setText(titleText);
    subtitle.setText(subTitleText);
  }
}
<com.androidannotations.viewgroup.TitleWithSubtitle_
        android:id="@+id/firstTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

@EAplication

@EApplication
public class MyApplication extends Application {
}
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @App
  MyApplication application;
}

EService

@EService
public class MyService extends Service {
}
MyService_.intent(getApplication()).start();
MyService_.intent(getApplication()).stop();

@EReceiver

@EReceiver
public class MyReceiver extends BroadcastReceiver {
}
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @Receiver(actions = "org.androidannotations.ACTION_1")
  protected void onAction1() {
  }
}

@EProvider

@EProvider
public class MyContentProvider extends ContentProvider {
}

@EViewById


@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @ViewById
  EditText myEditText;
  @ViewById(R.id.myTextView)
  TextView textView;
}
- 没有括号里定义的变量名称必须和布局的id名称一致

@EAfterViews

@EActivity(R.layout.main)
public class MyActivity extends Activity {
   @ViewById
   TextView myTextView;
   @AfterViews
   void updateTextWithDate() {
      myTextView.setText("Date: " + new Date());
   }
}
 一定要在这里进行view的一些设置,不要在oncreate()中设置,因为oncreate()在执行时 view还没有注入

@StringRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
   @StringRes(R.string.hello)
   String myHelloString;
   @StringRes
   String hello;
}
- 不能将变量设置成私有变量

@ColorRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @ColorRes(R.color.backgroundColor)
  int someColor;
  @ColorRes
  int backgroundColor;
}

@AnimationRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
   @AnimationRes(R.anim.fadein)
   XmlResourceParser xmlResAnim;
   @AnimationRes
   Animation fadein;
}

@DimensionRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @DimensionRes(R.dimen.fontsize)
  float fontSizeDimension;
  @DimensionRes
  float fontsize;
}

@DimensionPixelOffsetRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @DimensionPixelOffsetRes(R.string.fontsize)
  int fontSizeDimension;
  @DimensionPixelOffsetRes
  int fontsize;
}

@DimensionPixelSizeRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @DimensionPixelSizeRes(R.string.fontsize)
  int fontSizeDimension;
  @DimensionPixelSizeRes
  int fontsize;
}

@Extra

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @Extra("myStringExtra")
  String myMessage;
  @Extra("myDateExtra")
  Date myDateExtraWithDefaultValue = new Date();
}
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @Extra
  String myMessage;
}
- The name of the extra will be "myMessage",名字必须一致
MyActivity_.intent().myMessage("hello").start() ;
MyActivity_.intent().myMessage("hello").startForResult() ;

@SystemService

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @SystemService
  NotificationManager notificationManager;
}

@HtmlRes

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @HtmlRes(R.string.hello_html)
  Spanned myHelloString;
  @HtmlRes
  CharSequence helloHtml;
}

@FromHtml

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @ViewById(R.id.my_text_view)
  @FromHtml(R.string.hello_html)
  TextView textView;
  @ViewById
  @FromHtml
  TextView hello_html;
}
- 必须用在TextView

@NonConfigurationInstance

@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @NonConfigurationInstance
  Bitmap someBitmap;
  @NonConfigurationInstance
  @Bean
  MyBackgroundTask myBackgroundTask;
}
- 等同于Activity.onRetainNonConfigurationInstance()

@Click

@Click(R.id.myButton)
void myButtonWasClicked() {
}
@Click
void anotherButton() {
}
@Click
void yetAnotherButton(View clickedView) {
}
- LongClick和这个类似

@SeekBarProgressChange


@SeekBarProgressChange(R.id.seekBar)
 void onProgressChangeOnSeekBar(SeekBar seekBar, int progress, boolean fromUser) {
 }
 @SeekBarProgressChange(R.id.seekBar)
 void onProgressChangeOnSeekBar(SeekBar seekBar, int progress) {
 }
 @SeekBarProgressChange({R.id.seekBar1, R.id.seekBar2})
 void onProgressChangeOnSeekBar(SeekBar seekBar) {
 }
 @SeekBarProgressChange({R.id.seekBar1, R.id.seekBar2})
 void onProgressChangeOnSeekBar() {
 }
- @SeekBarTouchStart 和 @SeekBarTouchStop 接受开始和结束事件的监听

@TextChange

@TextChange(R.id.helloTextView)
 void onTextChangesOnHelloTextView(CharSequence text, TextView hello, int before, int start, int count) {
 }
 @TextChange
 void helloTextViewTextChanged(TextView hello) {
 }
 @TextChange({R.id.editText, R.id.helloTextView})
 void onTextChangesOnSomeTextViews(TextView tv, CharSequence text) {
 }
 @TextChange(R.id.helloTextView)
 void onTextChangesOnHelloTextView() {
 }

@BeforeTextChange

@BeforeTextChange(R.id.helloTextView)
 void beforeTextChangedOnHelloTextView(TextView hello, CharSequence text, int start, int count, int after) {
 }
 @BeforeTextChange
 void helloTextViewBeforeTextChanged(TextView hello) {
 }
 @BeforeTextChange({R.id.editText, R.id.helloTextView})
 void beforeTextChangedOnSomeTextViews(TextView tv, CharSequence text) {
 }
 @BeforeTextChange(R.id.helloTextView)
 void beforeTextChangedOnHelloTextView() {
 }

@AfterTextChange

@AfterTextChange(R.id.helloTextView)
 void afterTextChangedOnHelloTextView(Editable text, TextView hello) {
 }
 @AfterTextChange
 void helloTextViewAfterTextChanged(TextView hello) {
 }
 @AfterTextChange({R.id.editText, R.id.helloTextView})
 void afterTextChangedOnSomeTextViews(TextView tv, Editable text) {
 }
 @AfterTextChange(R.id.helloTextView)
 void afterTextChangedOnHelloTextView() {
 }

@Background

void myMethod() {
    someBackgroundWork("hello", 42);
}
@Background
void someBackgroundWork(String aParam, long anotherParam) {
}
- 后台运行
void myMethod() {
    someCancellableBackground("hello", 42);
    boolean mayInterruptIfRunning = true;
    BackgroundExecutor.cancelAll("cancellable_task", mayInterruptIfRunning);
}
@Background(id="cancellable_task")
void someCancellableBackground(String aParam, long anotherParam) {
}
void myMethod() {
    for (int i = 0; i < 10; i++)
        someSequentialBackgroundMethod(i);
}
@Background(serial = "test")
void someSequentialBackgroundMethod(int i) {
    SystemClock.sleep(new Random().nextInt(2000)+1000);
    Log.d("AA", "value : " + i);
}
- 非并发执行
@Background(delay=2000)
void doInBackgroundAfterTwoSeconds() {
}
- 延迟执行

@UIThread

void myMethod() {
    doInUiThread("hello", 42);
}
@UiThread
void doInUiThread(String aParam, long anotherParam) {
}
- UI线程
@UiThread(delay=2000)
void doInUiThreadAfterTwoSeconds() {
}
- 延迟
@UiThread(propagation = Propagation.REUSE)
void runInSameThreadIfOnUiThread() {
}
- 优化UI线程
@EActivity(R.layout.main)
public class MyActivity extends Activity {
  @Background
  void doSomeStuffInBackground() {
    publishProgress(0);
    publishProgress(10);
    publishProgress(100);
  }
  @UiThread
  void publishProgress(int progress) {
  }
}
- 后台向UI线程传值

@OnActivityResult

@OnActivityResult(REQUEST_CODE)
void onResult(int resultCode, Intent data) {
}
@OnActivityResult(REQUEST_CODE)
void onResult(int resultCode) {
}
@OnActivityResult(ANOTHER_REQUEST_CODE)
void onResult(Intent data) {
}
@OnActivityResult(ANOTHER_REQUEST_CODE)
void onResult() {
}

好了。关于annotation的介就是这么多。欢迎进群交流。还有,求工作啊!!!!

作者:sw950729 发表于2016/10/23 14:43:01 原文链接
阅读:23 评论:0 查看评论

iOS中 利用runtime处理程序中的常见崩溃 韩俊强的博客

$
0
0

前言

一个已经发布到AppStore上的App,最忌讳的就是崩溃问题。为什么在开发阶段或者测试阶段都不会崩溃,而发布到AppStore上就崩溃了呢?究其根源,最主要的原因就是数据的错乱。特别是 服务器返回数据的错乱,将严重影响到我们的App。


Foundation框架存在许多潜在崩溃的危险

  • 将 nil 插入可变数组中会导致崩溃。
  • 数组越界会导致崩溃。
  • 根据key给字典某个元素重新赋值时,若key为 nil 会导致崩溃。
  • ......

JQSafeKit简介

  • 这个框架利用runtime技术对一些常用并且容易导致崩溃的方法进行处理,可以有效的防止崩溃。
  • 并且打印出具体是哪一行代码会导致崩溃,让你快速定位导致崩溃的代码。
  • 你可以获取到原本导致崩溃的主要信息<由于这个框架的存在,并不会崩溃>,进行相应的处理。比如:
  • 你可以将这些崩溃信息发送到自己服务器。
  • 你若集成了第三方崩溃日志收集的SDK,比如你用了腾讯的Bugly,你可以上报自定义异常。

下面先来看下防止崩溃的效果吧

可导致崩溃的代码

NSString *nilStr = nil;
    NSArray *array = @[@"HaRi", nilStr];

若没有JQSafeKit来防止崩溃,则会直接崩溃,如下图


若有JQSafeKit来防止崩溃,则不会崩溃,并且会将原本会崩溃情况的详细信息打印出来,如下图



Installation【安装】

From CocoaPods【使用CocoaPods】

pod  “JQSafeKit

Manually【手动导入】

  • Drag all source files under floder JQSafeKit to your project.【将JQSafeKit文件夹中的所有源代码拽入项目中】

使用方法

  • 在AppDelegate的didFinishLaunchingWithOptions方法中添加如下代码,让JQSafeKit生效

//这句代码会让JQSafeKit生效,若没有如下代码,则JQSafeKit就不起作用
[JQSafeKit becomeEffective];

若你想要获取崩溃日志的所有详细信息,只需添加通知的监听,监听的通知名为:JQSafeKitNotification
     

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [JQSafeKit becomeEffective];
    
    //监听通知:JQSafeKitNotification, 获取JQSafeKit捕获的崩溃日志的详细信息
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealwithCrashMessage:) name:JQSafeKitNotification object:nil];
    return YES;
}

- (void)dealwithCrashMessage:(NSNotification *)note {
//注意:所有的信息都在userInfo中
//你可以在这里收集相应的崩溃信息进行相应的处理(比如传到自己服务器)
    NSLog(@"%@",note.userInfo);
}

下面通过打断点的形式来看下userInfo中的信息结构,看下包含了哪些信息

userInfo信息结构.png


  • 再看下控制台输出日志来看下userInfo中的包含了哪些信息

目前可以防止崩溃的方法有


  • NSArray
  • 1. NSArray的快速创建方式 NSArray *array = @[@"HaRi", @"JQSafeKit"]; //这种创建方式其实调用的是2中的方法
  • 2. +(instancetype)arrayWithObjects:(const id  _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt

  • 3. - (id)objectAtIndex:(NSUInteger)index


  • NSMutableArray
  • 1. - (id)objectAtIndex:(NSUInteger)index
  • 2. - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
  • 3. - (void)removeObjectAtIndex:(NSUInteger)index
  • 4. - (void)insertObject:(id)anObject atIndex:(NSUInteger)index

  • NSDictionary
  • 1. NSDictionary的快速创建方式 NSDictionary *dict = @{@"frameWork" : @"JQSafeKit"}; //这种创建方式其实调用的是2中的方法
  • 2. +(instancetype)dictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt

  • NSMutableDictionary
  • 1. - (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
  • 2. - (void)removeObjectForKey:(id)aKey

  • NSString
  • 1. - (unichar)characterAtIndex:(NSUInteger)index
  • 2. - (NSString *)substringFromIndex:(NSUInteger)from
  • 3. - (NSString *)substringToIndex:(NSUInteger)to {
  • 4. - (NSString *)substringWithRange:(NSRange)range {
  • 5. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
  • 6. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange
  • 7. - (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement

  • NSMutableString
  • 1. 由于NSMutableString是继承于NSString,所以这里和NSString有些同样的方法就不重复写了
  • 2. - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
  • 3. - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
  • 4. - (void)deleteCharactersInRange:(NSRange)range

About me -- CSDN

iOS开发者交流群:446310206



作者:qq_31810357 发表于2016/10/23 17:26:48 原文链接
阅读:448 评论:0 查看评论

android四大组件(总结)activity、service、content provider、broadcast receiver

$
0
0

Android四大组件分别为activityservicecontent providerbroadcast receiver

一、android四大组件详解

1activity

1)一个Activity通常就是一个单独的屏幕(窗口)。

2Activity之间通过Intent进行通信。

3android应用中每一个Activity都必须要在AndroidManifest.xml配置文件中声明,否则系统将不识别也不执行该Activity

2service

1service用于在后台完成用户指定的操作。service分为两种:

astarted(启动):当应用程序组件(如activity)调用startService()方法启动服务时,服务处于started状态。

bbound(绑定):当应用程序组件调用bindService()方法绑定到服务时,服务处于bound状态。

(2)startService()bindService()区别:

(a)started service(启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。

(b)使用bindService()方法启用服务,调用者与服务绑定在了一起,调用者一旦退出,服务也就终止,大有“不求同时生,必须同时死”的特点。

(3)开发人员需要在应用程序配置文件中声明全部的service,使用<service></service>标签。

(4)Service通常位于后台运行,它一般不需要与用户交互,因此Service组件没有图形用户界面。Service组件需要继承Service基类。Service组件通常用于为其他组件提供后台服务或监控其他组件的运行状态。

3content provider

1android平台提供了Content Provider使一个应用程序的指定数据集提供给其他应用程序。其他应用可以通过ContentResolver类从该内容提供者中获取或存入数据

2)只有需要在多个应用程序间共享数据是才需要内容提供者。例如,通讯录数据被多个应用程序使用,且必须存储在一个内容提供者中。它的好处是统一数据访问方式。

3ContentProvider实现数据共享ContentProvider用于保存和获取数据,并使其对所有应用程序可见。这是不同应用程序间共享数据的唯一方式,因为android没有提供所有应用共同访问的公共存储区。

4)开发人员不会直接使用ContentProvider类的对象,大多数是通过ContentResolver对象实现对ContentProvider的操作。

5ContentProvider使用URI来唯一标识其数据集,这里的URIcontent://作为前缀,表示该数据由ContentProvider来管理。

4broadcast receiver

1)你的应用可以使用它对外部事件进行过滤,只对感兴趣的外部事件(如当电话呼入时,或者数据网络可用时)进行接收并做出响应。广播接收器没有用户界面。然而,它们可以启动一个activityserice来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力,例如闪动背灯、震动、播放声音等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。

2)广播接收者的注册有两种方法,分别是程序动态注册和AndroidManifest文件中进行静态注册。

3)动态注册广播接收器特点是当用来注册的Activity关掉后,广播也就失效了。静态注册无需担忧广播接收器是否被关闭,只要设备是开启状态,广播接收器也是打开着的。也就是说哪怕app本身未启动,该app订阅的广播在触发时也会对它起作用。

二、android四大组件总结:

14大组件的注册

4大基本组件都需要注册才能使用,每个ActivityserviceContent Provider都需要在AndroidManifest文件中进行配置。AndroidManifest文件中未进行声明的activity、服务以及内容提供者将不为系统所见,从而也就不可用。而broadcast receiver广播接收者的注册分静态注册(在AndroidManifest文件中进行配置)和通过代码动态创建并以调用Context.registerReceiver()的方式注册至系统。需要注意的是在AndroidManifest文件中进行配置的广播接收者会随系统的启动而一直处于活跃状态,只要接收到感兴趣的广播就会触发(即使程序未运行)。

24大组件的激活

内容提供者的激活:当接收到ContentResolver发出的请求后,内容提供者被激活。而其它三种组件activity、服务和广播接收器被一种叫做intent的异步消息所激活

34大组件的关闭

内容提供者仅在响应ContentResolver提出请求的时候激活。而一个广播接收器仅在响应广播信息的时候激活。所以,没有必要去显式的关闭这些组件。Activity关闭:可以通过调用它的finish()方法来关闭一个activity。服务关闭:对于通过startService()方法启动的服务要调用Context.stopService()方法关闭服务,使用bindService()方法启动的服务要调用Contex.unbindService()方法关闭服务。

4android中的任务(activity栈)

a)任务其实就是activity的栈,它由一个或多个Activity组成,共同完成一个完整的用户体验。栈底的是启动整个任务的Activity,栈顶的是当前运行的用户可以交互的Activity,当一个activity启动另外一个的时候,新的activity就被压入栈,并成为当前运行的activity。而前一个activity仍保持在栈之中。当用户按下BACK键的时候,当前activity出栈,而前一个恢复为当前运行的activity。栈中保存的其实是对象,栈中的Activity永远不会重排,只会压入或弹出。

b)任务中的所有activity是作为一个整体进行移动的。整个的任务(即activity栈)可以移到前台,或退至后台。

(c)Android系统是一个多任务(Multi-Task)的操作系统,可以在用手机听音乐的同时,也执行其他多个程序。每多执行一个应用程序,就会多耗费一些系统内存,当同时执行的程序过多,或是关闭的程序没有正确释放掉内存,系统就会觉得越来越慢,甚至不稳定。为了解决这个问题,Android引入了一个新的机制,即生命周期(Life Cycle)。
作者:zhangyufeng0126 发表于2016/10/23 17:40:45 原文链接
阅读:326 评论:0 查看评论

Android插件GsonFormat发生parse err !!

$
0
0

使用as插件G送Format创建类的时候出现错误:
这里写图片描述

json数据如下:

{"HeWeather data service 3.0":[{"aqi":{"city":{"aqi":"50","co":"0","no2":"34","o3":"45","pm10":"50","pm25":"26","qlty":"优","so2":"3"}},"basic":{"city":"北京","cnty":"中国","id":"CN101010100","lat":"39.904000","lon":"116.391000","update":{"loc":"2016-10-23 17:16","utc":"2016-10-23 09:16"}},"daily_forecast":[{"astro":{"sr":"06:33","ss":"17:23"},"cond":{"code_d":"101","code_n":"101","txt_d":"多云","txt_n":"多云"},"date":"2016-10-23","hum":"54","pcpn":"0.0","pop":"1","pres":"1023","tmp":{"max":"15","min":"5"},"vis":"10","wind":{"deg":"183","dir":"无持续风向","sc":"微风","spd":"6"}},{"astro":{"sr":"06:34","ss":"17:22"},"cond":{"code_d":"502","code_n":"502","txt_d":"霾","txt_n":"霾"},"date":"2016-10-24","hum":"69","pcpn":"4.8","pop":"43","pres":"1018","tmp":{"max":"13","min":"6"},"vis":"9","wind":{"deg":"183","dir":"无持续风向","sc":"微风","spd":"7"}},{"astro":{"sr":"06:35","ss":"17:21"},"cond":{"code_d":"502","code_n":"502","txt_d":"霾","txt_n":"霾"},"date":"2016-10-25","hum":"78","pcpn":"0.0","pop":"0","pres":"1016","tmp":{"max":"17","min":"8"},"vis":"10","wind":{"deg":"166","dir":"无持续风向","sc":"微风","spd":"3"}},{"astro":{"sr":"06:36","ss":"17:19"},"cond":{"code_d":"101","code_n":"104","txt_d":"多云","txt_n":"阴"},"date":"2016-10-26","hum":"57","pcpn":"0.0","pop":"0","pres":"1024","tmp":{"max":"17","min":"9"},"vis":"10","wind":{"deg":"148","dir":"无持续风向","sc":"微风","spd":"3"}},{"astro":{"sr":"06:38","ss":"17:18"},"cond":{"code_d":"104","code_n":"100","txt_d":"阴","txt_n":"晴"},"date":"2016-10-27","hum":"84","pcpn":"6.9","pop":"99","pres":"1026","tmp":{"max":"16","min":"9"},"vis":"8","wind":{"deg":"16","dir":"无持续风向","sc":"微风","spd":"7"}},{"astro":{"sr":"06:39","ss":"17:17"},"cond":{"code_d":"100","code_n":"100","txt_d":"晴","txt_n":"晴"},"date":"2016-10-28","hum":"41","pcpn":"0.0","pop":"3","pres":"1030","tmp":{"max":"14","min":"5"},"vis":"10","wind":{"deg":"349","dir":"无持续风向","sc":"微风","spd":"3"}},{"astro":{"sr":"06:40","ss":"17:15"},"cond":{"code_d":"100","code_n":"100","txt_d":"晴","txt_n":"晴"},"date":"2016-10-29","hum":"32","pcpn":"0.0","pop":"1","pres":"1032","tmp":{"max":"13","min":"3"},"vis":"10","wind":{"deg":"190","dir":"无持续风向","sc":"微风","spd":"9"}}],"hourly_forecast":[{"date":"2016-10-23 19:00","hum":"54","pop":"0","pres":"1020","tmp":"12","wind":{"deg":"196","dir":"西南风","sc":"微风","spd":"6"}},{"date":"2016-10-23 22:00","hum":"57","pop":"0","pres":"1021","tmp":"10","wind":{"deg":"213","dir":"西南风","sc":"微风","spd":"5"}}],"now":{"cond":{"code":"101","txt":"多云"},"fl":"15","hum":"38","pcpn":"0","pres":"1021","tmp":"14","vis":"10","wind":{"deg":"184","dir":"西南风","sc":"4-5","spd":"24"}},"status":"ok","suggestion":{"comf":{"brf":"舒适","txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。"},"cw":{"brf":"较适宜","txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"},"drsg":{"brf":"较冷","txt":"建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。"},"flu":{"brf":"较易发","txt":"天气较凉,较易发生感冒,请适当增加衣服。体质较弱的朋友尤其应该注意防护。"},"sport":{"brf":"较不宜","txt":"天气较好,但考虑天气寒冷,推荐您进行各种室内运动,若在户外运动请注意保暖并做好准备活动。"},"trav":{"brf":"适宜","txt":"天气较好,但丝毫不会影响您出行的心情。温度适宜又有微风相伴,适宜旅游。"},"uv":{"brf":"弱","txt":"紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。"}}}]}

错误原因:
新建的类里面含有非法的变量:
List<HeWeather data service 3.0Bean>
HeWeather data service 3.0
这里写图片描述

改正之后即可:

package com.example.administrator.retiofitrxjava;

import com.google.gson.Gson;
import com.google.gson.annotations.SerializedName;

import java.util.List;

/**
 * Created by Administrator on 2016/10/23.
 */

public class MyJsonData {


    /**
     * aqi : {"city":{"aqi":"50","co":"0","no2":"34","o3":"45","pm10":"50","pm25":"26","qlty":"优","so2":"3"}}
     * basic : {"city":"北京","cnty":"中国","id":"CN101010100","lat":"39.904000","lon":"116.391000","update":{"loc":"2016-10-23 17:16","utc":"2016-10-23 09:16"}}
     * daily_forecast : [{"astro":{"sr":"06:33","ss":"17:23"},"cond":{"code_d":"101","code_n":"101","txt_d":"多云","txt_n":"多云"},"date":"2016-10-23","hum":"54","pcpn":"0.0","pop":"1","pres":"1023","tmp":{"max":"15","min":"5"},"vis":"10","wind":{"deg":"183","dir":"无持续风向","sc":"微风","spd":"6"}},{"astro":{"sr":"06:34","ss":"17:22"},"cond":{"code_d":"502","code_n":"502","txt_d":"霾","txt_n":"霾"},"date":"2016-10-24","hum":"69","pcpn":"4.8","pop":"43","pres":"1018","tmp":{"max":"13","min":"6"},"vis":"9","wind":{"deg":"183","dir":"无持续风向","sc":"微风","spd":"7"}},{"astro":{"sr":"06:35","ss":"17:21"},"cond":{"code_d":"502","code_n":"502","txt_d":"霾","txt_n":"霾"},"date":"2016-10-25","hum":"78","pcpn":"0.0","pop":"0","pres":"1016","tmp":{"max":"17","min":"8"},"vis":"10","wind":{"deg":"166","dir":"无持续风向","sc":"微风","spd":"3"}},{"astro":{"sr":"06:36","ss":"17:19"},"cond":{"code_d":"101","code_n":"104","txt_d":"多云","txt_n":"阴"},"date":"2016-10-26","hum":"57","pcpn":"0.0","pop":"0","pres":"1024","tmp":{"max":"17","min":"9"},"vis":"10","wind":{"deg":"148","dir":"无持续风向","sc":"微风","spd":"3"}},{"astro":{"sr":"06:38","ss":"17:18"},"cond":{"code_d":"104","code_n":"100","txt_d":"阴","txt_n":"晴"},"date":"2016-10-27","hum":"84","pcpn":"6.9","pop":"99","pres":"1026","tmp":{"max":"16","min":"9"},"vis":"8","wind":{"deg":"16","dir":"无持续风向","sc":"微风","spd":"7"}},{"astro":{"sr":"06:39","ss":"17:17"},"cond":{"code_d":"100","code_n":"100","txt_d":"晴","txt_n":"晴"},"date":"2016-10-28","hum":"41","pcpn":"0.0","pop":"3","pres":"1030","tmp":{"max":"14","min":"5"},"vis":"10","wind":{"deg":"349","dir":"无持续风向","sc":"微风","spd":"3"}},{"astro":{"sr":"06:40","ss":"17:15"},"cond":{"code_d":"100","code_n":"100","txt_d":"晴","txt_n":"晴"},"date":"2016-10-29","hum":"32","pcpn":"0.0","pop":"1","pres":"1032","tmp":{"max":"13","min":"3"},"vis":"10","wind":{"deg":"190","dir":"无持续风向","sc":"微风","spd":"9"}}]
     * hourly_forecast : [{"date":"2016-10-23 19:00","hum":"54","pop":"0","pres":"1020","tmp":"12","wind":{"deg":"196","dir":"西南风","sc":"微风","spd":"6"}},{"date":"2016-10-23 22:00","hum":"57","pop":"0","pres":"1021","tmp":"10","wind":{"deg":"213","dir":"西南风","sc":"微风","spd":"5"}}]
     * now : {"cond":{"code":"101","txt":"多云"},"fl":"15","hum":"38","pcpn":"0","pres":"1021","tmp":"14","vis":"10","wind":{"deg":"184","dir":"西南风","sc":"4-5","spd":"24"}}
     * status : ok
     * suggestion : {"comf":{"brf":"舒适","txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。"},"cw":{"brf":"较适宜","txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"},"drsg":{"brf":"较冷","txt":"建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。"},"flu":{"brf":"较易发","txt":"天气较凉,较易发生感冒,请适当增加衣服。体质较弱的朋友尤其应该注意防护。"},"sport":{"brf":"较不宜","txt":"天气较好,但考虑天气寒冷,推荐您进行各种室内运动,若在户外运动请注意保暖并做好准备活动。"},"trav":{"brf":"适宜","txt":"天气较好,但丝毫不会影响您出行的心情。温度适宜又有微风相伴,适宜旅游。"},"uv":{"brf":"弱","txt":"紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。"}}
     */

    @SerializedName("HeWeather data service 3.0")
    private List<HeWeatherdataserviceBean> HeWeatherdataservice;



    public List<HeWeatherdataserviceBean> getHeWeatherdataservice() {
        return HeWeatherdataservice;
    }

    public void setHeWeatherdataservice(List<HeWeatherdataserviceBean> HeWeatherdataservice) {
        this.HeWeatherdataservice = HeWeatherdataservice;
    }

    public static class HeWeatherdataserviceBean {
        /**
         * city : {"aqi":"50","co":"0","no2":"34","o3":"45","pm10":"50","pm25":"26","qlty":"优","so2":"3"}
         */

        private AqiBean aqi;
        /**
         * city : 北京
         * cnty : 中国
         * id : CN101010100
         * lat : 39.904000
         * lon : 116.391000
         * update : {"loc":"2016-10-23 17:16","utc":"2016-10-23 09:16"}
         */

        private BasicBean basic;
        /**
         * cond : {"code":"101","txt":"多云"}
         * fl : 15
         * hum : 38
         * pcpn : 0
         * pres : 1021
         * tmp : 14
         * vis : 10
         * wind : {"deg":"184","dir":"西南风","sc":"4-5","spd":"24"}
         */

        private NowBean now;
        private String status;
        /**
         * comf : {"brf":"舒适","txt":"白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。"}
         * cw : {"brf":"较适宜","txt":"较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。"}
         * drsg : {"brf":"较冷","txt":"建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。"}
         * flu : {"brf":"较易发","txt":"天气较凉,较易发生感冒,请适当增加衣服。体质较弱的朋友尤其应该注意防护。"}
         * sport : {"brf":"较不宜","txt":"天气较好,但考虑天气寒冷,推荐您进行各种室内运动,若在户外运动请注意保暖并做好准备活动。"}
         * trav : {"brf":"适宜","txt":"天气较好,但丝毫不会影响您出行的心情。温度适宜又有微风相伴,适宜旅游。"}
         * uv : {"brf":"弱","txt":"紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。"}
         */

        private SuggestionBean suggestion;
        /**
         * astro : {"sr":"06:33","ss":"17:23"}
         * cond : {"code_d":"101","code_n":"101","txt_d":"多云","txt_n":"多云"}
         * date : 2016-10-23
         * hum : 54
         * pcpn : 0.0
         * pop : 1
         * pres : 1023
         * tmp : {"max":"15","min":"5"}
         * vis : 10
         * wind : {"deg":"183","dir":"无持续风向","sc":"微风","spd":"6"}
         */

        private List<DailyForecastBean> daily_forecast;
        /**
         * date : 2016-10-23 19:00
         * hum : 54
         * pop : 0
         * pres : 1020
         * tmp : 12
         * wind : {"deg":"196","dir":"西南风","sc":"微风","spd":"6"}
         */

        private List<HourlyForecastBean> hourly_forecast;

        public static HeWeatherdataserviceBean objectFromData(String str) {

            return new Gson().fromJson(str, HeWeatherdataserviceBean.class);
        }

        public AqiBean getAqi() {
            return aqi;
        }

        public void setAqi(AqiBean aqi) {
            this.aqi = aqi;
        }

        public BasicBean getBasic() {
            return basic;
        }

        public void setBasic(BasicBean basic) {
            this.basic = basic;
        }

        public NowBean getNow() {
            return now;
        }

        public void setNow(NowBean now) {
            this.now = now;
        }

        public String getStatus() {
            return status;
        }

        public void setStatus(String status) {
            this.status = status;
        }

        public SuggestionBean getSuggestion() {
            return suggestion;
        }

        public void setSuggestion(SuggestionBean suggestion) {
            this.suggestion = suggestion;
        }

        public List<DailyForecastBean> getDaily_forecast() {
            return daily_forecast;
        }

        public void setDaily_forecast(List<DailyForecastBean> daily_forecast) {
            this.daily_forecast = daily_forecast;
        }

        public List<HourlyForecastBean> getHourly_forecast() {
            return hourly_forecast;
        }

        public void setHourly_forecast(List<HourlyForecastBean> hourly_forecast) {
            this.hourly_forecast = hourly_forecast;
        }

        public static class AqiBean {
            /**
             * aqi : 50
             * co : 0
             * no2 : 34
             * o3 : 45
             * pm10 : 50
             * pm25 : 26
             * qlty : 优
             * so2 : 3
             */

            private CityBean city;

            public static AqiBean objectFromData(String str) {

                return new Gson().fromJson(str, AqiBean.class);
            }

            public CityBean getCity() {
                return city;
            }

            public void setCity(CityBean city) {
                this.city = city;
            }

            public static class CityBean {
                private String aqi;
                private String co;
                private String no2;
                private String o3;
                private String pm10;
                private String pm25;
                private String qlty;
                private String so2;

                public static CityBean objectFromData(String str) {

                    return new Gson().fromJson(str, CityBean.class);
                }

                public String getAqi() {
                    return aqi;
                }

                public void setAqi(String aqi) {
                    this.aqi = aqi;
                }

                public String getCo() {
                    return co;
                }

                public void setCo(String co) {
                    this.co = co;
                }

                public String getNo2() {
                    return no2;
                }

                public void setNo2(String no2) {
                    this.no2 = no2;
                }

                public String getO3() {
                    return o3;
                }

                public void setO3(String o3) {
                    this.o3 = o3;
                }

                public String getPm10() {
                    return pm10;
                }

                public void setPm10(String pm10) {
                    this.pm10 = pm10;
                }

                public String getPm25() {
                    return pm25;
                }

                public void setPm25(String pm25) {
                    this.pm25 = pm25;
                }

                public String getQlty() {
                    return qlty;
                }

                public void setQlty(String qlty) {
                    this.qlty = qlty;
                }

                public String getSo2() {
                    return so2;
                }

                public void setSo2(String so2) {
                    this.so2 = so2;
                }
            }
        }

        public static class BasicBean {
            private String city;
            private String cnty;
            private String id;
            private String lat;
            private String lon;
            /**
             * loc : 2016-10-23 17:16
             * utc : 2016-10-23 09:16
             */

            private UpdateBean update;

            public static BasicBean objectFromData(String str) {

                return new Gson().fromJson(str, BasicBean.class);
            }

            public String getCity() {
                return city;
            }

            public void setCity(String city) {
                this.city = city;
            }

            public String getCnty() {
                return cnty;
            }

            public void setCnty(String cnty) {
                this.cnty = cnty;
            }

            public String getId() {
                return id;
            }

            public void setId(String id) {
                this.id = id;
            }

            public String getLat() {
                return lat;
            }

            public void setLat(String lat) {
                this.lat = lat;
            }

            public String getLon() {
                return lon;
            }

            public void setLon(String lon) {
                this.lon = lon;
            }

            public UpdateBean getUpdate() {
                return update;
            }

            public void setUpdate(UpdateBean update) {
                this.update = update;
            }

            public static class UpdateBean {
                private String loc;
                private String utc;

                public static UpdateBean objectFromData(String str) {

                    return new Gson().fromJson(str, UpdateBean.class);
                }

                public String getLoc() {
                    return loc;
                }

                public void setLoc(String loc) {
                    this.loc = loc;
                }

                public String getUtc() {
                    return utc;
                }

                public void setUtc(String utc) {
                    this.utc = utc;
                }
            }
        }

        public static class NowBean {
            /**
             * code : 101
             * txt : 多云
             */

            private CondBean cond;
            private String fl;
            private String hum;
            private String pcpn;
            private String pres;
            private String tmp;
            private String vis;
            /**
             * deg : 184
             * dir : 西南风
             * sc : 4-5
             * spd : 24
             */

            private WindBean wind;

            public static NowBean objectFromData(String str) {

                return new Gson().fromJson(str, NowBean.class);
            }

            public CondBean getCond() {
                return cond;
            }

            public void setCond(CondBean cond) {
                this.cond = cond;
            }

            public String getFl() {
                return fl;
            }

            public void setFl(String fl) {
                this.fl = fl;
            }

            public String getHum() {
                return hum;
            }

            public void setHum(String hum) {
                this.hum = hum;
            }

            public String getPcpn() {
                return pcpn;
            }

            public void setPcpn(String pcpn) {
                this.pcpn = pcpn;
            }

            public String getPres() {
                return pres;
            }

            public void setPres(String pres) {
                this.pres = pres;
            }

            public String getTmp() {
                return tmp;
            }

            public void setTmp(String tmp) {
                this.tmp = tmp;
            }

            public String getVis() {
                return vis;
            }

            public void setVis(String vis) {
                this.vis = vis;
            }

            public WindBean getWind() {
                return wind;
            }

            public void setWind(WindBean wind) {
                this.wind = wind;
            }

            public static class CondBean {
                private String code;
                private String txt;

                public static CondBean objectFromData(String str) {

                    return new Gson().fromJson(str, CondBean.class);
                }

                public String getCode() {
                    return code;
                }

                public void setCode(String code) {
                    this.code = code;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class WindBean {
                private String deg;
                private String dir;
                private String sc;
                private String spd;

                public static WindBean objectFromData(String str) {

                    return new Gson().fromJson(str, WindBean.class);
                }

                public String getDeg() {
                    return deg;
                }

                public void setDeg(String deg) {
                    this.deg = deg;
                }

                public String getDir() {
                    return dir;
                }

                public void setDir(String dir) {
                    this.dir = dir;
                }

                public String getSc() {
                    return sc;
                }

                public void setSc(String sc) {
                    this.sc = sc;
                }

                public String getSpd() {
                    return spd;
                }

                public void setSpd(String spd) {
                    this.spd = spd;
                }
            }
        }

        public static class SuggestionBean {
            /**
             * brf : 舒适
             * txt : 白天不太热也不太冷,风力不大,相信您在这样的天气条件下,应会感到比较清爽和舒适。
             */

            private ComfBean comf;
            /**
             * brf : 较适宜
             * txt : 较适宜洗车,未来一天无雨,风力较小,擦洗一新的汽车至少能保持一天。
             */

            private CwBean cw;
            /**
             * brf : 较冷
             * txt : 建议着厚外套加毛衣等服装。年老体弱者宜着大衣、呢外套加羊毛衫。
             */

            private DrsgBean drsg;
            /**
             * brf : 较易发
             * txt : 天气较凉,较易发生感冒,请适当增加衣服。体质较弱的朋友尤其应该注意防护。
             */

            private FluBean flu;
            /**
             * brf : 较不宜
             * txt : 天气较好,但考虑天气寒冷,推荐您进行各种室内运动,若在户外运动请注意保暖并做好准备活动。
             */

            private SportBean sport;
            /**
             * brf : 适宜
             * txt : 天气较好,但丝毫不会影响您出行的心情。温度适宜又有微风相伴,适宜旅游。
             */

            private TravBean trav;
            /**
             * brf : 弱
             * txt : 紫外线强度较弱,建议出门前涂擦SPF在12-15之间、PA+的防晒护肤品。
             */

            private UvBean uv;

            public static SuggestionBean objectFromData(String str) {

                return new Gson().fromJson(str, SuggestionBean.class);
            }

            public ComfBean getComf() {
                return comf;
            }

            public void setComf(ComfBean comf) {
                this.comf = comf;
            }

            public CwBean getCw() {
                return cw;
            }

            public void setCw(CwBean cw) {
                this.cw = cw;
            }

            public DrsgBean getDrsg() {
                return drsg;
            }

            public void setDrsg(DrsgBean drsg) {
                this.drsg = drsg;
            }

            public FluBean getFlu() {
                return flu;
            }

            public void setFlu(FluBean flu) {
                this.flu = flu;
            }

            public SportBean getSport() {
                return sport;
            }

            public void setSport(SportBean sport) {
                this.sport = sport;
            }

            public TravBean getTrav() {
                return trav;
            }

            public void setTrav(TravBean trav) {
                this.trav = trav;
            }

            public UvBean getUv() {
                return uv;
            }

            public void setUv(UvBean uv) {
                this.uv = uv;
            }

            public static class ComfBean {
                private String brf;
                private String txt;

                public static ComfBean objectFromData(String str) {

                    return new Gson().fromJson(str, ComfBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class CwBean {
                private String brf;
                private String txt;

                public static CwBean objectFromData(String str) {

                    return new Gson().fromJson(str, CwBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class DrsgBean {
                private String brf;
                private String txt;

                public static DrsgBean objectFromData(String str) {

                    return new Gson().fromJson(str, DrsgBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class FluBean {
                private String brf;
                private String txt;

                public static FluBean objectFromData(String str) {

                    return new Gson().fromJson(str, FluBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class SportBean {
                private String brf;
                private String txt;

                public static SportBean objectFromData(String str) {

                    return new Gson().fromJson(str, SportBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class TravBean {
                private String brf;
                private String txt;

                public static TravBean objectFromData(String str) {

                    return new Gson().fromJson(str, TravBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }

            public static class UvBean {
                private String brf;
                private String txt;

                public static UvBean objectFromData(String str) {

                    return new Gson().fromJson(str, UvBean.class);
                }

                public String getBrf() {
                    return brf;
                }

                public void setBrf(String brf) {
                    this.brf = brf;
                }

                public String getTxt() {
                    return txt;
                }

                public void setTxt(String txt) {
                    this.txt = txt;
                }
            }
        }

        public static class DailyForecastBean {
            /**
             * sr : 06:33
             * ss : 17:23
             */

            private AstroBean astro;
            /**
             * code_d : 101
             * code_n : 101
             * txt_d : 多云
             * txt_n : 多云
             */

            private CondBean cond;
            private String date;
            private String hum;
            private String pcpn;
            private String pop;
            private String pres;
            /**
             * max : 15
             * min : 5
             */

            private TmpBean tmp;
            private String vis;
            /**
             * deg : 183
             * dir : 无持续风向
             * sc : 微风
             * spd : 6
             */

            private WindBean wind;

            public static DailyForecastBean objectFromData(String str) {

                return new Gson().fromJson(str, DailyForecastBean.class);
            }

            public AstroBean getAstro() {
                return astro;
            }

            public void setAstro(AstroBean astro) {
                this.astro = astro;
            }

            public CondBean getCond() {
                return cond;
            }

            public void setCond(CondBean cond) {
                this.cond = cond;
            }

            public String getDate() {
                return date;
            }

            public void setDate(String date) {
                this.date = date;
            }

            public String getHum() {
                return hum;
            }

            public void setHum(String hum) {
                this.hum = hum;
            }

            public String getPcpn() {
                return pcpn;
            }

            public void setPcpn(String pcpn) {
                this.pcpn = pcpn;
            }

            public String getPop() {
                return pop;
            }

            public void setPop(String pop) {
                this.pop = pop;
            }

            public String getPres() {
                return pres;
            }

            public void setPres(String pres) {
                this.pres = pres;
            }

            public TmpBean getTmp() {
                return tmp;
            }

            public void setTmp(TmpBean tmp) {
                this.tmp = tmp;
            }

            public String getVis() {
                return vis;
            }

            public void setVis(String vis) {
                this.vis = vis;
            }

            public WindBean getWind() {
                return wind;
            }

            public void setWind(WindBean wind) {
                this.wind = wind;
            }

            public static class AstroBean {
                private String sr;
                private String ss;

                public static AstroBean objectFromData(String str) {

                    return new Gson().fromJson(str, AstroBean.class);
                }

                public String getSr() {
                    return sr;
                }

                public void setSr(String sr) {
                    this.sr = sr;
                }

                public String getSs() {
                    return ss;
                }

                public void setSs(String ss) {
                    this.ss = ss;
                }
            }

            public static class CondBean {
                private String code_d;
                private String code_n;
                private String txt_d;
                private String txt_n;

                public static CondBean objectFromData(String str) {

                    return new Gson().fromJson(str, CondBean.class);
                }

                public String getCode_d() {
                    return code_d;
                }

                public void setCode_d(String code_d) {
                    this.code_d = code_d;
                }

                public String getCode_n() {
                    return code_n;
                }

                public void setCode_n(String code_n) {
                    this.code_n = code_n;
                }

                public String getTxt_d() {
                    return txt_d;
                }

                public void setTxt_d(String txt_d) {
                    this.txt_d = txt_d;
                }

                public String getTxt_n() {
                    return txt_n;
                }

                public void setTxt_n(String txt_n) {
                    this.txt_n = txt_n;
                }
            }

            public static class TmpBean {
                private String max;
                private String min;

                public static TmpBean objectFromData(String str) {

                    return new Gson().fromJson(str, TmpBean.class);
                }

                public String getMax() {
                    return max;
                }

                public void setMax(String max) {
                    this.max = max;
                }

                public String getMin() {
                    return min;
                }

                public void setMin(String min) {
                    this.min = min;
                }
            }

            public static class WindBean {
                private String deg;
                private String dir;
                private String sc;
                private String spd;

                public static WindBean objectFromData(String str) {

                    return new Gson().fromJson(str, WindBean.class);
                }

                public String getDeg() {
                    return deg;
                }

                public void setDeg(String deg) {
                    this.deg = deg;
                }

                public String getDir() {
                    return dir;
                }

                public void setDir(String dir) {
                    this.dir = dir;
                }

                public String getSc() {
                    return sc;
                }

                public void setSc(String sc) {
                    this.sc = sc;
                }

                public String getSpd() {
                    return spd;
                }

                public void setSpd(String spd) {
                    this.spd = spd;
                }
            }
        }

        public static class HourlyForecastBean {
            private String date;
            private String hum;
            private String pop;
            private String pres;
            private String tmp;
            /**
             * deg : 196
             * dir : 西南风
             * sc : 微风
             * spd : 6
             */

            private WindBean wind;

            public static HourlyForecastBean objectFromData(String str) {

                return new Gson().fromJson(str, HourlyForecastBean.class);
            }

            public String getDate() {
                return date;
            }

            public void setDate(String date) {
                this.date = date;
            }

            public String getHum() {
                return hum;
            }

            public void setHum(String hum) {
                this.hum = hum;
            }

            public String getPop() {
                return pop;
            }

            public void setPop(String pop) {
                this.pop = pop;
            }

            public String getPres() {
                return pres;
            }

            public void setPres(String pres) {
                this.pres = pres;
            }

            public String getTmp() {
                return tmp;
            }

            public void setTmp(String tmp) {
                this.tmp = tmp;
            }

            public WindBean getWind() {
                return wind;
            }

            public void setWind(WindBean wind) {
                this.wind = wind;
            }

            public static class WindBean {
                private String deg;
                private String dir;
                private String sc;
                private String spd;

                public static WindBean objectFromData(String str) {

                    return new Gson().fromJson(str, WindBean.class);
                }

                public String getDeg() {
                    return deg;
                }

                public void setDeg(String deg) {
                    this.deg = deg;
                }

                public String getDir() {
                    return dir;
                }

                public void setDir(String dir) {
                    this.dir = dir;
                }

                public String getSc() {
                    return sc;
                }

                public void setSc(String sc) {
                    this.sc = sc;
                }

                public String getSpd() {
                    return spd;
                }

                public void setSpd(String spd) {
                    this.spd = spd;
                }
            }
        }
    }
}
作者:lw_zhaoritian 发表于2016/10/23 18:01:21 原文链接
阅读:56 评论:0 查看评论

使用AsyncTask异步更新UI界面及原理

$
0
0

源码放在github上,地址:https://github.com/jimiy/asynctask_example.git

概述: AsyncTask是在Android SDK 1.5之后推出的一个方便编写后台线程与UI线程交互的辅助类。AsyncTask的内部实现是一个线程池,所有提交的异步任务都会在这个线程池中的工作线程内执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler传递消息的方式,调用相关的回调函数,从而实现UI界面的更新。

AsyncTask抽象出后台线程运行的五个状态,分别是:

1、准备运行,2、正在后台运行,3、进度更新,4、完成后台任务,5、取消任务

对于这五个阶段,AsyncTask提供了五个回调函数:
1、准备运行:onPreExecute(),该回调函数在任务被执行之后立即由UI线程调用。这个步骤通常用来建立任务,在用户接口(UI)上显示进度条。
2、正在后台运行:doInBackground(Params…),该回调函数由后台线程在onPreExecute()方法执行结束后立即调用。通常在这里执行耗时的后台计算。计算的结果必须由该函数返回,并被传递到onPostExecute()中。在该函数内也可以使用publishProgress(Progress…)来发布一个或多个进度单位(unitsof progress)。这些值将会在onProgressUpdate(Progress…)中被发布到UI线程。
3. 进度更新:onProgressUpdate(Progress…),该函数由UI线程在publishProgress(Progress…)方法调用完后被调用。一般用于动态地显示一个进度条。
4. 完成后台任务:onPostExecute(Result),当后台计算结束后调用。后台计算的结果会被作为参数传递给这一函数。
5、取消任务:onCancelled (),在调用AsyncTask的cancel()方法时调用

AsyncTask的构造函数有三个模板参数:
1.Params,传递给后台任务的参数类型。
2.Progress,后台计算执行过程中,进步单位(progress units)的类型。(就是后台程序已经执行了百分之几了。)
3.Result, 后台执行返回的结果的类型。
AsyncTask并不总是需要使用上面的全部3种类型。标识不使用的类型很简单,只需要使用Void类型即可。

例子:从网络上下载图片,下载完成后在UI界面上显示出来,并会模拟下载进度更新。
AsyncTaskActivity.java

package com.szy.demo;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.Toast;

public class AsyncTaskActivity extends Activity
{

 private ImageView mImageView;
 private Button mBtnDownload;
 private ProgressBar mProgressBar;

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

  mImageView = (ImageView) findViewById(R.id.imageView);
  mBtnDownload = (Button) findViewById(R.id.btnDownload);
  mProgressBar = (ProgressBar)findViewById(R.id.progressBar);
  mBtnDownload.setOnClickListener(new OnClickListener()
  {
   @Override
   public void onClick(View v)
   {
    GetImageTask task = new GetImageTask();
    task.execute("http://www.baidu.com/img/baidu_sylogo1.gif");
   }
  });
 }

 class GetImageTask extends AsyncTask<String, Integer, Bitmap>
 {
  /**
   * 处理后台执行的任务,在后台线程执行
   */
  @Override
  protected Bitmap doInBackground(String... params)
  {
   publishProgress(0);//  publicProcess会去调用onProgressUpdate 将会调用onProgressUpdate(Integer... progress)方法
   HttpClient httpClient = new DefaultHttpClient();
   publishProgress(30);
   HttpGet httpGet = new HttpGet(params[0]);// 获取csdn的logo
   final Bitmap bitmap;
   try
   {
    HttpResponse httpResponse = httpClient.execute(httpGet);
    //获取返回图片  [通过bitmapFactory解析图片流]
    bitmap = BitmapFactory.decodeStream(httpResponse.getEntity().getContent());
   } catch (Exception e)
   {

    return null;
   }
   publishProgress(100);
   return bitmap;
  }

  /**
   * 在调用publishProgress之后被调用,在UI线程执行
   */
  protected void onProgressUpdate(Integer... progress)
  {
   mProgressBar.setProgress(progress[0]);// 更新进度条的进度
  }

  /**
   * 后台任务执行完之后被调用,在UI线程执行
   */
  protected void onPostExecute(Bitmap result)
  {
   if (result != null)
   {
    Toast.makeText(AsyncTaskActivity.this, "成功获取图片", Toast.LENGTH_LONG).show();
    mImageView.setImageBitmap(result);
   } else
   {
    Toast.makeText(AsyncTaskActivity.this, "获取图片失败", Toast.LENGTH_LONG).show();
   }
  }

  /**
   * 在 doInBackground(Params...)之前被调用,在UI线程执行
   */
  protected void onPreExecute()
  {
   mImageView.setImageBitmap(null);
   mProgressBar.setProgress(0);// 进度条复位
  }

  /**
   * 在UI线程执行
   */
  protected void onCancelled()
  {
   mProgressBar.setProgress(0);// 进度条复位
  }
 }

}

Activity布局文件main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <ProgressBar
  android:id="@+id/progressBar"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  style="?android:attr/progressBarStyleHorizontal"></ProgressBar>
 <Button
  android:id="@+id/btnDownload"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="下载图片"/>
 <ImageView
  android:id="@+id/imageView"
  android:layout_height="wrap_content"
  android:layout_width="wrap_content" />
</LinearLayout>

运行效果:
这里写图片描述

这里写图片描述

AsyncTask的实现原理
在分析实现流程之前,我们先了解一下AsyncTask有哪些成员变量

private static final int CORE_POOL_SIZE =5;//5个核心工作线程
private static final int MAXIMUM_POOL_SIZE = 128;//最多128个工作线程
private static final int KEEP_ALIVE = 1;//空闲线程的超时时间为1秒
private static final BlockingQueue<Runnable> sWorkQueue =
            new LinkedBlockingQueue<Runnable>(10);//等待队列
private static final ThreadPoolExecutorsExecutor = new
      ThreadPoolExecutor(CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS,
            sWorkQueue,sThreadFactory);//线程池是静态变量,所有的异步任务都会放到这个线程池的工作线程内执行。

当点击“下载图片”按钮之后会新建一个GetImageTask对象:

GetImageTask task = new GetImageTask();

此时会调用父类AsyncTask的构造函数:
AsyncTask.java

public AsyncTask()
{
 mWorker = new WorkerRunnable<Params, Result>()
 {
  public Result call() throws Exception
  {
   Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
   return doInBackground(mParams);
  }
 };

 mFuture = new FutureTask<Result>(mWorker)
 {
  @Override
  protected void done()
  {
   Message message;
   Result result = null;
   try
   {
    result = get();
   } catch (InterruptedException e)
   {
    android.util.Log.w(LOG_TAG, e);
   } catch (ExecutionException e)
   {
    throw new RuntimeException("An error occured while executing doInBackground()", e.getCause());
   } catch (CancellationException e)
   {
    message = sHandler.obtainMessage(MESSAGE_POST_CANCEL, new AsyncTaskResult<Result>(AsyncTask.this, (Result[]) null));
    message.sendToTarget();// 取消任务,发送MESSAGE_POST_CANCEL消息
    return;
   } catch (Throwable t)
   {
    throw new RuntimeException("An error occured while executing " + "doInBackground()", t);
   }

   message = sHandler.obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(AsyncTask.this, result));// 完成任务,发送MESSAGE_POST_RESULT消息并传递result对象
   message.sendToTarget();
  }
 };
}

WorkerRunnable类实现了callable接口的call()方法,该函数会调用我们在AsyncTask子类中实现的doInBackground(mParams)方法,由此可见,WorkerRunnable封装了我们要执行的异步任务。FutureTask中的protected void done() {}方法实现了异步任务状态改变后的操作。当异步任务被取消,会向UI线程传递MESSAGE_POST_CANCEL消息,当任务成功执行,会向UI线程传递MESSAGE_POST_RESULT消息,并把执行结果传递到UI线程。 由此可知,AsyncTask在构造的时候已经定义好要异步执行的方法doInBackground(mParams)和任务状态变化后的操作(包括失败和成功)。 当创建完GetImageTask对象后,执行
1 task.execute(“http://www.baidu.com/img/baidu_sylogo1.gif“);

此时会调用AsyncTask的execute(Params…params)方法 AsyncTask.java

public final AsyncTask<Params, Progress, Result> execute(Params... params)
{
 if (mStatus != Status.PENDING)
 {
  switch (mStatus)
  {
  case RUNNING:
   throw newIllegalStateException("Cannot execute task:" + " the taskis already running.");
  case FINISHED:
   throw newIllegalStateException("Cannot execute task:" + " the taskhas already been executed " + "(a task canbe executed only once)");
  }
 }
 mStatus = Status.RUNNING;
 onPreExecute();// 运行在ui线程,在提交任务到线程池之前执行
 mWorker.mParams = params;
 sExecutor.execute(mFuture);// 提交任务到线程池
 return this;
}

当任务正在执行或者已经完成,会抛出IllegalStateException,由此可知我们不能够重复调用execute(Params…params)方法。在提交任务到线程池之前,调用了onPreExecute()方法。然后才执行sExecutor.execute(mFuture)是任务提交到线程池。
前面我们说到,当任务的状态发生改变时(1、执行成功2、取消执行3、进度更新),工作线程会向UI线程的Handler传递消息,Handler要处理其他线程传递过来的消息。在AsyncTask中,InternalHandler是在UI线程上创建的,它接收来自工作线程的消息,实现代码如下:
AsyncTask.java

private static class InternalHandler extends Handler
{
 @SuppressWarnings({"unchecked","RawUseOfParameterizedType"})
        @Override
        public voidhandleMessage(Message msg) {
            AsyncTaskResult result =(AsyncTaskResult) msg.obj;
            switch (msg.what) {
                caseMESSAGE_POST_RESULT:
                    // There is onlyone result
                    result.mTask.finish(result.mData[0]);//执行任务成功
                    break;
                caseMESSAGE_POST_PROGRESS:
                   result.mTask.onProgressUpdate(result.mData);//进度更新
                    break;
                caseMESSAGE_POST_CANCEL:
                    result.mTask.onCancelled();//取消任务
                    break;
            }
        }
}

当接收到消息之后,AsyncTask会调用自身相应的回调方法。

总结:
1、 AsyncTask的本质是一个静态的线程池,AsyncTask派生出的子类可以实现不同的异步任务,这些任务都是提交到静态的线程池中执行。
2、线程池中的工作线程执行doInBackground(mParams)方法执行异步任务 3、当任务状态改变之后,工作线程会向UI线程发送消息,AsyncTask内部的InternalHandler响应这些消息,并调用相关的回调函数

作者:xiaorui51 发表于2016/10/23 20:17:23 原文链接
阅读:81 评论:0 查看评论

基于Android移动终端的微型餐饮管理系统的设计与实现2-侧滑菜单

$
0
0

上周发了《 基于Android移动终端的微型餐饮管理系统的设计与实现》第一章,虽然是个简介,但是居然上了首页推荐,还有朋友评论了。写了这么久CSDN,还是第一次有人评论我的文章,真是受宠若惊,长期以来,我一直都以为,我玩的是CSDN单机版。当然,这也说明,我过去写的东西都太烂了,不过这也算是我真实水平的反应吧,希望随着自身水平的提高,能写出更好的博文来,即使写不出,也算是自己成长的记录吧。


今天我先来写一下侧滑菜单,如图:




侧滑菜单的实现方法有很多,网上也有完整的开源框架,现在如果用Android Studio开发的话,一开始选模板的时候就可以选择一个带侧滑菜单的模板。


具体实现方法可以看这篇文章:

http://blog.csdn.net/lmj623565791/article/details/36677279


我就把我写的代码贴出来给大家好了


xml文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/slidingmenu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/aiw2"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center_vertical"
        android:text="@string/text_btnAddFragment"></LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnAddFragment"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="@string/text_btnAddFragment" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnOrderFragment"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="@string/text_btnOrderFragment" />
    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnDataFragment"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="@string/text_btnDataFragment" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnPrinterListFragment"
            style="@style/style_text"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            android:text="@string/text_btnPrinterListFragment" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnBluetoothFragment"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="@string/text_btnBluetoothFragment" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnSignUpFragent"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="@string/text_btnSignUpFragent" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnFoodListFragment"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="查看菜单" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_gravity="center"
            android:src="@drawable/leaf2" />

        <TextView
            android:id="@+id/btnClose"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center_vertical"
            style="@style/style_text"
            android:text="退出" />
    </LinearLayout>

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"

        android:layout_gravity="center_horizontal"
        android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>


然后是java代码,因为侧滑菜单的主要代码基本都写在MainActivity里,我就都贴出来了,启动应用的时候就先启动这个activity,这个小应用主要也是这个activity了,各个页面基本都是以fragment的形式依附在这个activity里的,所以一些设置私有云的代码还有读写上次退出时所在状态的代码也在这里

package com.exapmle.pc.restaurantclient;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.TextView;

import com.exapmle.pc.restaurantclient.Bluetooth.PrinterConnectDialog;
import com.exapmle.pc.restaurantclient.Fragments.AddFragment;
import com.exapmle.pc.restaurantclient.Fragments.BluetoothConnectFragment;
import com.exapmle.pc.restaurantclient.Fragments.DataFragment;
import com.exapmle.pc.restaurantclient.Fragments.FoodListFragmet;
import com.exapmle.pc.restaurantclient.Fragments.OrderFragment;
import com.exapmle.pc.restaurantclient.Fragments.PrinterSettingFragment;
import com.exapmle.pc.restaurantclient.Fragments.SignUpFragment;
import com.gprinter.aidl.GpService;
import com.gprinter.io.GpDevice;
import com.gprinter.service.GpPrintService;
import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu;

import cn.bmob.v3.Bmob;

/*
1.录入菜品
1.1 从手机获取照片
1.2 调用摄像头
1.3 菜名 价格 单位 简介 规格1 规格2

 2.点餐
 3.数据统计
  */
public class MainActivity extends Activity implements View.OnClickListener {
    private static final String strState = "state";
    private static final String strStateMain = "stateMain";

    private int stateMain = 1;
    private AddFragment addFragment;
    private OrderFragment orderFragment;
    private DataFragment dataFragment;
    private Fragment bluetoothFragment;
    private BluetoothConnectFragment printerListFragment;
    private SignUpFragment signUpFragment;
    private TextView tvAddFragment;
    private TextView tvOrderFragment;
    private TextView tvDataFragment;
    private TextView tvBluetoothFragment;
    private TextView tvPrinterListFragment;
    private TextView tvClose;
    private TextView tvSignUpFragment;
    private TextView btnFoodListFragment;
    private SlidingMenu menu;

    public static GpService mGpService= null;
    public static final String CONNECT_STATUS = "connect.status";
    private static final String TAG = "MainActivity";
    private PrinterServiceConnection conn = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e(TAG,"4444444444444444444");

        //读取显示界面状态数据
        SharedPreferences sp = this.getSharedPreferences(strState, MODE_PRIVATE);
        if (sp.getInt(strStateMain, 0) != 0) {//如果这个key不存在的话返回的数值
            stateMain = sp.getInt(strStateMain, 1);
        } else {
            changeMainState(1);
        }

        // 初始化 Bmob SDK
        // 使用时请将第二个参数Application ID替换成你在Bmob服务器端创建的Application ID
        Bmob.initialize(this, "******************************");
        connection();
        init();
    }
    /*
    以下为bluetooth建立连接的内容
     */
    private void startService() {
        Intent i= new Intent(this, GpPrintService.class);
        startService(i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void connection() {
        conn = new PrinterServiceConnection();
        Intent intent = new Intent("com.gprinter.aidl.GpPrintService");
        bindService(intent, conn, Context.BIND_AUTO_CREATE); // bindService
    }
    class PrinterServiceConnection implements ServiceConnection {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i("ServiceConnection", "onServiceDisconnected() called");
            mGpService = null;
        }
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mGpService = GpService.Stub.asInterface(service);
        }
    }

    private void init() {
        /*
        设置slidingmenu
         */
        // configure the SlidingMenu
         menu = new SlidingMenu(this);
        menu.setMode(SlidingMenu.LEFT);
        // 设置触摸屏幕的模式
        menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN);
        menu.setShadowWidthRes(R.dimen.shadow_width);
        //menu.setShadowDrawable(R.drawable.shadow);
        //menu.setBehindOffsetRes(R.dimen.slidingmenu_offset);//SlidingMenu划出时主页面显示的剩余宽度
        menu.setBehindWidth(400);//设置SlidingMenu菜单的宽度
        // 设置渐入渐出效果的值
        menu.setFadeDegree(0.35f);

        /**
         * SLIDING_WINDOW will include the Title/ActionBar in the content
         * section of the SlidingMenu, while SLIDING_CONTENT does not.
         */
        menu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT);
        //为侧滑菜单设置布局
        menu.setMenu(R.layout.slidingmenu_left);
        //设置要使菜单滑动,触碰屏幕的范围
        menu.setTouchModeAbove(SlidingMenu.TOUCHMODE_MARGIN);
        menu.setOnClickListener(this);
        /*
        设置slidingmenu里的按钮
        */
        tvAddFragment= (TextView) findViewById(R.id.btnAddFragment);
        tvAddFragment.setOnClickListener(this);
        tvOrderFragment= (TextView) findViewById(R.id.btnOrderFragment);
        tvOrderFragment.setOnClickListener(this);
        tvDataFragment= (TextView) findViewById(R.id.btnDataFragment);
        tvDataFragment.setOnClickListener(this);
        tvBluetoothFragment=(TextView) findViewById(R.id.btnBluetoothFragment);
        tvBluetoothFragment.setOnClickListener(this);
        tvPrinterListFragment= (TextView) findViewById(R.id.btnPrinterListFragment);
        tvPrinterListFragment.setOnClickListener(this);
        tvSignUpFragment= (TextView) findViewById(R.id.btnSignUpFragent);
        tvSignUpFragment.setOnClickListener(this);
        tvClose= (TextView) findViewById(R.id.btnClose);
        tvClose.setOnClickListener(this);

        btnFoodListFragment= (TextView) findViewById(R.id.btnFoodListFragment);
        btnFoodListFragment.setOnClickListener(this);
        setFragment();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnClean: {
                break;
            }
            case R.id.btnSave: {
                break;
            }
            case R.id.btnAddFragment:{
                changeMainState(1);
                setFragment();
                break;
            }
            case R.id.btnOrderFragment: {
                //layoutBottom2.setBackgroundColor(Color.RED);
                changeMainState(2);
                setFragment();
                break;
            }
            case R.id.btnDataFragment: {
                //layoutBottom2.setBackgroundColor(Color.RED);
                changeMainState(3);
                setFragment();
                break;
            }
            case R.id.btnPrinterListFragment: {
                //layoutBottom2.setBackgroundColor(Color.RED);
                changeMainState(4);
                setFragment();
                break;
            }
            case R.id.btnBluetoothFragment: {
                //layoutBottom2.setBackgroundColor(Color.RED);
                changeMainState(5);
                //setFragment();
                printerList();
                break;
            }
            case R.id.btnSignUpFragent: {
                //layoutBottom2.setBackgroundColor(Color.RED);
                changeMainState(6);
                setFragment();
                break;
            }
            case R.id.btnFoodListFragment: {
                changeMainState(7);
                setFragment();
                break;
            }
            case R.id.btnClose: {
                finish();
                break;
            }
            default:
                break;
        }
    }

    //转跳到打印机列表
    private void printerList() {
        Log.d(TAG, "openPortConfigurationDialog ");
        Intent intent = new Intent(this,
                PrinterConnectDialog.class);
        boolean[] state = getConnectState();
        intent.putExtra(MainActivity.CONNECT_STATUS, state);
        this.startActivity(intent);
    }


    public boolean[] getConnectState() {
        boolean[] state = new boolean[GpPrintService.MAX_PRINTER_CNT];
        for (int i = 0; i < GpPrintService.MAX_PRINTER_CNT; i++) {
            state[i] = false;
        }
        for (int i = 0; i < GpPrintService.MAX_PRINTER_CNT; i++) {
            try {
                if (MainActivity.mGpService.getPrinterConnectStatus(i) == GpDevice.STATE_CONNECTED) {
                    state[i] = true;
                }
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return state;
    }
    private void changeMainState(int i) {
        stateMain = i;
        SharedPreferences sp = this.getSharedPreferences(strState, MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();
        editor.putInt(strStateMain, stateMain);
        editor.commit();
        menu.showContent();
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void setFragment() {
        FragmentManager fm = getFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();

        switch (stateMain) {
            case 1: {
                addFragment = new AddFragment();
                transaction.replace(R.id.layout_main, addFragment);
                changeMainState(1);
                break;
            }
            case 2: {
                orderFragment = new OrderFragment();
                transaction.replace(R.id.layout_main, orderFragment);
                changeMainState(2);
                break;
            }
            case 3: {
                dataFragment = new DataFragment();
                transaction.replace(R.id.layout_main, dataFragment);
                changeMainState(3);
                break;
            }
            case 4: {
                bluetoothFragment = new PrinterSettingFragment();
                transaction.replace(R.id.layout_main, bluetoothFragment);
                changeMainState(4);
                break;
            }
            case 5: {
                printerListFragment = new BluetoothConnectFragment();
                transaction.replace(R.id.layout_main, printerListFragment);
                changeMainState(5);
                break;
            }
            case 6: {
                signUpFragment = new SignUpFragment();
                transaction.replace(R.id.layout_main, signUpFragment);
                changeMainState(6);
                break;
            }
            case 7: {
                FoodListFragmet foodListFragmet = new FoodListFragmet();
                transaction.replace(R.id.layout_main, foodListFragmet);
                changeMainState(7);
                break;
            }
            default:
                break;
        }
        transaction.addToBackStack(null);
        transaction.commit();
    }

}

    


Bmob云服务平台请看这里:

http://www.bmob.cn/


顺便把activuty_main.xml里的代码贴一下,里面虽然没有什么东西,但是这个界面很重要,因为其它界面都是依附在它上面的

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <LinearLayout
        android:id="@+id/layout_main"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="8.3"
        android:orientation="horizontal">

    </LinearLayout>

</LinearLayout>




作者:qq_15062527 发表于2016/10/23 20:21:40 原文链接
阅读:79 评论:0 查看评论

移动设备无线投屏到电视机

$
0
0

移动设备无线投屏到电视机主要有三种种技术:AirPlay、DLNA与Miracast技术。

AirPlay 

AirPlay 是苹果开发的一种无线技术,可以通过WiFi将iPhone 、iPad、iPodtouch 等iOS 设备上的包括图片、音频、视频通过无线的方式传输到支持AirPlay 设备。

AirPlay具备有镜像功能,这一功能叫AirPlay镜像,可将iPhone 或iPad 上的画面无线传输到电视上,也就是说你设备显示的是什么,电视屏幕显示就就是什么,而不仅限于图片和视频。

DLNA

DNLA,Digital Living Network Alliance,是索尼、英特尔、微软等发起的一套 PC、移动设备、消费电器之间互联互通的协议。它们的宗旨是“随时随地享受音乐、照片和视频”。

DLNA与苹果的AirPlay功能比较类似,协议也大体相同,他们都可以让你手机中的媒体内容投放到电视屏幕里。不同的是手机上的DLNA 并没有类似Apple TV的AirPlay 的镜像功能,也没有Apple TV 所支持的双屏体游戏体验。目前DLNA更多只是能将手机的照片和视频投送到大屏幕中。

Miracast

Miracast是由Wi-Fi联盟于2012年所制定,以Wi-Fi直连为基础的无线显示标准。支持此标准的设备可通过无线方式分享视频画面,例如手机可通过Miracast将影片或照片直接在电视或其他装置播放而无需受到连接线缆长度的影响。与DLNA不同的是,Miracast 也有类似于AirPlay 的镜像功能,可以将手机中屏幕内容直接投放到高清电视屏幕里,这样也可以通过电视屏幕来玩游戏了。

Android投屏主要还是使用DLNA。DLNA开发比较主流的库有Cling,还有CyberLink4Android

这两个库的github地址为:

https://github.com/CharonChui/CyberLink4Android

https://github.com/4thline/cling

相比较而言,CyberLink4Android这个库用起来跟简单一些。

DLNA详细介绍

一、DLNA简介

DLNA成立于2003年6月24日,其前身是DHWG(Digital Home Working Group 数字家庭工作组),由Sony、Intel、Microsoft等发起成立、旨在解决个人PC ,消费电器,移动设备在内的无线网络和有线网络的互联互通,使得数字媒体和内容服务的无限制的共享和增长成为可能,目前成员公司已达280多家。DLN全称为DIGITAL LIVING NETWORK ALLIANCE, 直译成中文就是“数字生活网络联盟”,其宗旨是Enjoy your music, photos and videos, anywhere anytime.

二、DLNA成员

这个组织将加入者分为两个层次,最高层次为promoter, 其次为contributor。promoter制定标准和协议,contributor可以分享这个组织的资源,也可以提交标准,参与讨论。现在绝大多数的电子制造商都加入了该组织,至少是contributor,而且年费还很贵。成员名单可以从http://www.dlna.org/about_us/roster/中可以找到。
DLNA的骨干成员包括以Intel为首的芯片制造商;以HP为首的PC制造商,以Sony,Panasonic,Sharp,Samsung,LG 为首的家电、消费电子制造商;以CISCO,HUWEI,MOTOROLA,ERICSSON为首的电信设备/移动终端/标准商;一家独大的Microsoft软件/操作系统商等等。
值得注意的有几点:
DLNA这个东西基本Intel,Microsoft两个领域巨头在推,一个搞芯片,一个搞系统。AMD没出现在2011的promoter名单中;Google来年会不会掺一脚不好说。还有QUALCOMM也参加进来了,这几年的智能手机芯片处理器他家的也比较多,而且他家还有很多专利可以吃。
2011就剩HP一个大PC商了,其他大PC商如Acer,Asus都还不是promoter,他们肯定要抢着加入的。lenovo不仅从promotor名单中消失了,自然也不会是contributor了,和AMD一样。最开始时lenovo是很积极的,在DHWG的时候也是骨干成员,回来中国搞了一个“IGRS闪联”,退出的原因不知道和这个有没有关系。IGRS在很大程度上和DLNA是比较类似的,框架协议和UPnP也是比较像的。
Awox和Cablelabs都是做互联多媒体设备的。Broadcom主要是做移动消费电子,有硬件solution,也有产芯片。
ACCESS(爱可视)是做软件的。现在软件的需求很大,给第三方提供软件solution是一块很大的蛋糕。cyberlink和arcsoft也在做这方面,已经有些成熟的软件solution了,像EMC,NeuSoft也有在做。
运营商开始加入了,像at&t美国电报电话公司,at&t也挺厉害的,到处搞签约机,像是跟PSP VITA也签了。以后中国移动联通不知道会不会也跑来参加(有点难...)。
dts和dolby都是做音视频标准的,他们基本是跑来收钱的,你机器上到他们的专利你就得付钱,跟以后肯定其他人也会跑来收钱。

三、DLNA标准的制定

该组织旨在建立一个基于开放的工业标准的互操作平台,并将确立技术设计规则,供企业开发数字家庭有关的产品。其工作目标是根据开放工业标准制定媒体格式,传输和协议互操作性的指南和规范,和其他工业标准化组织进行联络,提供互操作性测试,并进行数字家庭市场计划的制定和实施。
DLNA并不是创造技术,而是形成一种解决的方案,一种大家可以遵守的规范。所以DLNA选择的各种技术和协议都是目前所应用很广泛的技术和协议。所以很多家都要参加,希望DLNA采纳自己的协议和标准,以后自己好办事,可以的话顺便吃点专利费。大方向上肯定打不过Intel和Microsoft的,只能跟着他们走,可以提起其他方面的协议和标准。DLNA的标准写在DLNA GUIDELINES里面,就是大家开会一起写出来的,再开会不停修改的一个standard,一个specification。参加DLNA的商家必须按这个标准走。里面内容不太清楚,我现在没有这个GUIDELINES,这个必须是DLNA会员才能拿到,加会员要10000刀。

四、DLNA设备分类

Home NetWork Device(HND)。这类设备指家庭设备,具有比较大的尺寸及较全面的功能,主要与移动设备区别开来,下属5类设备:
Digital Media Server(DMS)。数字媒体服务器,提供媒体获取、记录、存储和输出功能。同时,内容保护功能是对DMS的强制要求。
DMS总是包含DMP的功能,并且肯能包含其他智能功能,包括设备/用户服务的管理;丰富的用户界面;媒体管理/收集和分发功能。DMS的例子有PC、数字机顶盒(附带联网,存储功能)和摄像机等等。
DMP。数字媒体播放器。能从DMS/M-DMS上查找并获取媒体内容并播放和渲染显示。比如智能电视、家庭影院等
DMC。数字媒体控制器,查找DMS的内容并建立DMS与DMR之间的连接并控制媒体的播放。如遥控器。
DMR。数字媒体渲染设备。通过其他设备配置后,可以播放从DMS上的内容。与DMP的区别在于DMR只有接受媒体和播放功能,而没查找有浏览媒体的功能。比如显示器、音箱等。
DMPr。数字媒体打印机,提供打印服务。网络打印机,一体化打印机就属于DMPr。
Mobile Handheld Devices(MHD)手持设备。相比家庭设备,手持设备的功能相对简化一些,支持的媒体格式也会不同。
M-DMS。与DMS类似,如移动电话,随身音乐播放器等。
M-DMP。与DMP类似。比如智能移动电视。
M-DMD。移动多媒体下载设备。如随身音乐播放器,车载音乐播放器和智能电子相框等
M-DMU。移动多媒体下载设备。如摄像设备和手机等。
M-DMC。与DMC类似。P如DA,智能遥控器。 手持设备没有定义M-DMR,因为手持设备会讲究便利性,会附加查找控制功能,要不然就只是普通的移动电视或收音机了。
Networked Infrastructure Devices (NID) 联网支持设备。
Mobile Network Connectivity Function (M-NCF)。移动网络连接功能设备。提供各种设备接入移动网络的物理介质。 DLNA的希望是全部实现无线化。
Interoperability Unit (MIU)媒体交互设备。提供媒体格式的转换以支持各种设备需要。
设备示例:
你下了班回到家,掏出手机拨到家庭模式,然后就在手机上遥控打开了等离子电视和PC,然后把订阅的新闻通过PC下载完成后打到等离子电视上播放。这时手机就是一个DMC/M-DMC,等离子电视是一个DMR,PC就是DMS。然后你手机上收到一张朋友从巴西传来的照片,你看完之后把它同步到PC上存储起来,这样手机现在的身份是M-DMU,然后你把这张图片放到电子相框里面。这个电子相框就是一个M-DMD,相框也有play的能力,所以他又是一个M-DMP。所以说这些设备的功能角色都是不定的,界限也不是那么严格。在DLNA Guidelines v1.0的时候还没有智能手机,后来在v1.5加入了。这个设备分类只是定义了功能,而且功能也会变的。以后还会出其它新设备,像pad,tab,touch各种各样,到时候标准也会变的。
现在目前的一些电视盒子大多都是DMS,像腾讯视频Android客户端有DLNA的功能,我们通过该客户端可以投放视频到盒子上面进行播放,对于此时手机就是一个DMC的功能。
又或者盒子通过DLNA功能共享出了一些视频、图片等,我们开发一个手机客户端,通过该客户端去获取盒子共享的内容,然后进行播放,这时候手机就相当于一个DMP的功能,稍后我们会分别实现DMC以及DMP的功能。

五、DLNA架构

DLNA架构是个互联系统,因此在逻辑上它也类似OSI(Open System Interconnection,开放系统互连)七层网络模型。
DLNA架构分为如下图7个层次:
NetWorking Connectivity 网络互联方式:包括物理连接的标准,有有线的,比如符合IEEE802.3标准的Ethernet,;有无线的 ,比如符合IEEE802.11a/g标准的WiFi,能做到54Mbps,蓝牙(802.15)等,技术都很成熟。现在OFDM和MIMO(802.11n)已经能做到300Mbps了,早就超过比较普及的100Mbps的Ethernet了,只不过产品还没有普及,以后肯定会用到。
NetWorking Stack 网络协议栈:DLNA的互联传输基本上是在IPV4协议簇的基础上的。用TCP或者UDP来传都可以。这一层相当于OSI网络层。
Device Discovery&Control 设备发现和控制。 这一层是DLNA的基础协议框架。DLNA用UPnP协议来实现设备的发现和控制。下面重点看一下UPnP。
UPnP,英文是Universal Plug and play,翻译过来就是通用即插即用。UPnP最开始Apple和Microsoft在搞,后来Apple不做了(这里多一嘴,为什么Apple不做了,因为Apple现在出了个),Microsoft还在继续做,Intel也加进来做,Sony,Moto等等也有加入。UPnP有个网站http://www.upnp.org/,我们发现DLNA的网页和UPnP的网页很像,颜色也差不多,就可以知道他们关系很好了。DNLA主要是在推UPnP。
微软官方网站对UPnP的解释:通用即插即用 (UPnP) 是一种用于 PC 机和智能设备(或仪器)的常见对等网络连接的体系结构,尤其是在家庭中。UPnP 以 Internet 标准和技术(例如 TCP/IP、HTTP 和 XML)为基础,使这样的设备彼此可自动连接和协同工作,从而使网络(尤其是家庭网络)对更多的人成为可能。 举个例子。我们在自己的PC(win7)里面打开网络服务的UPnP选项,然后再家庭网络中共享一个装着视频的文件夹,然后买一台SmartTV回来打开就可以找到这台PC的共享文件夹,然后就直接在电视上选文件播放了。 UPnP的另外一个作用是给家庭网内的devices做自动的网络地址转换NAT(NAT,Network Address Translation)和端口映射(Port Mapping),因为家庭网络里面没有那么多IP,所有的devices可能都要通过同一个ip出去。转换映射之后,家庭网络内外的devices就可以通过internet自由地相互连接,而不受内网地址不可访问的阻碍。
UPnP Device Architecture 1.0中会说明设备是怎样通过UPnP来相互发现和控制,以及传递消息的。
Media Management媒体管理。
媒体管理包括媒体的识别、管理、分发和记录(保存),UPnP AV Architecture:1 and UPnP Printer Architecture:1这两个属于UPnP的文档会说明如何进行媒体管理。 UPnP AV Architecture 定义了UPnP AV设备间媒体传送以及和CP间的交互。UPnP AV也定义了两种UPnP AV设备:UPnP AV MediaServer(MS)和UPnP AV MediaRender(MR),以及他们具有的4种服务:
Content Directory Service(CDS):能将可访问的媒体内容列出。
Connection Manager Service(CMS):决定媒体内容可以通过何种方式由UPnP AV Media Server传送至UPnP AV MediaRender。
AVTransport Service:控制媒体内容,比如播放、停止、暂停、查找等。
Rendering Control Service:控制以何种方式播放内容,比如音量、静音、亮度等。
Media Transport 媒体传输: 这一层用HTTP(HyperText Transfer Protocol)超文本传输协议。就是平时我们上网用的媒体传输协议。HTTP用TCP可靠传输,也有混合UDP方式的HTTP。现在HTTP的最新版本是HTTP1.1。可选协议是RTP。 举例:我们输入一个网址,回车,给server发一个request,用TCP我们就可以等server给我们消息,说明server收到我们的消息了,否则我们就重发;接着server给我们TCP包,我们收一个就给server回信说我们收到了,要是server收不到回信,他就认为包丢掉了,会再传一个同样的包过来。不停地回信就是会比较慢。
那如果我们用UDP会怎样?就是说我们不给server回信说我们收到编号是x的包了,server也就不给我们重发丢掉的包了,这样我们就丢包了。
但是我们传stream的时候,比如视频流,不用存,看完就完了,这种时候就可以用UDP来传。加上局域网里面QoS本来就很高,丢包都是不太可能的。所以UDP肯定会用。局域网多播的时候也用UDP,这个在后面讲。 媒体的传输方案如下:
从DMS/M-DMS至DMP/M-DMP,即使不立即播放。
从一个DMS到另一个DMS,这时接收方DMS播放接收媒体内容,表现为一个DMP;也可以不立即播放,可能只是存储或者处理。
传输 模式有三种:
流传输。当DMR/DMP需要实时渲染接收媒体,媒体具时序性。
交互传输。不包含时序的媒体,如图片传输。
后台传输。非实时的媒体传输,比如上传下载等。
Media Formats媒体格式。格式Formats在这里等同于编码格式Codec,平时我们说的编码格式比如Mpeg-2,AVC,x264就是视频编码格式;PCM,mp3(MPEG-2 Layer 3),aac,flac就是音频编码格式。而avi,rmvb,mkv这些是媒体封装格式,包含视频音频可能还有字幕流。比如一个常见的后缀为mkv的文件,它的视频Codec是x264,音频是aac,它的视音频编码属于Mpeg-4 Codec Family。
Remote UI 远程用户接口。 说白了就是遥控器。比如说有个TV,我们说不管是用遥控器还是直接在TV上按按钮,效果是一样的。不过两者按钮的排列布局是不一样的。好了,现在到DLNA了,我想用手机当遥控器可不可以?当然可以,只要获得TV上按钮的功能,传到手机上来,模拟一个遥控器就好了。DLNA现在想用浏览器的方式,TV给你一个XML,手机上就出现遥控器界面了,有点像webQQ,webOS那种,这样在手机上就不需要客户端了,TV功能更新了,手机直接跟TV要新的XML,很方便。

六、DLNA可以支持的格式

Image:JPEG PNG, GIF, TIFF
Audio:LPCM AAC, AC-3, ATRAC 3plus, MP3, WMA9
AV: MPEG2 MPEG-1, MPEG-4, AVC, WMV9


欢迎扫描二维码,关注公众号



作者:robertcpp 发表于2016/10/23 21:31:37 原文链接
阅读:837 评论:0 查看评论

MyEclipse10 激活教程

$
0
0

首先要保证jdk环境变量已经配置好


双击run.bat
【第一步】:输入任意用户名
【第二步】:点击Systemid... 按钮,自动生成本机器的systemid。
【第三步】: 点菜单Tools->RebuildKey
【第四步】:点击active按钮.会在显示区域生成
LICENSE_KEY
ACTIVATION_CODE
ACTIVATION_KEY
这时候不要打开myeclipse的激活页面输入。
【第五步】:打开菜单Tools->ReplaceJarFile,弹出文件选择对话框,定位
到MyEclipse的安装目录common文件夹下选择plugins文件夹(MyEclipse\Common\plugins\)
点击打开,程序会卡住,不要担心,正在替换文件呢!
一会之后,会输出信息,文件已被替换
【第六步】:点菜单Tools->SaveProperites
OK 。打开你的myeclipse已经不需要再输入激活码什么的了。


在这里我在更新一下。今天我同学在激活的过程中发现双击run.bat会一闪而过。

如果你保证你的jdk环境变量已经配置好。复制你的软件安装路径,比如我的


然后右键编辑run.bat文件


那么右键编辑run.bat。

第一行写入你的软件的根目录 比如我的是C盘

我的第一行写入 c:

第二行写cd 粘贴根目录地址

我的第二行写 cd C:\Users\scx\Desktop\MyEclipse10.0 注册破解\MyEclipse10.0 Crack

第三行 使用java运行jar文件

java -jar me_active.jar


我的全部代码:


然后保存 双击打开即可。


破解软件下载地址

作者:su20145104009 发表于2016/10/23 22:13:36 原文链接
阅读:215 评论:0 查看评论

记一个同时支持模糊匹配和静态推导的Atom语法补全插件的开发过程: 序

$
0
0

模糊提示

静态推导

简介

过去的一周,都睡的很晚,终于做出了Atom上的APICloud语法提示与补全插件:apicloud_autocomplete .个中滋味,感觉还是有必要记录下来的.代码基于 GPL-3.0 开源,所以我可以较为详细的记录一些很难被理解和体会的技术细节.APICloud目前已有Studio,VSCode,Webstrom和Sublime的语法补全插件,但是毫无疑问,我做的这款,是目前为止最好的 – 唯一的一个支持100%所有API,唯一的一个同时支持模糊匹配和静态推导语法提示插件!

可能你会说,估计是Atom语法补全的扩展机制灵活等等吧!但是,我可以很明确地告诉你,核心逻辑是基于正则匹配的通用逻辑,和Atom没有必然的联系! apicloud_autocomplete ,需要多个技术栈的创造性地混合使用,某种程度上,这个系列的文章,就是写给全栈开发工程师的赞歌!哈哈~

你会耐心读完整个系列文章的N个可能性

  • 你可能想做一个ReactNative或者Weex的API级别的语法提示与补全插件!注意,我说的是精确到特定API的提示,而不是简单的通用语法提示.比如现在有好多jsx语法自动补全的提示,但是并没有能真正提示某个模块的某个方法或者某个属性的ReactNative或者Weex的插件.
  • 你可能对网页数据的针对性抽取感兴趣.从HTML格式的数据中,按照特定规则,抽离特定的数据,正则固然可以,但是我推荐你使用 pup.这个Task,使用了非常复杂的pup使用技巧,值得一读.
  • 你可能对正则表达式的深入使用感兴趣.刚开始,基于Atom的分析树写的,但是通用性太弱,后来就改成基于正则的了.展示了一些复杂的正则用法,比如后向匹配.不得不说,正则表达式式,太强大了!
  • 你可能对较大量数据的清洗和格式化感兴趣.文章将展示一些你可能以后也会需要的shell脚本.顺序很重要!

难点与技术点一览

  • 海量数据,却没有现成的获取模块api信息的通用接口.300多个模块,几千条api,如果一条条录入其方法名,代码模块,没有 30 天,真的很难搞定!但是,我只用了 3 天!简单的shell知识,还是挺有帮助的.
  • 模糊提示.这个是很实用的功能,实现起来还是需要一点点正则技巧的.
  • 静态推导,即根据上下文推断变量正式模块类型.仔细想想,或许你能理解问题的困难之处 – 你只是一个语法提示,是不能真实地执行代码的,你要做一个静态分析,来推断出某个变量对应的模块的类型,进而在其模块信息内部搜索相关的api提示!

系列文章规划

现在的工作,我很难每天都有时间去写博客.尽量这个系列在周内更新完;如果delay了,还请见谅!当然,插件本身的逻辑代码已经写就,大家可以直接去看github上阅读:apicloud_autocomplete 插件源码

  • (一) 抓取需要的模块信息. – 会分享一个基于公开文档的完整的模块信息数据压缩包呦!

  • (二) apicloud_autocomplete 架构设计与实现. – 会着重讲述”模糊匹配”与”静态推导”的正则技巧.

  • (三) 清洗数据,导入插件. –你在看的时候,更多有价值的信息在数据清洗部分;但是我想说的是,当你把完整的真实数据导入既定功能代码中,当插件终于有了完整数据,被赋予完整生命,竟然还能运行的时候,那种兴奋,真的是很难言表!大家有兴趣,有时间,一定要自己搞下这个!

关于 GPL-3.0

我努力寻找商业竞争和技术共享之间的结合点,目前为止我发现基于 GPL-3.0 可以很好地平衡这两点.

  • 他人修改代码后,不可以闭源;
  • 新增代码,不需要采用同样许可证;
  • 不需要对源码的修改之处,提供说明文档;

参考资源

作者:sinat_30800357 发表于2016/10/23 22:32:27 原文链接
阅读:216 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>