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

AVL树

$
0
0

AVL树>

     在之前我实现了二叉搜索树,但是二叉搜索树存在问题,就是当输入单调增或者单调减的结点数据后,二叉树就退化成类似链表的结构了,为了解决二叉搜索树的这种弊端就引入了AVL树.

AVL树的性质>

     1).左子树和右子树的高度之差的绝对值不超过1.

     2).树中的每个左子树和右子树都是AVL树.

     3).每个结点都有一个平衡因子,任一结点的平衡因子都是{-1,0,1},(每个结点的平衡因子等于右子树的高度-左子树的高度).

      AVL树是一种高度平衡的二叉搜索树,它要求所有节点所在的二叉树都满足一个条件就是:平衡因子不大于2,此时搜索二叉树也就近似于满二叉树了,但是要实现这样的一棵树就需要不停的旋转.

     下面就让我们来旋转不平衡的AVL树吧,我总结了以下四种可能出现的旋转>

     

       1.左单旋>

         

       2.右单旋>

         

       3.右左双旋>

            

       4.左右双旋>

       

     细心的童鞋就会发现了,我在右左双旋和左右双旋的的情况中我画了两种不同的插入插入位置,这并不是无聊,而是在双旋的时候插入位置的不同就会对平衡因子造成影响.

     构造了这样的一颗AVL树之后如何判断这棵树是否是平衡树呢?

     最简单也就是最容易想到的就是对这颗树的每一个节点进行遍历,得到这个结点的平衡因子进行判断,看是否合法.我们知道一颗树满足平衡树的条件就是>平衡因子的绝对值不大于2.这种想法虽然可以实现但是毕竟太费时间了,从时间复杂度上来说,我们需要求深度也需要遍历每个结点,时间复杂度是O(N*N).

     有没有可能优化呢?我们是每次都要求子树的深度,这就是时间复杂度高的原因,假设这样一种情况>在计算深度的同时也进行每个结点的判断,这样是不是就优化了呢?当然是啦,我们先判断左结点,如果左结点的左右都合法我们就+1并向上一层返;然后进行右结点的判断,最后再进行当前结点的判断.这种想法有点类似后序遍历的思路.它的时间复杂度为O(N).

代码实现区>

      

#pragma once

template<class K,class V>
struct AVLTreeNode
{
	K _key;
	V _value;
	AVLTreeNode<K,V>* _left;
	AVLTreeNode<K,V>* _right;
	AVLTreeNode<K,V>* _parent;
	int _bf;       //平衡因子  --右子树的高度减左子树
	AVLTreeNode(const K& key,const V& value)
		:_key(key)
		,_value(value)
		,_left(NULL)
		,_right(NULL)
		,_parent(NULL)
		,_bf(0)
	{}
};

template<class K,class V>
class AVLTree
{
	typedef AVLTreeNode<K,V> Node;
public:
	AVLTree()
		:_root(NULL)
	{}
	~AVLTree()
	{
		_Destroy(_root);
	}
	bool Insert(const K& key,const V& value)
	{
		if(_root == NULL)     //空
		{
			_root=new Node(key,value);
			return true;
		}
		Node *cur=_root;
		Node *parent=NULL;
		while (cur)
		{
			if(cur->_key < key)
			{
				parent=cur;
				cur=cur->_right;
			}
			else if (cur->_key > key)
			{
				parent=cur;
				cur=cur->_left;
			}
			else               //找到相同的结点
				return false;
		}
		cur=new Node(key,value);
		cur->_parent=parent;
		if(parent->_key < key)
			parent->_right=cur;
		else                   //parent->_key > key
			parent->_left=cur;

		while (parent)
		{
			if(parent->_left == cur)
				--parent->_bf;
			else if(parent->_right == cur)
				++parent->_bf;
			if(parent->_bf == 0)      //满足AVL树
				return true;
			else if(parent->_bf == 1 || parent->_bf == -1)
			{
				//继续向上调整
				cur=parent;
				parent=parent->_parent;
			}
			else     //2 -2   旋转
			{
				if(parent->_bf == 2)
				{
					if(cur->_bf == 1)         //左单旋
						_RotateL(parent);
					else if(cur->_bf == -1)
						_RotateRL(parent);    //右左双旋
				}
				else   //-2
				{
					if(cur->_bf == 1)
						_RotateLR(parent);    //左右双旋
					else if(cur->_bf == -1)
						_RotateR(parent);     //右单旋
				}
			}
		}
		return true;
	}
	void InOrder()
	{
		_InOrder(_root);
		cout<<endl;
	}
	bool IsBalance()
	{
		return _IsBalance(_root);
	}
	bool IsBalanceOP()
	{
		int height=0;
		return _IsBalanceOP(_root,height);
	}
protected:
	bool _IsBalanceOP(Node *root,int &height)   
	{
		if(root == NULL)
		{
			height=0;
			return true;
		}
		int left=0;
		_IsBalanceOP(root->_left,height);
		int right=0;
		_IsBalanceOP(root->_right,height);
		int bf=right-left;
		if(abs(bf) < 2)             //平衡因子合法
		{
			height=1 + left > right ? left : right;
			return true;
		}
		return false;
	}
	void _InOrder(Node *root)
	{
		if(root == NULL)
			return ;
		_InOrder(root->_left);
		cout<<root->_key<<" ";
		_InOrder(root->_right);
	}
	bool _IsBalance(Node *root)
	{
		if(root == NULL)
			return true;
		int left=_HeightTree(root->_left);
		int right=_HeightTree(root->_right);
		int bf=right-left;
		if(root->_bf != bf)
		{
			cout<<"Is UnBalance:"<<root->_key<<endl;
			return false;
		}
		return abs(bf) < 2 && _IsBalance(root->_left) 
			&& _IsBalance(root->_right);
	}
	int _HeightTree(Node *root)
	{
		if(root == NULL)
			return 0;
		int leftsize=1+_HeightTree(root->_left);
		int rightsize=1+_HeightTree(root->_right);
		return leftsize > rightsize ? leftsize : rightsize;
	}
	void _RotateL(Node *parent)
	{
		Node *subR=parent->_right;
		Node *subRL=subR->_left;
		parent->_right=subRL;
		if(subRL)
			subRL->_parent=parent;
		subR->_left=parent;
		Node *tmp=parent->_parent;
		parent->_parent=subR;
		if(tmp == NULL)         //parent是根结点
		{
			_root=subR;
			subR->_parent=NULL;
		}
		else                   //parent不是根结点
		{
			if(tmp->_left == parent)
				tmp->_left=subR;
			else
				tmp->_right=subR;
			subR->_parent=tmp;
		}
		subR->_bf=parent->_bf=0;     //更新平衡因子
	}
	void _RotateR(Node *parent)
	{
		Node *subL=parent->_left;
		Node *subLR=subL->_right;
		parent->_left=subLR;
		if(subLR)
			subLR->_parent=parent;
		subL->_right=parent;
		Node *tmp=parent->_parent;
		parent->_parent=subL;
		if(tmp == NULL)         //parent是根结点
		{
			_root=subL;
			subL->_parent=NULL;
		}
		else                    //parent不是根结点
		{
			if(tmp->_left == parent)
				tmp->_left=subL;
			else
				tmp->_right=subL;
			subL->_parent=tmp;
		}
		subL->_bf=parent->_bf=0;         //更新平衡因子
	}
	void _RotateLR(Node *parent)
	{
		Node *subL=parent->_left;
		Node *subLR=subL->_right;
		int bf=subLR->_bf;
		_RotateL(parent->_left);
		_RotateR(parent);
		if(bf == 0)
			subLR->_bf=subL->_bf=parent->_bf=0;
		else if(bf == 1)
		{
			subL->_bf=-1;
			subLR->_bf=1;
			parent->_bf=0;
		}
		else         //-1
		{
			subL->_bf=0;
			subLR->_bf=-1;
			parent->_bf=1;
		}
	}
	void _RotateRL(Node *parent)
	{
		Node *subR=parent->_right;
		Node *subRL=subR->_left;
		int bf=subRL->_bf;
		_RotateR(parent->_right);
		_RotateL(parent);
		if(bf == 0)
			parent->_bf=subR->_bf=subRL->_bf=0;
		else if(bf == 1)
		{
			subR->_bf=0;
			subRL->_bf=1;
			parent->_bf=-1;
		}
		else       //-1
		{
			subR->_bf=1;
			subRL->_bf=-1;
			parent->_bf=0;
		}
	}
	void _Destroy(Node *&root)
	{
		if(root == NULL)
			return ;
		Node *cur=root;
		if(cur)
		{
			_Destroy(root->_left);
			_Destroy(root->_right);
			delete cur;
			cur=NULL;
		}
	}
protected:
	Node *_root;
};

void testAVLTree()
{
	int array1[]={16, 3, 7, 11, 9, 26, 18, 14, 15};
	size_t size=sizeof(array1)/sizeof(array1[0]);
	AVLTree<int,int> tree;
	for (size_t i=0;i<size;++i)
	{
		tree.Insert(array1[i],i);
	}
	tree.InOrder();
	cout<<"tree IsBalance?"<<tree.IsBalance()<<endl;   //1

	int array2[]={4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
	size_t len=sizeof(array2)/sizeof(array2[0]);
	AVLTree<int,int> tree2;
	for(size_t i=0;i<len;++i)
	{
		tree2.Insert(array2[i],i);
	}
	tree2.InOrder();
	cout<<"tree2 IsBalance?"<<tree2.IsBalance()<<endl;  //1
}


 

 

       

    

 

      

作者:qq_34328833 发表于2016/10/25 16:21:53 原文链接
阅读:95 评论:0 查看评论

Swift自定义UITabBar

$
0
0

前言

很多时候,系统原生的 UITabBar 并不能满足我们的需求,譬如我们想要给图标做动态的改变,或者比较炫一点的展示,原生的处理起来都很麻烦。所以很多时候都需要自定义一个 UITabBar,里面的图标、颜色、背景等等都可以根据需求去改变。

效果展示:

自定义UITabBar

从零开始

先说一下思路

页面继承自 UITabBarController ,然后自定义一个 UIView ,添加到 TabBar 上。取消原本的控制按钮。创建自定义按钮,即重写 UIButtonimageView 、和 titleLabelframe ,完成图片、文字的重新布局。最后实现不同按钮的协议方法。

效果图中,只有两边的两个页面在 UITabBarController 的管理下,中间三个都是通过自定义按钮实现的模态页面,即 present 过去的。多用于拍摄图片、录制视频、发表动态等功能。

Demo文件

代码实现:

  1. 首先不妨先建立三个基础文件,然后在丰富代码。其中, IWCustomButton 继承自 UIButtonIWCustomTabBarView 继承自 UIViewIWCustomTabBarController 继承自 UITabBarController

  2. 修改 AppDelegate 文件中 didFinishLaunchingWithOptions 方法,保证启动时没有异常:

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
        //  创建Window
        window = UIWindow(frame: UIScreen.main.bounds)
        //  初始化一个tabbar
        let customTabBar = IWCustomTabBarController()
        //  设置根控制器
        window?.rootViewController = customTabBar
    
        window?.makeKeyAndVisible()
    
        return true
    }
  3. 首先在 IWCustomTabBarController 文件中添加代码:

    //  IWCustomTabBarController.swift
    import UIKit
    class IWCustomTabBarController: UITabBarController {
    
    //  MARK: - Properties
    //  图片
    fileprivate let tabBarImageNames = ["tb_home","tb_person"]
    fileprivate let tabBarTitles = ["首页","我的"]
    
    //  MARK: - LifeCycle
    override func viewDidLoad() {
        super.viewDidLoad()
    
        //  自定义 TabBar 外观
        createCustomTabBar(addHeight: 0)
    
        //  创建子控制器
        addDefaultChildViewControllers()
    
        //  设置每一个子页面的按钮展示
        setChildViewControllerItem()
    }
    
    //  MARK: - Private Methods
    
    /// 添加默认的页面
    fileprivate func addDefaultChildViewControllers() {
        let vc1 = UIViewController()
        vc1.view.backgroundColor = UIColor.white
    
        let vc2 = UIViewController()
        vc2.view.backgroundColor = UIColor.lightGray
    
        viewControllers = [vc1, vc2]
    }
    
    /// 设置外观
    ///
    /// - parameter addHeight: 增加高度,0 为默认
    fileprivate let customTabBarView = IWCustomTabBarView()
    fileprivate func createCustomTabBar(addHeight: CGFloat) {
    
        //  改变tabbar 大小
        var oriTabBarFrame = tabBar.frame
        oriTabBarFrame.origin.y -= addHeight
        oriTabBarFrame.size.height += addHeight
        tabBar.frame = oriTabBarFrame
    
        customTabBarView.frame = tabBar.bounds
        customTabBarView.frame.origin.y -= addHeight
        customTabBarView.backgroundColor = UIColor.groupTableViewBackground
        customTabBarView.frame.size.height = tabBar.frame.size.height + addHeight
        customTabBarView.isUserInteractionEnabled = true
        tabBar.addSubview(customTabBarView)
    }
    
    /// 设置子页面的item项
    fileprivate func setChildViewControllerItem() {
        guard let containViewControllers = viewControllers else {
            print("⚠️  设置子页面 item 项失败  ⚠️")
            return
        }
    
        if containViewControllers.count != tabBarImageNames.count {
            fatalError("子页面数量和设置的tabBarItem数量不一致,请检查!!")
        }
    
        //  遍历子页面
        for (index, singleVC) in containViewControllers.enumerated() {
            singleVC.tabBarItem.image = UIImage(named: tabBarImageNames[index])
            singleVC.tabBarItem.selectedImage = UIImage(named: tabBarImageNames[index] + "_selected")
            singleVC.tabBarItem.title = tabBarTitles[index]
        }
    }
    }

    上面就是一个基本的纯代码创建的 UITabBarController 的实际效果了,运行后,查看效果:

    基本的运行效果

    现在明显的问题就是我们的原始图片是红色的,为什么现在都是灰、蓝色,因为 UITabBar 使用图片时渲染了,如果我们需要使用原始图片,则对 UIImage 方法扩展:

    extension UIImage {
    var originalImage: UIImage {
        return self.withRenderingMode(.alwaysOriginal)
    }
    }

    然后修改遍历子页面的代码:

    //  遍历子页面
        for (index, singleVC) in containViewControllers.enumerated() {
            singleVC.tabBarItem.image = UIImage(named: tabBarImageNames[index]).originalImage
            singleVC.tabBarItem.selectedImage = UIImage(named: tabBarImageNames[index] + "_selected").originalImage
            singleVC.tabBarItem.title = tabBarTitles[index]
        }

    运行后便可查看到原始的图片效果。

  4. 编写文件 IWCustomTabBarView :

    import UIKit
    //  自定义按钮功能
    enum IWCustomButtonOperation {
    case customRecordingVideo       //  录像
    case customTakePhoto            //  拍照
    case customMakeTape             //  录音
    }
    /// 页面按钮点击协议
    protocol IWCustomTabBarViewDelegate {
    
    /// 点击tabBar 管理下的按钮
    ///
    /// - parameter customTabBarView:     当前视图
    /// - parameter didSelectedButtonTag: 点击tag,这个是区分标识
    func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedButtonTag: Int)
    
    /// 点击自定义的纯按钮
    ///
    /// - parameter customTabBarView:      当前视图
    /// - parameter didSelectedOpertaionButtonType: 按钮类型,拍照、摄像、录音
    func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedOpertaionButtonType: IWCustomButtonOperation)
    }
    class IWCustomTabBarView: UIView {
    //  MARK: - Properties
    //  协议
    var delegate: IWCustomTabBarViewDelegate?
    //  操作按钮数组
    fileprivate var operationButtons = [IWCustomButton]()
    //  tabbar 管理的按钮数组
    fileprivate var customButtons = [IWCustomButton]()
    //  自定义按钮图片、标题
    fileprivate let operationImageNames = ["tb_normol","tb_normol","tb_normol"]
    fileprivate let operationTitls = ["摄像", "拍照", "录音"]
    
    //  MARK: - Init
    override init(frame: CGRect) {
        super.init(frame: frame)
    
        //  添加自定义按钮
        addOperationButtons()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        print("IWCustomTabBarView 页面 init(coder:) 方法没有实现")
    }
    
    /// 布局控件
    override func layoutSubviews() {
        super.layoutSubviews()
    
        //  设置位置
        let btnY: CGFloat = 0
        let btnWidth = bounds.width / CGFloat(subviews.count)
        let btnHeight = bounds.height
    
        //  这里其实就两个
        for (index, customButton) in customButtons.enumerated() {
    
            switch index {
            case 0:
                customButton.frame = CGRect(x: 0, y: 0, width: btnWidth, height: btnHeight)
                customButton.tag = index
            case 1:
                customButton.frame = CGRect(x: btnWidth * 4, y: 0, width: btnWidth, height: btnHeight)
                customButton.tag = index
            default:
                break
            }
        }
    
        //  这里有三个
        for (index, operBtn) in operationButtons.enumerated() {
            let btnX = (CGFloat(index) + 1) * btnWidth
            operBtn.frame = CGRect(x: btnX, y: btnY, width: btnWidth, height: btnHeight)
        }
    }
    
    //  MARK: - Public Methods
    /// 根据原始的 TabBarItem 设置自定义Button
    ///
    /// - parameter originalTabBarItem: 原始数据
    func addCustomTabBarButton(by originalTabBarItem: UITabBarItem) {
        //  添加初始按钮
        let customButton = IWCustomButton()
        customButtons.append(customButton)
        addSubview(customButton)
    
        //  添加点击事件
        customButton.addTarget(self, action: #selector(customButtonClickedAction(customBtn:)), for: .touchUpInside)
    
        //  默认展示第一个页面
        if customButtons.count == 1 {
            customButtonClickedAction(customBtn: customButton)
        }
    }
    
    //  MARK: - Private Methods
    
    /// 添加操作按钮
    fileprivate func addOperationButtons() {
        for index in 0 ..< 3 {
            let operationBtn = IWCustomButton()
            operationButtons.append(operationBtn)
            operationBtn.setImage(UIImage(named: operationImageNames[index]), for: .normal)
            operationBtn.setImage(UIImage(named: operationImageNames[index]), for: .highlighted)
            operationBtn.setTitle(operationTitls[index], for: .normal)
            operationBtn.tag = 100 + index
            operationBtn.addTarget(self, action: #selector(operationButtonClickedAction(operBtn:)), for: .touchUpInside)
            addSubview(operationBtn)
        }
    }
    
    ///  操作按钮点击事件
    @objc fileprivate func operationButtonClickedAction(operBtn: IWCustomButton) {
        switch operBtn.tag {
        case 100:
            delegate?.iwCustomTabBarView(customTabBarView: self, .customRecordingVideo)
        case 101:
            delegate?.iwCustomTabBarView(customTabBarView: self, .customTakePhoto)
        case 102:
            delegate?.iwCustomTabBarView(customTabBarView: self, .customMakeTape)
        default:
            break
        }
    }
    
    //  保证按钮的状态正常显示
    fileprivate var lastCustomButton = IWCustomButton()
    
    ///  tabbar 管理下按钮的点击事件
    @objc fileprivate func customButtonClickedAction(customBtn: IWCustomButton) {
        delegate?.iwCustomTabBarView(customTabBarView: self, customBtn.tag)
    
        lastCustomButton.isSelected = false
        customBtn.isSelected = true
        lastCustomButton = customBtn
    }
    }

    IWCustomTabBarController 文件的 setChildViewControllerItem() 方法中,修改遍历子页面的代码,获取当前的 UITabBarItem

    // 遍历子页面 for (index, singleVC) in containViewControllers.enumerated() { 
    singleVC.tabBarItem.image = UIImage(named: tabBarImageNames[index]) 
    singleVC.tabBarItem.selectedImage = UIImage(named: tabBarImageNames[index] + "_selected") 
    singleVC.tabBarItem.title = tabBarTitles[index]
    //  添加相对应的自定义按钮
            customTabBarView.addCustomTabBarButton(by: singleVC.tabBarItem)
    }

    运行后,看到效果好像乱乱的,暂时不用在意,在后面的代码中会慢慢整理出理想的效果。

    乱糟糟的

    简单分析上面的代码:这里我在中间加入了三个自定义的按钮。这样的话,最下面应该是有5个按钮的。当然也可以加入一个或者两个等,只需要修改上面对应的数值就可以了。这里面比较主要的就是自定义协议 IWCustomTabBarViewDelegate 和布局方法 layoutSubviews,布局方法里如果能理解两个 for 循环和对应数组中的数据来源、作用,那么问题就简单很多了。

    这里要说一个属性 lastCustomButton ,这个属性会让我们避免不必要的遍历按钮,有些时候多个按钮只能有一个被选中时,有种常见的方法就是遍历按钮数组,令其中一个 isSelected = true ,其他按钮的 isSelected = false ,而这个属性就能取代遍历。

    其实存在的问题也很明显,就是这么写的话很难去扩展,譬如如果上面的代码已经完成了,但是临时需要减少一个自定义按钮,那么就需要改动多个地方。这里只是提供一种自定义的思路,只是说还有很多可以优化的地方。

  5. 关于自定义的 UIButotn ,是个很有意思的地方。因为视觉上的改变都是在这里发生,先使用默认的设置:

    import UIKit
    class IWCustomButton: UIButton {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        titleLabel?.textAlignment = .center
        setTitleColor(UIColor.gray, for: .normal)
        setTitleColor(UIColor.red, for: .selected)
        titleLabel?.font = UIFont.italicSystemFont(ofSize: 12)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        print("⚠️⚠️⚠️ init(coder:) 方法没有实现")
    }
    
    /// 根据传入的 UITabBarItem 设置数据显示
    ///
    /// - parameter tabBarItem: 数据来源
    func setTabBarItem(tabBarItem: UITabBarItem) {
        setTitle(tabBarItem.title, for: .normal)
        setImage(tabBarItem.image, for: .normal)
        setImage(tabBarItem.selectedImage, for: .highlighted)
        setImage(tabBarItem.selectedImage, for: .selected)
    }
    }

    修改 IWCustomTabBarView 文件的 addCustomTabBarButton(by: ) 方法:

    //  MARK: - Public Methods
    /// 根据原始的 TabBarItem 设置自定义Button
    ///
    /// - parameter originalTabBarItem: 原始数据
    func addCustomTabBarButton(by originalTabBarItem: UITabBarItem) {
        //  添加初始按钮
        let customButton = IWCustomButton()
        customButtons.append(customButton)
        addSubview(customButton)
    
        //  传值
        customButton.setTabBarItem(tabBarItem: originalTabBarItem)
    
        //  添加点击事件
        customButton.addTarget(self, action: #selector(customButtonClickedAction(customBtn:)), for: .touchUpInside)
    
        //  默认展示第一个页面
        if customButtons.count == 1 {
            customButtonClickedAction(customBtn: customButton)
        }
    }

    看看运行结果:

    自定义按钮后

    首先,我们发现了乱的原因,就是自定义的按钮和原本的 UITabBarItem 的显示起了冲突。那么先修改这个问题:在 IWCustomTabBarController 方法中页面即将出现时添加方法:

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    
        //  移除原生的 TabBarItem ,否则会出现覆盖现象
        tabBar.subviews.forEach { (subView) in
            if subView is UIControl {
                subView.removeFromSuperview()
            }
        }
    }

    那么上面重复显示的原生项此时就移除了。下一个问题:发现自定义按钮图像的大小不一致。其实中间图片本身的大小就是比两边的大的。以 2x.png 为例,中间的图标是 70x70,而两边的是 48x48。如果在没有文字显示的情况下,在按钮的初始化方法中添加 imageView?.contentMode = .center ,图片居中展示,自定义按钮到这个地方就可以结束了(可以尝试不要 title ,查看运行效果)。甚至可以在自定义按钮的初始化方法里使用仿射变换来放大、缩小图片。

    这里为了控制图片、文字的位置,重写 UIButton 的两个方法:

    /// 重写 UIButton 的 UIImageView 位置
    ///
    /// - parameter contentRect: 始位置
    ///
    /// - returns: 修改后
    override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
        let imageWidth = contentRect.size.height * 4 / 9
        let imageHeight = contentRect.size.height
        return CGRect(x: bounds.width / 2 - imageWidth / 2, y: imageHeight / 9, width: imageWidth, height: imageWidth)
    }
    
    /// 重写 UIButton 的 TitleLabel 的位置
    ///
    /// - parameter contentRect: 原始位置
    ///
    /// - returns: 修改后
    override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
        let titleWidth = contentRect.size.width
        let titleHeight = contentRect.size.height / 3
        return CGRect(x: bounds.width / 2 - titleWidth / 2, y: bounds.height - titleHeight, width: titleWidth, height: titleHeight)
    }  

    对上面代码做简单地说明,首先说方法中 contentRect 这个变量,它的 size 是这个 UIButton 的大小,而不是单独的 UIImageView ,或者 titleLabel 的大小。上面的一些具体数值,譬如 4 / 9 等这种奇葩的比例数值,仅仅是我根据自己的审美观随便写入的一些数值,至于到具体的开发中,可以固定大小,也可以使用更加细致的比例,因为 tabBar 默认的高度是 49 ,那么很多数据就可以使用了。现在看看效果:

    修改自定义按钮后

  6. IWCustomTabBarController 文件中实现 IWCustomTabBarView 文件中的协议方法,首先添加协议,然后实现方法,别忘了令 customTabBarView.delegate = self

    //  MARK: - IWCustomTabBarViewDelegate
    ///  点击 tabbar 管理下的按钮
    func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedButtonTag: Int) {
        selectedIndex = didSelectedButtonTag
    }
    
    ///  点击自定义添加的的按钮
    func iwCustomTabBarView(customTabBarView: IWCustomTabBarView, _ didSelectedOpertaionButtonType: IWCustomButtonOperation) {
        switch didSelectedOpertaionButtonType {
        case .customRecordingVideo:
            print("摄像")
            let vc = UIViewController()
            vc.view.backgroundColor = UIColor.orange
            addBackButton(on: vc.view)
            present(vc, animated: true, completion: nil)
        case .customTakePhoto:
            print("拍照")
            let vc = UIViewController()
            vc.view.backgroundColor = UIColor.green
            addBackButton(on: vc.view)
            present(vc, animated: true, completion: nil)
        case .customMakeTape:
            print("录音")
            let vc = UIViewController()
            vc.view.backgroundColor = UIColor.cyan
            addBackButton(on: vc.view)
            present(vc, animated: true, completion: nil)
        }
    }
    
    fileprivate func addBackButton(on superView: UIView) {
        let btn = UIButton()
        btn.frame = CGRect(x: 100, y: 100, width: 100, height: 50)
        btn.backgroundColor = UIColor.blue
        btn.setTitle("返回", for: .normal)
        btn.setTitleColor(UIColor.white, for: .normal)
        btn.addTarget(self, action: #selector(dismissAction), for: .touchUpInside)
        superView.addSubview(btn)
    }
    @objc func dismissAction() {
        dismiss(animated: true, completion: nil)
    }

    上面的代码,只单独说一点,就是协议方法 iwCustomTabBarView(customTabBarView : , _ didSelectedButtonTag) 中, selectedIndex 这个属性并非我们自己定义的变量,而是系统设置的,所以这时候 didSelectedButtonTag 所代表值就显得很有意思了,它正是我们在 UITabBar 管理下 ViewController 是下标值。看看这时候的效果吧:

    完成后

  7. 最后再说一点,有时候我们需要给自定义的 IWCustomTabBarView 添加背景图片,那么这时候会出现一个问题,就是原本的 TabBar 的浅灰色背景始终会有一条线,此时在 IWCustomTabBarController 文件的 viewDidLoad() 方法中添加下面的代码即可。

        //  去除 TabBar 阴影
        let originalTabBar = UITabBar.appearance()
        originalTabBar.shadowImage = UIImage()
        originalTabBar.backgroundImage = UIImage()

完了

作者:xxh0307 发表于2016/10/26 16:15:59 原文链接
阅读:20 评论:0 查看评论

最全面的Xcode 8 带来的新特性。

$
0
0

一、首先看欢迎界面,这个是我们开发者经常接触的


欢迎界面扁平化网格去掉,是不是好看很多了

二、创建工程更加人性化


常用的放在最前面
  • 1.内置表情包(Sticker Packs)

    • 可以通过在Xcode中新建Sticker Pack Application来创建。这种方式可以简单地通过添加图片来在iMessage中添加表情包。添加的贴纸需要满足一下条件
      图片类型必须是 png、apng、gif或者jpeg
      文件大小必须 小于500K
      图片大小必须在 100 100 到 206 206 之间

    • 需要注意的是:必须要永远提供 @3x 大小的图片(即 300 300 到 618 618)。系统可以根据当前设备通过 runtime 自动调整图片来呈现 @2x 和 @1x
      系统能够自适应的展示贴纸,所以为了更好的展示贴纸,最好提供的贴纸是以下三种大小的类型
      小型 100100
      中型 136
      136
      大型 206*206

  • 2.iMessage应用
    iMessage app使用完整的框架和Message app进行交互。使用iMessage app能够
    在消息应用内呈现一个自定义的用户交互界面。 使用MSMessagesAppViewController
    创建一个自定义或者动态的表情包浏览器。使用 MSStickerBrowserViewController
    添加文本、表情、或者媒体文件到消息应用的文本输入框。使用 MSConversation
    创建带有特定的应用数据交互的消息。使用 MSMessage
    更新可以相互影响的消息(例如,创建游戏或者可以合作的应用)。

三、类名提示

从Xcode8beta1就支持类名提示了,所以Xcode8正式版也支持类名提示,你们在也不用忧伤了

四、Swift3.0 and Swift2.3 随意切换

Xcode8支持3.0语法和2.3语法随意切换,默认创建出来是3.0语法, 如果想用2.3语法是可以切换的

值得一提的是Xcode8.0并没有做语法捆绑,在以往的版本中都是捆绑最新的语法
PS:苹果越来越开放


语法切换

默认

3.0

2.3
  • 语法转换
    当然Swift只能向上兼容,不能向下兼容,如果你的语法是2.3的可以转换为3.0的,但是转换不一定成功


    语法转换

五、控制台输出

控制台是大家最长用的从Xcode8beta1 OC控制台不NSLog不输出,到以后控制台都输出一大堆系统信息等等,大家很烦,其实军哥也很烦,例如:


系统信息


看到这里想必大家都想禁止,但是如何禁止呢

OS_ACTIVITY_MODE disable
PS: 知道你不明白这个是什么意思,接下来上图


点击工程

修改对应的属性,禁止系统的输出日志

禁止后的效果,我们自己输出的东西依然可以正常输出

PS:每个新工程都需要配置,也是略微的忧伤,不过相比之下控制台输出没办法解决好多了

六、字体改变

Xcode8中用的字体是San Francisco Mono字体


字体改变,对于那些强迫症的程序员来说简直就是福音

七、代码高亮当前行,看上图

PS:再也不用代码高亮行的插件了

八、代码调试支持Runtime

Xcode8新增Runtime调试,界面可以展示运行时的问题,改善的界面调试对于调试不清晰或者不满意的布局变的更简单


运行时调试

九、代码签名

Xcode8之后必须用代码签名,包括第三方SDK也需要代码签名,好的一点是苹果提供自动代码签名


代码签名,自动

十、文档

文档焕然一新,分为Swift 和OC文档,具体看图


文档

十一、UIColor新增了2个API,我们之前都是RGB设置颜色,现在我们可以使用sRGB来设置颜色,这样的好处是性能更加好,色彩也更丰富。

+(UIColor *)colorWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0);

-(UIColor *)initWithDisplayP3Red:(CGFloat)displayP3Red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_AVAILABLE_IOS(10_0);

十二、关于UIStatusBar方法过期,新增其他API修改UIStatusBar

-(UIStatusBarStyle)preferredStatusBarStyle{

return UIStatusBarStyleDefault;

}

十三、推送

       在更新之后,推送这块更改了是比较大的。首先所有相关的通知都被放到UserNotifications框架里面,并且增加了撤销,更新,中途修改通知内容等,而且通知不仅仅只是简单的文本通知,还可以加入视频,图片等进行通知。推送流程:     

申请和注册 ->  创建和发送推送 -> 展示和处理推送

申请权限已经不用区分本地和远程了,iOS统一了API

[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error){        if(granted){       

} else {   

    } 

  }];

发送通知也有了一个比较统一的流程

UNMutableNotificationContent  *content = [[UNMutableNotificationContent  alloc]init];   

content.body=@"标题";   

content.subtitle=@"推送";

NSString*identifier =@"notification";   

UNTimeIntervalNotificationTrigger *tigger =[UNTimeIntervalNotificationTrigger  triggerWithTimeInterval:3repeats:NO];    UNNotificationRequest *request = [UNNotificationRequest  requestWithIdentifier:identifier content:content  trigger:tigger];    [[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError* _Nullable error) {

if(error) {

NSLog(@"%@",error);       

}    }];

UNMutableNotificationContent为推送内容的主体类,里面的属性可以对推送内容进行编辑。

identifier是对此推送的唯一标识

UNTimeIntervalNotificationTrigger是针对本地的一个触发器,可以延迟进行推送。UNCalendarNotificationTrigger,在某月某日某时定时触发推送。UNLocationNotificationTrigger,当用户离开或者进入某地区触发推送。


十四、ATS的问题

         目前来说对我们没影响但是在2017年1月1日起,苹果不允许我们使用将NSAllowsArbitraryLoads设置为YES的方式跳过ATS。否则提交APP可能会被拒绝。所以这里的解决方法就是。








作者:qq_31518167 发表于2016/10/26 16:16:54 原文链接
阅读:33 评论:0 查看评论

Android屏幕适配

$
0
0

(一)背景知识

为什么需要屏幕适配

    Android是一个开放的系统,全球各种用户、手机企业、OEM厂商都可以对Android系统进行定制,这就导致了Android系统的碎片化问题。其中对于开发者来讲工作中最常碰到的就是屏幕碎片化,那么如何解决屏幕碎片化问题,实现最优的屏幕适配,是每个Android开发者所要面临的问题,这里我整合CSDN博主赵凯强的关于Android屏幕适配的博文的知识,写成博客分享给大家。

Android中的显示单位

这里写图片描述

常见的定义

屏幕尺寸

    屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米
    比如常见的屏幕尺寸有2.4、2.8、3.5、3.7、4.2、5.0、5.5、6.0等

屏幕分辨率

屏幕分辨率是指在横纵向上的像素点数,单位是px,1px=1个像素点。一般以纵向像素*横向像素,如1960*1080。

屏幕像素密度

    屏幕像素密度是指每英寸上的像素点数,单位是dpi,即“dot per inch”的缩写。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

dp、dip、dpi、sp、px

    px:我们应该是比较熟悉的,前面的分辨率就是用的像素为单位,大多数情况下,比如UI设计、Android原生API都会以px作为统一的计量单位,像是获取屏幕宽高等。
    dip和dp:是一个意思,都是Density Independent Pixels的缩写,即密度无关像素,上面我们说过,dpi是屏幕像素密度,假如一英寸里面有160个像素,这个屏幕的像素密度就是160dpi,那么在这种情况下,dp和px如何换算呢?在Android中,规定以160dpi为基准,1dip=1px,如果密度是320dpi,则1dip=2px,以此类推。
    sp:即scale-independent pixels,与dp类似,但是可以根据文字大小首选项进行放缩,是设置字体大小的御用单位。

dip与px之间的换算公式

a. 2N + 2N/2 = PX
b.(2N-1)+ 2N/2 = PX

    注:偶数值dip 的1.5倍等于相对应的px值,偶数值的间距与奇数元素设置居中对齐的时候会有1px的误差。
    下面是一个常见的dp和px的转换工具类

public class DensityUtil {  

    /** 
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素) 
     */  
    public static int dip2px(Context context, float dpValue) {  
        final float scale = context.getResources().getDisplayMetrics().density;  
        return (int) (dpValue * scale + 0.5f);  
    }  

    /** 
     * 根据手机的分辨率从 px(像素) 的单位 转成为 dp 
     */  
    public static int px2dip(Context context, float pxValue) {  
        final float scale = context.getResources().getDisplayMetrics().density;  
        return (int) (pxValue / scale + 0.5f);  
    }  
}  

mdpi、hdpi、xdpi、xxdpi、xxxdpi

    mdpi、hdpi、xdpi、xxdpi、xxxdpi用来修饰Android中的drawable文件夹及values文件夹,用来区分不同像素密度下的图片和dimen值。
    根据谷歌官方文档,上述五个具体解释如下:

这里写图片描述

    在开发的过程中,我们可以和美工配合,设计五种主流的像素密度图标,并图片放在合适的文件夹里面。

这里写图片描述

下图为图标的各个屏幕密度的对应尺寸

这里写图片描述

尺寸资源XML文件

    通常我们在企业级项目中常常会使用尺寸资源文件,目的是为了做到一处更改全部更改。

这里写图片描述

实例:

    该示例在布局文件中添加一个TextView和一个Button,分别使用尺寸资源来定义它们的宽和高。

    1.在工程的res\values\目录下添加一个dimens.xml尺寸资源文件,并且添加4个尺寸资源(如下面代码所示),可视化的添加方法跟添加字符串类似,不过其Value值要是“数字+单位”(我自己的体会)。当然你也可以直接输代码。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="test_width">150px</dimen>
    <dimen name="test_height">100px</dimen>
    <dimen name="btn_width">30mm</dimen>
    <dimen name="btn_height">10mm</dimen>
</resources>

    2.在工程的res\layout\目录下添加一个test_dimens.xml布局资源文件,在布局文件中添加一个TextView和一个Button,TextView的宽高尺寸引用尺寸资源来设置,Button的宽和高在代码中设置:

<TextView
     android:text="@string/test_demen"
     android:id="@+id/myTextView01"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:textColor="@color/blue_text"
     android:width="@dimen/test_width"
     android:height="@dimen/test_height"
     android:background="@color/red_bg"
 />
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/test_demen1" />

3.Button宽和高设置代码

  Button myButton = (Button)findViewById(R.id.button1);
        Resources r =  getResources();
        float btn_h = r.getDimension(R.dimen.btn_height);
        float btn_w = r.getDimension(R.dimen.btn_width);
        myButton.setHeight((int)btn_h);
        myButton.setWidth((int)btn_w);

可以看出,尺寸资源和字符串资源的使用很相似。

(二)屏幕适配方案

充分利用”wrap_content” 、”match_parent”以及“weight”

    通常我们会在布局视图中使用”wrap_content”和”match_parent”来确定它的宽和高。如果你使用了”wrap_content”,相应视图的宽和高就会被设定成刚好能够包含视图中内容的最小值。而如果你使用了”match_parent”,就会让视图的宽和高延伸至充满整个父布局。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  
    <LinearLayout android:layout_width="match_parent"   
                  android:id="@+id/linearLayout1"    
                  android:gravity="center"  
                  android:layout_height="50dp">  
        <ImageView android:id="@+id/imageView1"   
                   android:layout_height="wrap_content"  
                   android:layout_width="wrap_content"  
                   android:src="@drawable/logo"  
                   android:paddingRight="30dp"  
                   android:layout_gravity="left"  
                   android:layout_weight="0" />  
        <View android:layout_height="wrap_content"   
              android:id="@+id/view1"  
              android:layout_width="wrap_content"  
              android:layout_weight="1" />  
        <Button android:id="@+id/categorybutton"  
                android:background="@drawable/button_bg"  
                android:layout_height="match_parent"  
                android:layout_weight="0"  
                android:layout_width="120dp"  
                style="@style/CategoryButtonStyle"/>  
    </LinearLayout>  

    <fragment android:id="@+id/headlines"   
              android:layout_height="fill_parent"  
              android:name="com.example.android.newsreader.HeadlinesFragment"  
              android:layout_width="match_parent" />  
</LinearLayout>  

这里写图片描述

android:layout_weight属性

在布局中一旦View设置了weight属性,那么该 View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比。

    假设设屏幕宽度为100,在两个view的宽度都为match_parent的情况下,原有宽度为100,两个的View的宽度都为100,那么剩余宽度为100-(100+100) = -100, 左边的View占比三分之一,所以总宽度是100+(-100)*1/3 = (2/3)100.事实上默认的View的weight这个值为0,一旦设置了这个值,那么所在view在绘制的时候onMeasure会执行两次。通常我们在使用weight属性时,将width设为0dip即可,效果跟设成wrap_content是一样的。

1.当两个组件同时设置android:layout_width=”match_parent”

<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:layout_marginTop="20dp"
    android:orientation="horizontal">

    <Button
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_weight="1"
        android:text="@string/btn1" 
        android:background="@android:color/holo_blue_bright"/>

        <Button
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_weight="2"
        android:text="@string/btn2" 
        android:background="@android:color/holo_green_light"/>

</LinearLayout>

这里写图片描述

从上图我们可以看出按钮1的权重为1,但是却占据了2/3的宽度

2.当两个组件同时设置android:layout_width=”wrap_content”

<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:layout_marginTop="20dp"
    android:orientation="horizontal">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:layout_weight="1"
        android:text="@string/btn1" 
        android:background="@android:color/holo_blue_bright"/>

        <Button
        android:layout_width="wrap_content"
        android:layout_height="50dp"
        android:layout_weight="2"
        android:text="@string/btn2" 
        android:background="@android:color/holo_green_light"/>

</LinearLayout>

这里写图片描述

从上图我们可以看出按钮1的权重为1,占据了1/3的宽度

多使用相对布局RelativeLayout,少使用绝对布局

    如果你需要让子视图能够有更多的排列方式,而不是简单地排成一行或一列,使用RelativeLayout将会是更好的解决方案。RelativeLayout允许布局的子控件之间使用相对定位的方式控制控件的位置,比如你可以让一个子视图居屏幕左侧对齐,让另一个子视图居屏幕右侧对齐。

<RelativeLayout 
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  
        …… 
</RelativeLayout>  

使用尺寸Size限定符

    我们的应用程序实现可自适应的布局外,还应该提供一些方案根据屏幕的配置来加载不同的布局,可以通过配置限定符(configuration qualifiers)来实现。配置限定符允许程序在运行时根据当前设备的配置自动加载合适的资源(比如为不同尺寸屏幕设计不同的布局)。
    现在有很多的应用程序为了支持大屏设备,都会实现“two pane”模式(程序会在左侧的面板上展示一个包含子项的List,在右侧面板上展示内容)。平板和电视设备的屏幕都很大,足够同时显示两个面板,而手机屏幕一次只能显示一个面板,两个面板需要分开显示。所以,为了实现这种布局,你可能需要以下文件:
res/layout/main.xml,single-pane(默认)布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  
    <fragment android:id="@+id/headlines"  
              android:layout_height="fill_parent"  
              android:layout_width="match_parent" />  
</LinearLayout>  

res/layout-large/main.xml,two-pane布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:orientation="horizontal">  
    <fragment android:id="@+id/headlines"  
              android:layout_height="fill_parent"  
              android:layout_width="400dp"  
              android:layout_marginRight="10dp"/>  
    <fragment android:id="@+id/article"  
              android:layout_height="fill_parent"  
              android:layout_width="fill_parent" />  
</LinearLayout>  

    第二个布局的目录名中包含了large限定符,那些被定义为大屏的设备(比如7寸以上的平板)会自动加载此布局,而小屏设备会加载另一个默认的布局。

最小宽度Smallest-width限定符

    Smallest-width限定符允许你设定一个具体的最小值(以dp为单位)来指定屏幕。例如,7寸的平板最小宽度是600dp,所以如果你想让你的UI在这种屏幕上显示two pane,在更小的屏幕上显示single pane,你可以使用sw600dp来表示你想在600dp以上宽度的屏幕上使用two pane模式。
res/layout/main.xml,single-pane(默认)布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent">  

    <fragment android:id="@+id/headlines"  
              android:layout_height="fill_parent"   
              android:layout_width="match_parent" />  
</LinearLayout>  

res/layout-sw600dp/main.xml,two-pane布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="fill_parent"  
    android:layout_height="fill_parent"  
    android:orientation="horizontal">  
    <fragment android:id="@+id/headlines"  
              android:layout_height="fill_parent"  
              android:layout_width="400dp"  
              android:layout_marginRight="10dp"/>  
    <fragment android:id="@+id/article"  
              android:layout_height="fill_parent"  
              android:layout_width="fill_parent" />  
</LinearLayout>  

    这意味着,那些最小屏幕宽度大于600dp的设备会选择layout-sw600dp/main.xml(two-pane)布局,而更小屏幕的设备将会选择layout/main.xml(single-pane)布局。

使用布局别名

    Smallest-width限定符仅在Android 3.2及之后的系统中有效。因而,你也需要同时使用Size限定符(small, normal, large和xlarge)来兼容更早的系统。例如,你想手机上显示single-pane界面,而在7寸平板和更大屏的设备上显示multi-pane界面,你需要提供以下文件:

res/layout/main.xml: single-pane布局
res/layout-large: multi-pane布局
res/layout-sw600dp: multi-pane布局

    最后的两个文件是完全相同的,为了要解决这种重复,你需要使用别名技巧。例如,你可以定义以下布局:

res/layout/main.xml, single-pane布局
res/layout/main_twopanes.xml, two-pane布局

加入以下两个文件:
res/values-large/layout.xml:

<resources>  
    <item name="main" type="layout">@layout/main_twopanes</item>  
</resources>  

res/values-sw600dp/layout.xml:

<resources>  
    <item name="main" type="layout">@layout/main_twopanes</item>  
</resources>  

    最后两个文件有着相同的内容,但是它们并没有真正去定义布局,它们仅仅只是给main定义了一个别名main_twopanes。这样两个layout.xml都只是引用了@layout/main_twopanes,就避免了重复定义布局文件的情况。

使用.9图

    “点九”是andriod平台的应用软件开发里的一种特殊的图片形式,文件扩展名为:.9.png
    智能手机中有自动横屏的功能,同一幅界面会在随着手机(或平板电脑)中的方向传感器的参数不同而改变显示的方向,在界面改变方向后,界面上的图形会因为长宽的变化而产生拉伸,造成图形的失真变形。
    我们都知道android平台有多种不同的分辨率,很多控件的切图文件在被放大拉伸后,边角会模糊失真。OK,在android平台下使用点九PNG技术,可以将图片横向和纵向同时进行拉伸,以实现在多分辨率下的完美显示效果。

这里写图片描述

    对比很明显,使用点九后,仍能保留图像的渐变质感,和圆角的精细度。
    从中我们也可以理解为什么叫“点九PNG”,其实相当于把一张png图分成了9个部分(九宫格),分别为4个角,4条边,以及一个中间区域,4个角是不做拉升的,所以还能一直保持圆角的清晰状态,而2条水平边和垂直边分别只做水平和垂直拉伸,所以不会出现边会被拉粗的情况,只有中间用黑线指定的区域做拉伸。结果是图片不会走样。

实现百分比布局

    在开发中,组件布局是大家每日开发必须要面对的工作,对于Android来说提供五种常用布局,分别是:

  • LinearLayout(线性布局)
  • TableLayout(表格布局)
  • RelativeLayout(相对布局)
  • AbsoluteLayout(绝对布局)
  • FrameLayout(框架布局)

    但是,开发中如果可以按照百分比的方式进行界面布局,将会对我们的适配工作带来许多便利。前段时间,谷歌正式提供百分比布局支持库(android-support-percent-lib),对于我们开发者来讲只需要导入这个库就可以实现百分比布局。现在我们抛开谷歌库不谈,自己其实也可以实现百分比布局。具体实现大家可以参考我之前的博客Android自实现百分比布局

支持各种屏幕密度

使用非密度制约像素

    由于各种屏幕的像素密度都有所不同,因此相同数量的像素在不同设备上的实际大小也有所差异,这样使用像素定义布局尺寸就会产生问题。因此,请务必使用 dp 或 sp 单位指定尺寸。dp 是一种非密度制约像素,其尺寸与 160 dpi 像素的实际尺寸相同。sp 也是一种基本单位,但它可根据用户的偏好文字大小进行调整(即尺度独立性像素),因此我们应将该测量单位用于定义文字大小。
    例如,请使用 dp(而非 px)指定两个视图间的间距:

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/clickme"
android:layout_marginTop="20dp" />

请务必使用 sp 指定文字大小:

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp" />

    另外,虽然说dp可以去除不同像素密度的问题,使得1dp在不同像素密度上面的显示效果相同,但是还是由于Android屏幕设备的多样性,如果使用dp来作为度量单位,并不是所有的屏幕的宽度都是相同的dp长度,比如说,Nexus S和Nexus One属于hdpi,屏幕宽度是320dp,而Nexus 5属于xxhdpi,屏幕宽度是360dp,Galaxy Nexus属于xhdpi,屏幕宽度是384dp,Nexus 6 属于xxxhdpi,屏幕宽度是410dp。所以说,光Google自己一家的产品就已经有这么多的标准,而且屏幕宽度和像素密度没有任何关联关系,即使我们使用dp,在320dp宽度的设备和410dp的设备上,还是会有90dp的差别。当然,我们尽量使用match_parent和wrap_content,尽可能少的用dp来指定控件的具体长宽,再结合上权重,大部分的情况我们都是可以做到适配的。
    但是除了这个方法,我们还有没有其他的更彻底的解决方案呢?我们换另外一个思路来思考这个问题。因为分辨率不一样,所以不能用px;因为屏幕宽度不一样,所以要小心的用dp,那么我们可不可以用另外一种方法来统一单位,不管分辨率是多大,屏幕宽度用一个固定的值的单位来统计呢?答案是:当然可以。我们假设手机屏幕的宽度都是320某单位,那么我们将一个屏幕宽度的总像素数平均分成320份,每一份对应具体的像素就可以了。我们看下面的代码

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

publicclassMakeXml {

    privatefinalstatic String rootPath = "C:\\Users\\Administrator\\Desktop\\layoutroot\\values-{0}x{1}\\";

    privatefinalstaticfloat dw = 320f;
    privatefinalstaticfloat dh = 480f;

    privatefinalstatic String WTemplate = "<dimen name=\"x{0}\">{1}px</dimen>\n";
    privatefinalstatic String HTemplate = "<dimen name=\"y{0}\">{1}px</dimen>\n";

    publicstaticvoid main(String[] args) {
        makeString(320, 480);
        makeString(480,800);
        makeString(480, 854);
        makeString(540, 960);
        makeString(600, 1024);
        makeString(720, 1184);
        makeString(720, 1196);
        makeString(720, 1280);
        makeString(768, 1024);
        makeString(800, 1280);
        makeString(1080, 1812);
        makeString(1080, 1920);
        makeString(1440, 2560);
    }

    publicstaticvoid makeString(int w, int h) {

        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb.append("<resources>");
        float cellw = w / dw;
        for (int i = 1; i < 320; i++) {
            sb.append(WTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellw * i) + ""));
        }
        sb.append(WTemplate.replace("{0}", "320").replace("{1}", w + ""));
        sb.append("</resources>");

        StringBuffer sb2 = new StringBuffer();
        sb2.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
        sb2.append("<resources>");
        float cellh = h / dh;
        for (int i = 1; i < 480; i++) {
            sb2.append(HTemplate.replace("{0}", i + "").replace("{1}",
                    change(cellh * i) + ""));
        }
        sb2.append(HTemplate.replace("{0}", "480").replace("{1}", h + ""));
        sb2.append("</resources>");

        String path = rootPath.replace("{0}", h + "").replace("{1}", w + "");
        File rootFile = new File(path);
        if (!rootFile.exists()) {
            rootFile.mkdirs();
        }
        File layxFile = new File(path + "lay_x.xml");
        File layyFile = new File(path + "lay_y.xml");
        try {
            PrintWriter pw = new PrintWriter(new FileOutputStream(layxFile));
            pw.print(sb.toString());
            pw.close();
            pw = new PrintWriter(new FileOutputStream(layyFile));
            pw.print(sb2.toString());
            pw.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }

    publicstaticfloat change(float a) {
        int temp = (int) (a * 100);
        return temp / 100f;
    }
}

    代码应该很好懂,我们将一个屏幕宽度分为320份,高度480份,然后按照实际像素对每一个单位进行复制,放在对应values-widthxheight文件夹下面的lax.xml和lay.xml里面,这样就可以统一所有你想要的分辨率的单位了,下面是生成的一个320*480分辨率的文件,因为宽高分割之后总分数和像素数相同,所以x1就是1px,以此类推

<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">1.0px</dimen>
<dimen name="x2">2.0px</dimen>
<dimen name="x3">3.0px</dimen>
<dimen name="x4">4.0px</dimen>
<dimen name="x5">5.0px</dimen>
<dimen name="x6">6.0px</dimen>
<dimen name="x7">7.0px</dimen>
<dimen name="x8">8.0px</dimen>
<dimen name="x9">9.0px</dimen>
<dimen name="x10">10.0px</dimen>
...省略好多行
<dimen name="x300">300.0px</dimen>
<dimen name="x301">301.0px</dimen>
<dimen name="x302">302.0px</dimen>
<dimen name="x303">303.0px</dimen>
<dimen name="x304">304.0px</dimen>
<dimen name="x305">305.0px</dimen>
<dimen name="x306">306.0px</dimen>
<dimen name="x307">307.0px</dimen>
<dimen name="x308">308.0px</dimen>
<dimen name="x309">309.0px</dimen>
<dimen name="x310">310.0px</dimen>
<dimen name="x311">311.0px</dimen>
<dimen name="x312">312.0px</dimen>
<dimen name="x313">313.0px</dimen>
<dimen name="x314">314.0px</dimen>
<dimen name="x315">315.0px</dimen>
<dimen name="x316">316.0px</dimen>
<dimen name="x317">317.0px</dimen>
<dimen name="x318">318.0px</dimen>
<dimen name="x319">319.0px</dimen>
<dimen name="x320">320px</dimen>
</resources>

那么1080*1960分辨率下是什么样子呢?我们可以看下,由于1080和320是3.37倍的关系,所以x1=3.37px

<?xml version="1.0" encoding="utf-8"?>
<resources><dimen name="x1">3.37px</dimen>
<dimen name="x2">6.75px</dimen>
<dimen name="x3">10.12px</dimen>
<dimen name="x4">13.5px</dimen>
<dimen name="x5">16.87px</dimen>
<dimen name="x6">20.25px</dimen>
<dimen name="x7">23.62px</dimen>
<dimen name="x8">27.0px</dimen>
<dimen name="x9">30.37px</dimen>
<dimen name="x10">33.75px</dimen>
...省略好多行
<dimen name="x300">1012.5px</dimen>
<dimen name="x301">1015.87px</dimen>
<dimen name="x302">1019.25px</dimen>
<dimen name="x303">1022.62px</dimen>
<dimen name="x304">1026.0px</dimen>
<dimen name="x305">1029.37px</dimen>
<dimen name="x306">1032.75px</dimen>
<dimen name="x307">1036.12px</dimen>
<dimen name="x308">1039.5px</dimen>
<dimen name="x309">1042.87px</dimen>
<dimen name="x310">1046.25px</dimen>
<dimen name="x311">1049.62px</dimen>
<dimen name="x312">1053.0px</dimen>
<dimen name="x313">1056.37px</dimen>
<dimen name="x314">1059.75px</dimen>
<dimen name="x315">1063.12px</dimen>
<dimen name="x316">1066.5px</dimen>
<dimen name="x317">1069.87px</dimen>
<dimen name="x318">1073.25px</dimen>
<dimen name="x319">1076.62px</dimen>
<dimen name="x320">1080px</dimen>
</resources>

无论在什么分辨率下,x320都是代表屏幕宽度,y480都是代表屏幕高度。
    那么,我们应该如何使用呢?首先,我们要把生成的所有values文件夹放到res目录下,当设计师把UI高清设计图给你之后,你就可以根据设计图上的尺寸,以某一个分辨率的机型为基础,找到对应像素数的单位,然后设置给控件即可。
    下图还是两个Button,不同的是,我们把单位换成了我们在values文件夹下dimen的值,这样在你指定的分辨率下,不管宽度是320dp、360dp,还是410dp,就都可以完全适配了。

这里写图片描述

但是,还是有个问题,为什么下面的三个没有适配呢?
    这是因为由于在生成的values文件夹里,没有对应的分辨率,其实一开始是报错的,因为默认的values没有对应dimen,所以我只能在默认values里面也创建对应文件,但是里面的数据却不好处理,因为不知道分辨率,我只好默认为x1=1dp保证尽量兼容。这也是这个解决方案的几个弊端,对于没有生成对应分辨率文件的手机,会使用默认values文件夹,如果默认文件夹没有,就会出现问题。
    所以说,这个方案虽然是一劳永逸,但是由于实际上还是使用的px作为长度的度量单位,所以多少和google的要求有所背离,不好说以后会不会出现什么不可预测的问题。其次,如果要使用这个方案,你必须尽可能多的包含所有的分辨率,因为这个是使用这个方案的基础,如果有分辨率缺少,会造成显示效果很差,甚至出错的风险,而这又势必会增加软件包的大小和维护的难度,所以大家自己斟酌,择优使用。

提供备用位图

    由于 Android 可在具有各种屏幕密度的设备上运行,因此我们提供的位图资源应始终可以满足各类普遍密度范围的要求:低密度、中等密度、高密度以及超高密度。这将有助于我们的图片在所有屏幕密度上都能得到出色的质量和效果。
    要生成这些图片,我们应先提取矢量格式的原始资源,然后根据以下尺寸范围针对各密度生成相应的图片。

  • xhdpi:2.0
  • hdpi:1.5
  • mdpi:1.0
  • ldpi:0.75

    也就是说,如果我们为 xhdpi 设备生成了 200x200 px尺寸的图片,就应该使用同一资源为 hdpi、mdpi 和 ldpi 设备分别生成 150x150、100x100 和 75x75 尺寸的图片。
    然后,将生成的图片文件放在 res/ 下的相应子目录中(mdpi、hdpi、xhdpi、xxhdpi),系统就会根据运行您应用的设备的屏幕密度自动选择合适的图片。
    这样一来,只要我们引用 @drawable/id,系统都能根据相应屏幕的 dpi 选取合适的位图。
    但是还有个问题需要注意下,如果是.9图或者是不需要多个分辨率的图片,就放在drawable文件夹即可,对应分辨率的图片要正确的放在合适的文件夹,否则会造成图片拉伸等问题。

参考链接:http://blog.csdn.net/zhaokaiqiang1992/article/details/45419023

作者:mynameishuangshuai 发表于2016/10/26 16:19:48 原文链接
阅读:36 评论:0 查看评论

cpufreq动态频率调节(Sofia3GR)浅析及应用层APK实现

$
0
0
  • cpufreq动态频率调节浅析

cpufreq核心部分的代码都在:/drivers/cpufreq/cpufreq.c中,本文章是基于SOFIA3GR 6.0的代码进行解析,linux内核版本3.14.0。具体cpufreq核心(core)架构与API可参考:http://blog.csdn.net/droidphone/article/details/9385745

这里主要针对cpufreq的sysfs接口进行解析及apk实现定频功能

C:\Users\qinfeng>adb shell
root@TM800AR:/ # cd  sys/devices/system/cpu
cd  sys/devices/system/cpu
root@TM800AR:/sys/devices/system/cpu # ls
ls
cpu0
cpu1
cpu2
cpu3
cpufreq
cpuidle
kernel_max
modalias
offline
online
possible
power
present
uevent
version

所有与cpufreq相关的sysfs接口都位于/sys/devices/system/cpu下面:
其中:
cpu0到cpu7代表我们这个cpu是四核的,有四个cpu
online:代表正在工作的cpu
offline:代表未工作被关闭的cpu
present:代表主板上已经安装的cpu

root@TM800AR:/sys/devices/system/cpu # cat online
cat online
0-3
root@TM800AR:/sys/devices/system/cpu # cat offline
cat offline

root@TM800AR:/sys/devices/system/cpu # cat present
cat present
0-3

其中:kernel_max,version,possible这三个看下面代码输出容易理解,我们现在测试的也是RK es2.0的芯片
但modalias搞不懂,uevent为空

root@TM800AR:/sys/devices/system/cpu # cat kernel_max
cat kernel_max
3
root@TM800AR:/sys/devices/system/cpu # cat version
cat version
es2.0
root@TM800AR:/sys/devices/system/cpu # cat possible
cat possible
0-3
root@TM800AR:/sys/devices/system/cpu # cat modalias
cat modalias
x86cpu:vendor:0000:family:0006:model:005D:feature:,0000,0001,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0013,0017,0018,0019,001A,001B,001C,0034,003B,003D,0068,006B,006F,0070,0072,0074,0075,0076,0080,0081,0083,0089,008D,008E,008F,0093,0094,0096,0097,0099,00C0,00C8,0121,0127,0129,012D,013F
root@TM800AR:/sys/devices/system/cpu # cat uevent
cat uevent
root@TM800AR:/sys/devices/system/cpu #

至此,还有几个目录
cpu0-cpu3,cpufreq,cpuidle,power
主要针对cpu0说明一下,cpu0里面的参数决定cpu频率调整模式,cpu频率设置的一些节点,cpu1,cpu2,cpu3设置都是基于cpu0的 所以只许更改cpu0的相关配置参数即可

root@TM800AR:/sys/devices/system/cpu/cpu0 # ls -al
ls -al
drwxr-xr-x root     root              2016-01-08 08:01 cache
drwxr-xr-x root     root              2016-01-08 08:01 cpufreq
drwxr-xr-x root     root              2016-01-08 08:01 cpuidle
-r-------- root     root         4096 2016-01-08 08:01 crash_notes
-r-------- root     root         4096 2016-01-08 08:01 crash_notes_size
drwxr-xr-x root     root              2016-01-08 08:01 power
lrwxrwxrwx root     root              2016-01-08 08:01 subsystem -> ../../../../bus/cpu
drwxr-xr-x root     root              2016-01-08 08:01 topology
-rw-r--r-- root     root         4096 2016-01-08 08:01 uevent
root@TM800AR:/sys/devices/system/cpu/cpu1 # ls -al
ls -al
drwxr-xr-x root     root              2016-01-08 08:38 cache
lrwxrwxrwx root     root              2016-01-08 08:38 cpufreq -> ../cpu0/cpufreq
drwxr-xr-x root     root              2016-01-08 08:01 cpuidle
-r-------- root     root         4096 2016-01-08 08:01 crash_notes
-r-------- root     root         4096 2016-01-08 08:01 crash_notes_size
-rw-r--r-- root     root         4096 2016-01-08 08:01 online
drwxr-xr-x root     root              2016-01-08 08:01 power
lrwxrwxrwx root     root              2016-01-08 08:01 subsystem -> ../../../../bus/cpu
drwxr-xr-x root     root              2016-01-08 08:38 topology
-rw-r--r-- root     root         4096 2016-01-08 08:01 uevent

1.android中有一个动态调整cpu频率的模块,会生成一个文件夹/sys/devices/system/cpu/cpu0/cpufreq/
现在着重分析cpu动态频率调节相关的cpu0/cpufreq相关

127|root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # ls
ls
affected_cpus
cpuinfo_cur_freq
cpuinfo_max_freq
cpuinfo_min_freq
cpuinfo_transition_latency
related_cpus
scaling_available_frequencies
scaling_available_governors
scaling_cur_freq
scaling_driver
scaling_governor
scaling_max_freq
scaling_min_freq
scaling_setspeed
stats
thermal_scaling_max_freq

1.1 其中:cpuinfo_cur_freq cpuinfo_max_freq cpuinfo_min_freq这三个分别代表cpu当前运行频率,最高运行频率和最低运行频率,前缀cpuinfo代表的是cpu硬件上支持的频率,而scaling前缀代表的是可以通过CPUFreq系统用软件进行调节时所支持的频率,
cpuinfo_cur_freq代表通过硬件实际上读到的频率值,而scaling_cur_freq则是软件当前的设置值,多数情况下这两个值是一致的,但是也有可能因为硬件的原因,有微小的差异。scaling_available_frequencies会输出当前软件支持的频率值,看看我的cpu支持那些频率

1|root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # cat scaling_available_frequencies
at scaling_available_frequencies                                              <
416000 728000 900000 1040000 1200000

代表我这个cpu的可选频率有416000 728000 900000 1040000 1200000 5个档位
实际代码中对应(voltage-table-v2代表不同频率对应的cpu电压):

  &cpufreq {                                                                                                                             

          intel,cpufreq-table-v2 = <416000 728000 900000 1040000 1200000>;
         intel,voltage-table-v2 = <925000 1125000 1150000 1225000 1275000>;
        intel,vddcore-table-v2 = <VDD_CORE_MED VDD_CORE_HIGH VDD_CORE_HIGH VDD_CORE_HIGH VDD_CORE_HIGH>;
 };

同时我们得注意:scaling-min-freq和scaling-max-freq只能写上面数字的频率其他数字都是无效的

1.2 scaling_governor代表cpu频率调整模式

1|root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # cat scaling_available_governors
at scaling_available_governors                                                <
ondemand userspace interactive performance

上述可以看出我们cpu支持的模式有四种:

performance :CPU会固定工作在其支持的最高运行频率上;
Userspace:最早的cpufreq 子系统通过userspace governor为用户提供了这种灵活性。系统将变频策略的决策权交给了用户态应用程序,并提供了相应的接口供用户态应用程序调节CPU 运行频率使用。
ondemand:userspace是内核态的检测,效率低。而ondemand正是人们长期以来希望看到的一个完全在内核态下工作并且能够以更加细粒度的时间间隔对系统负载情况进行采样分析的governor。
interactived
此模式在与用户交互的时候,反应速度更快(即是频率调节的速度更快,更能随时对及时处理器的符合作出反应),由此,便可以提供更好地用户体验(conservative模式反应速度慢于此,因此有时候会出现稍卡的体验)
当然,为了达成这一点,interactive有更高的处理器负荷采样率,并且摒弃了上述两种调节方式在高负荷时候处理器频率不满足需求以后才进行调频,interactive保证了更快的反应,保留了频率调节器的高优先级,来更快地处理器负荷高起来的时候将频提高。
还有一个模式我们cpu没有:
powersave:CPU会固定工作在其支持的最低运行频率上。因此这两种governors 都属于静态governor,即在使用它们时CPU 的运行频率不会根据系统运行时负载的变化动态作出调整。这两种governors 对应的是两种极端的应用场景,使用performance governor 是对系统高性能的最大追求,而使用powersave governor 则是对系统低功耗的最大追求。
参考http://bbs.hiapk.com/thread-1313181-1-1.html

当我们要设置软件cpu频率时一定要注意:(scaling_max_freq scaling_min_freq scaling_setspeed)
当我们选择userspace作为我们的调频governor时,我们可以通过scaling_setspeed手工设置需要的频率。powersave则简单地使用最低的工作频率进行运行,而performance则一直选择最高的频率进行运行。

现在我们以应用层设置cpu频率为例制作一个适应我们cpu的apk并贴出源码及效果:
1.我们必须先设置节点的权限 我们现在是在我们代码平台实现,device/rockchip/sofia3gr/init.rc(不同平台有差异)
加入如下四条命令:


chown system system /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
chmod 0644 /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed
chown system system /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
chmod 0644 /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

增加scaling_governor权限主要是让我们用户能用scaling_setspeed设置cpu频率
scaling_setspeed主要用来设置cpu频率

上述命令也可以在adb shell中去设置前提是userdebug或者user版本并且有root权限,执行
adb root
adb remount

权限设置好后如下命令设置governor策略为userspace

root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # cat scaling_governor
cat scaling_governor
interactive
root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # echo userspace > scaling_governor
cho userspace > scaling_governor                                              <
root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # cat scaling_governor
cat scaling_governor
userspace
root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq #

如果机器有温控管理记得关闭温控管理 此平台关闭方法
先在adb shell 中输入 setprop persist.service.thermal 0 ,并重启设备,以关掉Intel thermal app
之后我们便能设置scaling_setspeed了

root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # echo 728000 > scaling_setspeed
cho 728000 > scaling_setspeed                                                 <
root@TM800AR:/sys/devices/system/cpu/cpu0/cpufreq # cat cpuinfo_cur_freq
cat cpuinfo_cur_freq
728000

这样cpu频率就运行在了728000

2.继续我们之前的apk怎么实现呢
将apk放入系统中编译:
在相应的mk文件中加入:
**PRODUCT_PACKAGES +=\
Cpufreq_for_intel**

3.apk源码
注意点
3.1 android.mk必须修改 6 LOCAL_CERTIFICATE := platform为平台签名apk
1. android.mk
1 LOCAL_PATH := (callmydir)2include(CLEAR_VARS)
3
4 LOCAL_PACKAGE_NAME := Cpufreq_for_intel
5 LOCAL_SRC_FILES := $(call all-subdir-java-files)
6 LOCAL_CERTIFICATE := platform
7 include $(BUILD_PACKAGE)
3.2 AndroidManifest.xml 需要加上如下属性
android:sharedUserId=”android.uid.system”

贴出部分源码:
MainActivity.java

package com.example.cpufreq_for_intel;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Settings.System;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

    private RadioGroup cpufreq_group = null;

    private RadioButton freq1 = null;
    private RadioButton freq2 = null;
    private RadioButton freq3 = null;
    private RadioButton freq4 = null;
    private RadioButton freq5 = null;
    private TextView tvCpu1 = null;

    Boolean thd = true;

    static String c1 = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor";
    static String c2 = "/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i("012", "beginning cpuFreq Setting now!");
        cpufreq_group = (RadioGroup) findViewById(R.id.radioGroup);
        freq1 = (RadioButton) findViewById(R.id.freq1);
        freq2 = (RadioButton) findViewById(R.id.freq2);
        freq3 = (RadioButton) findViewById(R.id.freq3);
        freq4 = (RadioButton) findViewById(R.id.freq4);
        freq5 = (RadioButton) findViewById(R.id.freq5);

        tvCpu1 = (TextView) findViewById(R.id.tvCpu1);

        setCurCpuFreqManager("userspace");

        tvCpu1.setText("" + getCurCpuFreq());


        cpufreq_group
                .setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {

                    @Override
                    public void onCheckedChanged(RadioGroup arg0, int arg1) {
                        // TODO Auto-generated method stub
                        if (arg1 == freq1.getId()) {
                            setCurCpuFreq("1200000");
                            tvCpu1.setText("" + getCurCpuFreq());
                            Toast.makeText(getApplicationContext(),
                                    "cpuFreq set :1200000", Toast.LENGTH_SHORT)
                                    .show();
                        } else if (arg1 == freq2.getId()) {
                            setCurCpuFreq("1040000");
                            tvCpu1.setText("" + getCurCpuFreq());
                            Toast.makeText(getApplicationContext(),
                                    "cpuFreq set :1040000", Toast.LENGTH_SHORT)
                                    .show();
                        } else if (arg1 == freq3.getId()) {
                            setCurCpuFreq("900000");
                            tvCpu1.setText("" + getCurCpuFreq());
                            Toast.makeText(getApplicationContext(),
                                    "cpuFreq set :900000", Toast.LENGTH_SHORT)
                                    .show();
                        } else if (arg1 == freq4.getId()) {
                            setCurCpuFreq("728000");
                            tvCpu1.setText("" + getCurCpuFreq());
                            Toast.makeText(getApplicationContext(),
                                    "cpuFreq set :728000", Toast.LENGTH_SHORT)
                                    .show();
                        } else if (arg1 == freq5.getId()) {
                            setCurCpuFreq("416000");
                            tvCpu1.setText("" + getCurCpuFreq());
                            Toast.makeText(getApplicationContext(),
                                    "cpuFreq set :416000", Toast.LENGTH_SHORT)
                                    .show();
                        }

                    }
                });

        Log.d("012", "getCurCpuFreq: " + getCurCpuFreq()
                + "       getCurCpuFreqManager:" + getCurCpuFreqManager());

    }

    // 实时设置CPU当前频率(单位KHZ)
    public static void setCurCpuFreq(String s) {

        try {
            FileWriter fw = new FileWriter(c2);
            String sfreq = String.valueOf(s);
            Log.e("012", "Excute exception: 0000c2");
            fw.write(sfreq);
            fw.flush();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("012", "ctrlCpuFreq(): error;");
        }
    }

    // 实时获取CPU当前频率(单位KHZ)
    public static String getCurCpuFreq() {
        String result = "N/A";
        try {
            FileReader fr = new FileReader(
                    "/sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed");// scaling_cur_freq
            BufferedReader br = new BufferedReader(fr);
            String text = br.readLine();
            result = text.trim();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 实时获取CPU管理策略
    public static String getCurCpuFreqManager() {
        String result = "N/A";
        try {
            FileReader fr = new FileReader(
                    "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor");// scaling_cur_freq
            BufferedReader br = new BufferedReader(fr);
            String text = br.readLine();
            result = text.trim();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    // 设置CPU管理策略 userspace performance ondemand interactive
    public static void setCurCpuFreqManager(String s1) {

        Log.e("012", "set setCurCpuFreqManager:" + s1);
        try {
            FileWriter fw = new FileWriter(c1);
            String sfreq = String.valueOf(s1);
            fw.write(sfreq);
            fw.flush();
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
            Log.e("012", "set setCurCpuFreqManager error;");
            // return;
        }
    }

    // 获取CPU名字
    public static String getCpuName() {
        try {
            FileReader fr = new FileReader("/proc/cpuinfo");
            BufferedReader br = new BufferedReader(fr);
            String text = br.readLine();
            String[] array = text.split(":\\s+", 100);
            for (int i = 0; i < array.length; i++) {
            }
            return array[1];
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        thd = false;
        Log.e("012", "start onDestroy~~~");
    }

}

AndroidManifest.xml 如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.cpufreq_for_intel"
    android:versionCode="1"
    android:sharedUserId="android.uid.system"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="23" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.cpufreq_for_intel.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

apk源码下载:链接:http://pan.baidu.com/s/1cqXsq6 密码:0hd9

作者:qf0727 发表于2016/10/26 16:22:08 原文链接
阅读:38 评论:0 查看评论

iOS 高德地图(五)绘制点标记

$
0
0

(一)添加默认样式点标记

  • iOS SDK提供的大头针标注MAPinAnnotationView,通过它可以设置大头针颜色、是否显示动画、是否支持长按后拖拽大头针改变坐标等。
    **
  • **这里用到的类是
    MAPinAnnotationView
    让我们对它的属性有个了解。
    **
  • 继承关系图:

    2016-09-30_18-07-06.png

  • 属性图:
    2016-09-30_17-49-14.png

iOS SDK提供的大头针标注MAPinAnnotationView,通过它可以设置大头针颜色、是否显示动画、是否支持长按后拖拽大头针改变坐标等。在地图上添加大头针标注的步骤如下:

(1) 修改ViewController.m文件,在viewDidAppear方法中添加如下所示代码添加标注数据对象。

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    MAPointAnnotation *pointAnnotation = [[MAPointAnnotation alloc] init];
    pointAnnotation.coordinate = CLLocationCoordinate2DMake(39.989631, 116.481018);
    pointAnnotation.title = @"方恒国际";
    pointAnnotation.subtitle = @"阜通东大街6号";

    [_mapView addAnnotation:pointAnnotation];
}

(2) 实现 协议中的 mapView:viewForAnnotation:回调函数,设置标注样式。如下所示:


- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id <MAAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MAPointAnnotation class]])
    {
        static NSString *pointReuseIndentifier = @"pointReuseIndentifier";
        MAPinAnnotationView*annotationView = (MAPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:pointReuseIndentifier];
        if (annotationView == nil)
        {
            annotationView = [[MAPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:pointReuseIndentifier];
        }
        annotationView.canShowCallout= YES;       //设置气泡可以弹出,默认为NO
        annotationView.animatesDrop = YES;        //设置标注动画显示,默认为NO
        annotationView.draggable = YES;        //设置标注可以拖动,默认为NO
        annotationView.pinColor = MAPinAnnotationColorPurple;
        return annotationView;
    }
    return nil;
}

(二)自定义标注图标

  • 若大头针样式的标注不能满足您的需求,您可以自定义标注图标
    其实就是修改一下MAAnnotationView 的 image 属性
    步骤:

**(1) 添加标注数据对象,可参考大头针标注的步骤(1)。
(2) 导入标记图片文件到工程中。这里我们导入一个名为 restauant.png 的图片文件。
(3) 在 协议的回调函数mapView:viewForAnnotation:中修改 MAAnnotationView 对应的标注图片。示例代码如下:**

- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MAPointAnnotation class]])
    {
        static NSString *reuseIndetifier = @"annotationReuseIndetifier";
        MAAnnotationView *annotationView = (MAAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseIndetifier];
        if (annotationView == nil)
        {
            annotationView = [[MAAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:reuseIndetifier];
        }
        annotationView.image = [UIImage imageNamed:@"restaurant"];
        //设置中心点偏移,使得标注底部中间点成为经纬度对应点
        annotationView.centerOffset = CGPointMake(0, -18);
        return annotationView;
    }
    return nil;
}

2016-09-30_18-13-54.png

(三)添加自定义气泡

  • 气泡在iOS中又称为callout,它由背景和气泡内容构成,如下图所示:
    2016-09-30_18-15-21.png

思路:
我们点击标注可以弹出气泡
是利用了MAAnnotationView 的这个方法


- (void) setSelected:animated:

我们需要重写这个方法,选中时新建并添加气泡视图,传入数据;非选中时删除气泡视图。
**
1.自定义一个气泡视图
2.定义MAAnnotationView 的子类,重写 setSelected:animated: 方法
3.修改ViewController.m,在MAMapViewDelegate的回调方法mapView:viewForAnnotation中的修改annotationView的类型
**

1.每个气泡显示的内容是根据您的需求定义的,这里我们按照如上图所示的气泡介绍实现一个自定义气泡的步骤:

(1) 新建自定义气泡类 CustomCalloutView,继承 UIView。

(2) 在 CustomCalloutView.h 中定义数据属性,包含:图片、商户名和商户地址。

@interface CustomCalloutView : UIView

@property (nonatomic, strong) UIImage *image; //商户图
@property (nonatomic, copy) NSString *title; //商户名
@property (nonatomic, copy) NSString *subtitle; //地址

@end

(3) 在CustomCalloutView.m中重写UIView的drawRect方法,绘制弹出气泡的背景。

#define kArrorHeight        10

- (void)drawRect:(CGRect)rect
{

    [self drawInContext:UIGraphicsGetCurrentContext()];

    self.layer.shadowColor = [[UIColor blackColor] CGColor];
    self.layer.shadowOpacity = 1.0;
    self.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);

}

- (void)drawInContext:(CGContextRef)context
{

    CGContextSetLineWidth(context, 2.0);
    CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.8].CGColor);

    [self getDrawPath:context];
    CGContextFillPath(context);

}

- (void)getDrawPath:(CGContextRef)context
{
    CGRect rrect = self.bounds;
    CGFloat radius = 6.0;
    CGFloat minx = CGRectGetMinX(rrect),
    midx = CGRectGetMidX(rrect),
    maxx = CGRectGetMaxX(rrect);
    CGFloat miny = CGRectGetMinY(rrect),
    maxy = CGRectGetMaxY(rrect)-kArrorHeight;

    CGContextMoveToPoint(context, midx+kArrorHeight, maxy);
    CGContextAddLineToPoint(context,midx, maxy+kArrorHeight);
    CGContextAddLineToPoint(context,midx-kArrorHeight, maxy);

    CGContextAddArcToPoint(context, minx, maxy, minx, miny, radius);
    CGContextAddArcToPoint(context, minx, minx, maxx, miny, radius);
    CGContextAddArcToPoint(context, maxx, miny, maxx, maxx, radius);
    CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
    CGContextClosePath(context);
}

(4) 定义用于显示气泡内容的控件,并添加到SubView中。

如上图所示气泡,我们需要一个UIImageView和两个UILabel,添加方法如下:

#define kPortraitMargin     5
#define kPortraitWidth      70
#define kPortraitHeight     50

#define kTitleWidth         120
#define kTitleHeight        20

@interface CustomCalloutView ()

@property (nonatomic, strong) UIImageView *portraitView;
@property (nonatomic, strong) UILabel *subtitleLabel;
@property (nonatomic, strong) UILabel *titleLabel;

@end

@implementation CustomCalloutView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        self.backgroundColor = [UIColor clearColor];
        [self initSubViews];
    }
    return self;
}

- (void)initSubViews
{
    // 添加图片,即商户图
    self.portraitView = [[UIImageView alloc] initWithFrame:CGRectMake(kPortraitMargin, kPortraitMargin, kPortraitWidth, kPortraitHeight)];

    self.portraitView.backgroundColor = [UIColor blackColor];
    [self addSubview:self.portraitView];

    // 添加标题,即商户名
    self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kPortraitMargin * 2 + kPortraitWidth, kPortraitMargin, kTitleWidth, kTitleHeight)];
    self.titleLabel.font = [UIFont boldSystemFontOfSize:14];
    self.titleLabel.textColor = [UIColor whiteColor];
    self.titleLabel.text = @"titletitletitletitle";
    [self addSubview:self.titleLabel];

    // 添加副标题,即商户地址
    self.subtitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(kPortraitMargin * 2 + kPortraitWidth, kPortraitMargin * 2 + kTitleHeight, kTitleWidth, kTitleHeight)];
    self.subtitleLabel.font = [UIFont systemFontOfSize:12];
    self.subtitleLabel.textColor = [UIColor lightGrayColor];
    self.subtitleLabel.text = @"subtitleLabelsubtitleLabelsubtitleLabel";
    [self addSubview:self.subtitleLabel];
}
(5) 在CustomCalloutView.m中给控件传入数据。


- (void)setTitle:(NSString *)title
{
    self.titleLabel.text = title;
}

- (void)setSubtitle:(NSString *)subtitle
{
    self.subtitleLabel.text = subtitle;
}

- (void)setImage:(UIImage *)image
{
    self.portraitView.image = image;
}

2.自定义AnnotationView,步骤如下:

(1) 新建类CustomAnnotationView,继承MAAnnotationView或MAPinAnnotationView。若继承MAAnnotationView,则需要设置标注图标;若继承MAPinAnnotationView,使用默认的大头针标注

(2) 在CustomAnnotationView.h中定义自定义气泡属性,代码如下所示:

#import "CustomCalloutView.h"

@interface CustomAnnotationView : MAAnnotationView

@property (nonatomic, readonly) CustomCalloutView *calloutView;

@end

(3) 在CustomAnnotationView.m中修改calloutView属性,如下:

@interface CustomAnnotationView ()

@property (nonatomic, strong, readwrite) CustomCalloutView *calloutView;

@end

(4) 重写选中方法setSelected。选中时新建并添加calloutView,传入数据;非选中时删除calloutView。

#define kCalloutWidth       200.0
#define kCalloutHeight      70.0

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    if (self.selected == selected)
    {
        return;
    }

    if (selected)
    {
        if (self.calloutView == nil)
        {
            self.calloutView = [[CustomCalloutView alloc] initWithFrame:CGRectMake(0, 0, kCalloutWidth, kCalloutHeight)];
            self.calloutView.center = CGPointMake(CGRectGetWidth(self.bounds) / 2.f + self.calloutOffset.x,
                                                  -CGRectGetHeight(self.calloutView.bounds) / 2.f + self.calloutOffset.y);
        }

        self.calloutView.image = [UIImage imageNamed:@"building"];
        self.calloutView.title = self.annotation.title;
        self.calloutView.subtitle = self.annotation.subtitle;

        [self addSubview:self.calloutView];
    }
    else
    {
        [self.calloutView removeFromSuperview];
    }

    [super setSelected:selected animated:animated];
}

注意:提前导入building.png图片。
**
3.修改ViewController.m
**
在MAMapViewDelegate的回调方法mapView:viewForAnnotation中的修改annotationView的类型,代码如下:

#import “CustomAnnotationView.h”

- (MAAnnotationView *)mapView:(MAMapView *)mapView viewForAnnotation:(id<MAAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MAPointAnnotation class]])
    {
        static NSString *reuseIndetifier = @"annotationReuseIndetifier";
        CustomAnnotationView *annotationView = (CustomAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:reuseIndetifier];
        if (annotationView == nil)
        {
            annotationView = [[CustomAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:reuseIndetifier];
        }
        annotationView.image = [UIImage imageNamed:@"restaurant"];

        // 设置为NO,用以调用自定义的calloutView
        annotationView.canShowCallout = NO;

        // 设置中心点偏移,使得标注底部中间点成为经纬度对应点
        annotationView.centerOffset = CGPointMake(0, -18);
        return annotationView;
    }
    return nil;
}
作者:Xiejunyi12 发表于2016/10/26 16:37:52 原文链接
阅读:35 评论:0 查看评论

Activity启动流程分析(二)

$
0
0

广告时间,大家喜欢我的文章,可以关注我的博客http://zwgeek.com

前面说到,希望分析一下Activity的启动流程,整个过程准备分为三篇文章来写
- 程序调用startActivity后发生的操作
- 如果被startActivity的程序是需要启动的程序,程序在最开始初始化时发生的操作。例如在Launcher中启动一个程序。
- 如果被startActivity的程序是已经启动的程序,发生的操作。例如程序自己调用startActivity启动一个自己程序中的Activity

第一篇文章也已经讲完了程序调用startActivity之后发生的事情。

还没有读过的同学可以看这里Activity启动流程分析

这篇文章就来讲第二个部分,一个程序被启动之后发生的事情。第一篇文章中还提到了几个问题,也会在这篇文章中做出解答。

  • getApplication() ,getApplicationContext(),getBaseContext()分别有什么区别?
  • 单个进程中能存在多个application么?
  • 为什么不能使用service或者application作为创建dialog的context参数?

第一篇文章最后讲到了Process.start方法,说到了这个方法会启动一个线程,并且运行ActivityThread的main方法来正式开启一个Android程序。所以,很显然,这篇文章就要从ActivityThread的main方法来开始。

首先,来看一下这个Android程序的起点方法,一睹芳容。

这里写图片描述

这个方法重要的是在Looper prepare之后的部分,Handler跟loop的机制比较简单,可以先百度一下,我后面可能也会写篇文章说一下。简单来说loop就是一个无限循环,通过循环去去查询有没有handler发过来命令,如果有就处理,没有就继续循环。

这样的话,我们就能看出,主进程在做了一个初始化工作之后就把自己放在了一个loop循环中,要跟这个程序打交道,怎么办呢,就是通过获取它的handler,然后发命令,比如现在需要调用onResume方法,通过handler告诉主进程looper要调用onResume,looper就会做相应的处理了。当然这个是关于Android生命周期方法的调用问题,我们也是要单独拉出来讲的,这里就不细说了。

其实这篇文章的重点是这一句话,thread.attach(false),ActivityThread的初始化操作。来看下这个方法具体做了什么操作。

这里写图片描述

这个方法看下来还好,也都是配置一些监听器,像ViewRoot监听,内存监听,等等。重要的还是我选中的这一段,首先创建了一个ApplicationThread,然后把这个ApplicationThread交给了RuntimeInit,很多人开发过程中最头痛的就是RuntimeException,其实这里就是异常监控的初始化过程。然后创建了一个ActivityManagerNative,第一篇文章中就提过,ActivityManagerNative在创建的时候就会和ActivityManagerService绑定。接下来程序就可以通过AMN来访问AMS了。可是大家有想过,Client可以通过AMN来访问AMS,但是Server端怎么访问Client端呢,看这句话attachApplication,其实这里就是程序把自己的一个控制器交给了Server端,然后Server端就可以通过这个控制器来操作Client端了。不信我们来看下ApplicationThread的方法。

这里写图片描述

是不是看到很多控制生命周期的方法,是的,AMS就是通过这个ApplicationThread来控制Client端的。

那么我们来总结一下这里,程序通过AMN来绑定AMS后,自己创建了一个桥梁applicationThread,然后把这个桥梁交给AMS,意思就是说,这是我小弟,以后联系我可以通过他。另一方面,在这个attach中client的各种初始化已经完成了。接下来的工作就通过attachApplication这个方法移交给AMS端了。

那接下来我们来看AMS端的attachApplication方法

这里写图片描述

AMS端先通过Binder查询到程序的pid,然后调用attachApplicationLocked,继续往下看,这个方法就是AMS在接到“一个新进程启动了”这件事之后做了一些工作,很复杂,但AMS毕竟是老板,多做一些是正常的。

这里写图片描述

这里写图片描述

先是用一个ProcessRecord来记录所有和Process有关的信息。

这里写图片描述

这里有一个generateProvider的操作,但是我感觉应该不是生成程序的ContentProvider,因为此时Manifest文件还没有被解析,这里应该是为Application生成一些系统必要的ContentProvider。至于对不对,后面再验证吧。

后面做了一些配置工作后,调用了这个方法

这里写图片描述

是的,这个方法才是主线剧情。通过这个方法,AMS将自己初始化的一些成果,告诉了Client端,并将控制权重新交回给Client端。在看bindApplication之前,我们看下AMS后面的工作。

这里写图片描述

源码的注释写的比较清楚了,判断有没有其他组件在等这个进程启动,如果有,那么这个进程已经启动了,就该通知他们做事了。这不关我们的事,回到正题吧,看看控制权回到Client那边后,又做了什么。不过这里我们也再一次验证了AMS通过ApplicationThread这个类来和Client端打交道。

好,老板做完事情了,工作又回到小弟手中了。什么?你说老板其实什么都没做,你可以去财务领工资了。

但是ApplicationThread毕竟是个桥梁,实际的工作还是得给app的老大ActivityThread来做,所以这个bindApplication方法也是记录了一些AMS传回来的信息之后,又把工作给了ActivityThread。

我们来看下这个bindApplication做了什么,首先记录了一些配置信息

这里写图片描述

然后在VM中注册APP的信息

这里写图片描述

这里注释也说了,有两种情况,两个package是共享runtime的。

  • 设置了shareUserId
  • 设置了ProcessName

在share的情况下是不用再VM中注册的,我的理解是,share的组件并不是一个完整的app,而他所属的原来的app其实已经注册过了。

这里有个问题是,工作是怎么给ActivityThread的呢?看bindApplication的第三部分

这里写图片描述

ApplicationThread是ActivityThread的内部类,内部类代表什么呢,它其实是持有一个外部类ActivityThread的对象引用的。可以这么说在ApplicationThread中其实是可以调到ActivityThread的所有方法的。那么它为什么要用这种sendMessage,然后通过Handler处理的这种方式呢。我们来想一下Handler的一个作用是什么。切换线程,在任何情况下,不管bindApplication这个方法运行在哪个线程中,只要通过handler这种方式,都可以回到ActivityThread所在的线程,也就是主线程。这就保证了什么呢,保证了Android的所有生命周期方法都是运行在主线程的,也就是我们常说的UI线程。

简单跟下sendMessage

这里写图片描述

这里写图片描述

这里写图片描述

mH是一个H类型的对象,这个H就是ActivityThread内部的Handler

这里写图片描述

看下这个Handler的handleMessage方法,跟下对BIND_APPLICATION消息的处理。

这里写图片描述

进到了handleBIndApplication方法,这里就是Application的启动过程了,让我们来仔细看下这个方法处理的步骤

这里写图片描述

国际惯例,前面也是各种记录,配置,初始化的工作,我们可以完全忽略。

这里写图片描述

这个地方是一个小的知识点,在3.1以前的版本上,AsyncTask会改变默认的executor,我们看下改变之后的executor是什么样的。

这里写图片描述

而默认的executor是这样的

这里写图片描述

所以3.1以前版本的AsyncTask是并行执行任务的,而3.1以后版本反而是顺序执行任务的,当然,这个配置可以通过AsyncTask的参数而改变。

然后我们回到handleBindApplication,后面会继续设置时区,位置,屏幕参数等。

这里写图片描述

之后创建了一个Context对象,注意,这是我们到目前位置接触到的第一个Context。我们知道在Android中Application,Activity等等其实都是Context的子类,但是他们又是不同的。这里创建的这个context对应的是我们开发过程中的哪个呢,让我们继续往下看。

这里写图片描述

然后又是一堆配置,其中包括UI线程不能执行网络操作的配置

这里写图片描述

然后是关于调试的相关配置,开启一个调试端口,其实关于调试也是需要讲很多的,调试本身也是C/S结构的,客户端开一个端口,等着服务端来连接进行调试,这里就是客户端打开端口的操作。

这里写图片描述

设置了一个默认的HTTP代理

这里写图片描述

后面一段是创建了一个Instrumentation对象,这里不截图的。之前第一篇文章我们也提到过,跟ActivityStart有关的操作都是由Instrumentation这个类管理的,被我们亲切的称为大管家,其实是为了监视我们的操作。。。

然后,这个方法讲了这么多,前面大家基本可以忽略,到现在才是重点。

这里写图片描述

这一段,首先创建了一个Application,恩,我们开发过程中遇见的Application就是这里生成的。后面一段是初始化ContentProvider,这个我们后面讲ContentProvider启动过程的时候会看到,不过这里能知道的一个点就是ContentProvider的启动时间是相当早的,在Application的onCreate之前。然后这里的providers确实是之前我们说到的AMS生成的,然后一路传过来的。恩,先不细看了,因为这篇文章主要想说Activity的启动过程。

后面调用了Instrumentation的onCreate方法,是个空方法,可Override,再后面看到吗,通过Instrumentation大管家呼叫了Application的OnCreate方法

这里写图片描述

至此,我们开发者接触到的Android生命周期中的第一个方法,Application的onCreate被执行了。至于前面生成的Context,我看了一下,传给了Instrumentation成为了Instrumentation中的appContext,但是我并没有找到跟Application对象结合的方法。这个继续往后看吧。

到此为止,这个handleBindApplication方法就结束了,创建了一个appContext,一个Instrumentation,一个Application,并且调用了Application的onCreate。中间还涉及到ContentProvider的初始化操作,我们先忽略。那么Activity在哪里,为什么感觉自己被带偏了。我又一路往前找。终于在AMS的attachApplicationLocked中,我看到了这一步。

这里写图片描述

bindApplication在执行完我们上面说的那一堆之后,调用了StackSupervisor的attachApplicationLocked,好,我们来看一下。同时,这里的调用顺序也保证了Application的onCreate方法在Activity之前进行。

这里写图片描述

方法中有一个realStartActivity的方法,名字很形象,前面我们调用过那么多次的startActivity,但是真正的Activity在这里才生成。

这个realStartActivity嘟噜嘟噜的扯了好多,不知道在干吗,但是终于看到了一个熟悉的影子

这里写图片描述

就这样控制权又回到了ActivityThread

这里写图片描述

这里先创建了一个ActivityClientRecord,这个就是Client端管理生成的activity对象的包装类,后面生成的Activity类都会被ActivityClientRecord包装一层,然后保存到ActivityThread的mActivities中。

这里写图片描述

跟Application那边一样,scheduleLaunchActivity最终会被handleLaunchActivity处理,我们略过中间过程,直接看handleLaunchActivity吧。

这里写图片描述

这个方法触发了Activity的两个生命周期方法,分别是我标出来的onCreate的onResume,然后后面那一段我的理解是Activity被创建出来,并且调用了onResume之后并没有被显示,那么就立刻调用onPause,但其实不是很懂这个地方。让我想想再回来补充吧。

接下来看performLaunchActivity吧

这里写图片描述

首先更新了ActivityClientRecord的信息,包括ActivityInfo,ComponentName等,我们开发过程中也是经常用到,这些信息都是存在ActivityClientRecord中的。

这里写图片描述

接下来创建了一个Activity,天哪,我们分析了这么久,终于看到Activity了。创建过程很简单,通过反射new了一个类出来,这个时候的Activity是还没有生命周期的。需要把Activity托管给AMS,才能有生命周期。

这里写图片描述

接着,我们获取到之前创建的那个Application,为Activity创建了一个Context,然后通过Activity的attach方法把这些绑定起来。

这里写图片描述

生成Context的方法和之前为Instrumentation生成Context的方法差不多,返回的是一个ContextImpl类型的对象,保存了Activity的上下文。

这里写图片描述

attach方法将所有的对象包括Instrumentation,Application, ActivityThread等等全部在Activity中保存了一份。

这里写图片描述

回到performLaunchActivity,attach之后通过Instrumentation大管家调用了Activity的onCreate方法

这里写图片描述

然后将生成的activity交给ActivityClientRecord,并保存在mActivities中,这就完成了Activity的生成,并托管给系统,之后系统都可以在适当的时候通过token来获取到相应的Activity,并调用其生命周期。

这样performLaunchActivity就结束了,我们返回上一层handleLaunchActivity继续往下看,Activity在生成之后是会立刻调用onResume的。这两个生命周期有什么区别呢, 其实就在于onCreate跟onResume之间执行的这几句话,说实话,在创建的时候区别不大。不同的是onResume未来还会被调用,但是onCreate只有创建的时候才会被调用。

这里写图片描述

其实到这来一个Activity的启动流程就已经结束了,但是我们顺便来看下handleResumeActivity的工作吧

这里写图片描述

看这来,从mActivities中根据token获取了ActivityClientRecord,并进一步获得了里面的activity,然后执行了onResume方法,我刚想说,咦,这次调用没有通过大管家哎,然后看了一下performResume方法里面,其实还是通过Instrumentation调用的。

这里写图片描述

然后,还更新了ActivityClientRecord的相关信息等。

其实到这里onResume已经调用完成了,那么handleResumeActivity后面的这一堆在干什么呢?

这里写图片描述

通过方法名我们知道,Activity在onResume之后才开始处理显示的逻辑,这里就是通知AMS,Activity onResume已经调用完了,接下来要显示了,那么AMS就会通知WindowManger来显示Activity,这就是另外一件事了,我们在这里就不细细讨论了。

呼~终于写完了,整个流程主线还是很清楚的,AMS和AMN的分工明确。

整个流程总结一下,是下面这种关系

这里写图片描述

至于前面提出的三个问题,这篇文章太长了,不准备在这里说了,我会单独拉一篇文章来讲的

最后还是广告时间,如果喜欢这篇文章,可以关注我的博客http://zwgeek.com

作者:zgzczzw 发表于2016/10/26 16:52:03 原文链接
阅读:37 评论:0 查看评论

[gitbook] Android框架分析系列之Android Binder详解

$
0
0

请支持作者原创:

https://mr-cao.gitbooks.io/android/content/android-binder.html


Android Binder详解

本文将对Android的binder机制做一个较为深入的分析,除了讲解binder实现的细节,还会讲解binder通信中的基础原理,以及创建binder service的注意事项。本文的代码分析基于Android4.2.2。

1. binder简介

在我刚刚学习binder的时候,对于binder非常的困惑,现在想起来困惑的原因还是因为对于IPC的不了解。在学习binder之前,最好是对IPC有个基本的了解。IPC是Inter-process communication的缩写,即进程间通信。IPC是一种允许进程间互相通信交换数据的机制。在Linux平台上,进程之间是隔离的,各个进程运行在自己的虚拟地址空间中,如果不采取IPC手段,进程之间是不能互相交换数据的。为了实现进程之间的数据交换,Linux提供了多种IPC机制

  • 信号

  • 管道

  • Socket

  • 消息队列

  • 信号量

  • 共享内存

Android是基于Linux系统开发,除了上面的IPC机制以外,Android又提供了一种新的选择:binder。本文不打算探究这几种机制之间的差异以及优劣,我的主要关注点在binder的实现上。binder的实现采取了面向对象的编程思想,Android提供了大量的帮助类,通过使用这些帮助类,binder程序开发人员基本不用关心数据是如何在进程之间传递的,而是集中精力设计好顶层服务接口,按照规范实现好proxy和service类就可以很方便的扩展一个本地服务。站在binder开发人员的角度来讲,一个binder的实现包括以下三个方面:

  • 顶层服务接口类的定义,此类中声明了一系列的纯虚函数作为公共的服务接口。类的头文件名一般为IXXXService.h,服务接口类命令为IXXXService,XXX为服务模块名。比如Android系统提供的多媒体服务的接口类为:IMediaPlayerService,其头文件名为IMediaPlayerService.h。

  • proxy端的实现。

  • service端的实现。proxy是相对于service而言,proxy和service都间接继承于IXXXService顶层服务接口类,他们都实现了IXXXService中声明的虚函数接口,所以从外观上看,是没什么区别的。对于用户来说,只需要持有一个IXXXService的指针,就可以调用其服务函数,不用关心这个指针究竟是指向哪个子类的具体实现。proxy和service内部对于同一个函数的实现是有差异的,在proxy端的实现中,是将函数的参数打包进容器,然后透过Android提供的binder通信机制传递给service端,service端的实现是从这个容器中读取出对应的参数,然后调用相应的实现函数。从这个角度来说,proxy只是一个空的壳子,它并没有做实际的工作,而是把做实际工作需要的条件打包好,传递给service,由service来完成具体工作。

下面的图简单的描述了顶层服务接口和proxy代理类,与service服务类之间的关系。IXXXService是一个顶层服务接口类,它声明了一个doAction的方法,其子类proxy和service分别实现了这个方法。但是proxy是将doAction方法参数打包,发送给service,由service负责执行。 

binder驱动是通信的媒介,为通信的进程在驱动层分配buffer,将用户层的参数buffer复制到驱动层buffer,完成数据的交换。下图描述了这个过程: 

站在系统角度来说,binder的实现包括:

  • 一个client进程

  • 一个service进程

  • binder驱动

其中,提供服务接口的进程为service进程,使用服务的为client进程。binder在这两者之间充当通信的媒介,所有的通信数据都是经过bidner传递到对方的用户空间。下面是一个简单的图例,描述了一次同步binder调用的过程: 

proxy调用service的服务,称之为一次binder调用。binder调用有两种形式:

  • 同步调用

  • 异步调用

所谓同步调用就是proxy发送完数据给媒介binder driver之后,开始等待的状态。直到service端处理完本次调用,通过binder driver返回了处理结果。异步调用就简单了,proxy直接发送完数据给媒介binder driver之后就返回了,不用等到service的处理结果。

综上,binder是Android提供的一种IPC通信机制,方便进程之间交换数据。binder的实现包括一个公共的顶层服务接口,同时实现了这个公共顶层接口的proxy代理端和service端。binder driver充当通信媒介。

2. binder的实现

上一个小节中,提纲挈领的介绍了bidner的基础信息,本章主要从代码的角度来分析binder的实现。Android给用户提供了一个共享库:libbinder.so,这个库提供了一系列的接口类和帮助函数,借助这些工具类,我们可以很方便的实现自己的binder service。代码的路径:

frameworks/native/libs/binder

首先我们来看一张”全家福“: 

上面这张图中比较清晰的描述了binder库中提供的类之间的关系。在面向对象编程中,一个对象的行为在它的基类中就定义好了,所以我们首先对上图中两个顶层基类IBinder和IInterface做个简单的介绍,以达到纲举目张的目的。

2.1. IBinder类简介

IBinder是负责binder通信机制的基类,它有两个子类——BpBinder和BBinder。BpBinder代表着proxy,而BBinder代表着service。IBinder中有一个函数:

virtual status_t transact( uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);

这是个纯虚函数,它肩负着binder数据传输的重任,从函数的名字就可以看出其重要性,子类Bpbinder和BBinder负责它的具体实现。BpBinder的此函数负责传输数据,而BBinder的此函数是负责接收数据。关于此函数的参数的解释如下:

  • code:函数码值,每一个服务接口类中声明的虚函数都对应一个码值,proxy端通过将码值传递给service端,从而告知service端请求执行的函数。

  • data:Parcel是Android中的容器类,用于装载数据。每个Parcel都对应一块内存buffer,用于存放数据。data中保存的是执行函数所需要的参数,proxy端把数据打包传递到service端,service端按照顺序读取参数,然后传递给对应的函数去执行。

  • reply:指向Parcel对象的指针,和data不同的是,它是用来装载service执行函数后返回的结果。 proxy可以从此Parcel中读取service的返回值,比如service的函数执行完毕之后返回一个int值,那么就可以调用reply->readInt32()获得这个int值。

  • flags:表示函数是同步调用还是异步调用。默认是同步调用,如果需要异步调用,flags会被赋值为IBinder::FLAG_ONEWAY。同步调用是阻塞的,必须等待service执行完毕返回执行结果之后proxy的执行流才得以继续,否则执行函数调用的线程就一直处于wait状态。

IBinder中有这么两个虚函数:

virtual BBinder* localBinder();
virtual BpBinder* remoteBinder();

默认的实现都是返回NULL。在BBinder中实现了localBinder:

BBinder* BBinder::localBinder()
{
    return this;
}

在BpBinder中实现了remoteBinder:

BpBinder* BpBinder::remoteBinder()
{
    return this;
}

所以,如果要区分一个IBinder对象是local binder还是remote binder,那么调用IBinder对象的上述两个函数,对结果进行check就可以知道了。如果localBinder返回非空,那么就是一个local binder,如果remoteBinder返回非空,那么就是一个remote binder。在binder通信中,究竟什么是local binder,什么是remote binder呢?首先,继承自IBinder类的对象,都是binder对象。BBinder因为生存在服务进程中,所以称之为local binder,而BpBinder所对应的实体在另外一个进程中,所以称之为remote binder。BpBinder和BBinder对应关系可以参见下图: 

进程Process_1中有一个BpBinder A,它对应进程Process_2中的BBinder A。相对于Process_1来说,BpBinder A就是remote binder;相对于Process_2来说,BBinder A就是local binder。

同时,进程Process_2中有一个BpBinder B,它对应进程Process_1中的BBinder B。相对于Process_2来说,BpBinder B就是个remote binder,相对于Process_1来说,BBinder B是一个local binder。

从上图我们还可以得知,Process_1调用BpBinder将打包好的函数参数发送到binder driver中Process_2对应的buffer,BBinder A从这块buffer中读取数据,然后进行处理;同理,Process_2调用BpBinder B将打包好的函数参数发送到binder driver中Process_1对应的buffer,BBinder B从这块buffer中读取数据,然后进行处理。一次binder调用从用户空间到kernel空间,数据copy的次数为1。

IBinder类另一个重要的函数:

virtual status_t linkToDeath(const sp<DeathRecipient>& recipient, void* cookie = NULL, uint32_t flags = 0) = 0;

这个函数是用来为BBinder注册死亡通知的。在客户端进程中,持有一个BpBinder,它对应着服务进程中的某个BBinder。如果服务进程crash了,那么BBinder也就不存在了,BpBinder就无法再和BBinder通信了。因为BBinder的死亡客户端是没法主动知道的,所以需要注册个死亡通知:当BBinder不存在了,死亡通知就会被派发,以便客户端进程能做一些善后的工作。这个函数只在BpBinder中实现了——很显然,BBinder不需要为自己注册死亡通知。 DeathRecipient是IBinder的一个内部类,它有一个纯虚函数的方法,需要用户自己去实现:

class DeathRecipient : public virtual RefBase {
public:
    virtual void binderDied(const wp<IBinder>& who) = 0;
};

2.2. IInterface类简介

从抽象的角度来将,基类IBinder实现的是通信数据的传输。这些通信数据来自于顶层服务接口类,所以还需要为服务接口类IXXXService定义一个基类——IInterface。每一个服务接口类IXXXService都需要继承IInterface,IInterface.h中定义了一些和服务相关的变量和函数。 首先看看IInterface的定义:

class IInterface : public virtual RefBase{
public:
    IInterface();
    sp<IBinder> asBinder();
    sp<const IBinder> asBinder() const;

protected:
    virtual ~IInterface();
    virtual IBinder* onAsBinder() = 0;
};

IInterface类相对于IBinder而言,简单了许多。值得关注的是函数asBinder,它返回了一个IBinder的指针。asBinder函数内部实际上是调用的虚函数virtual IBinder* onAsBinder() = 0,由之前的“全家福”可知,这个纯虚函数是由子类BpInterface和BnInterface实现。由此可知,可以从一个IInterfcae获得一个IBinder对象。下面看看两个模板类的不同实现:

template<typename INTERFACE>
IBinder* BnInterface<INTERFACE>::onAsBinder()
{
    return this;
}

template<typename INTERFACE>
inline IBinder* BpInterface<INTERFACE>::onAsBinder()
{
    return remote();
}

BnInterfce的onAsBinder函数是直接返回的this指针,因为BnInterfce是由IBinder继承而来;BpInterface的onAsBinder函数调用基类BpRefBase的remote函数,返回BpRefBase内部的mRemote指针,这个指针指向的是IBinder对象,后面我们会将到这个对象实际上是一个BpBinder对象。

有没有想过,为什么需要从一个IInterfce类中获得一个IBinder对象?答案很简单,只有IBinder对象是可以跨进程传递的。如果我们要把一个IBinder对象从一个进程传递到另外一个进程,那么就首先要获得它的IBinder指针。binder驱动不仅仅支持常规的数据类型传递,具有特色的是还支持binder对象和文件句柄的传递。传输一个IBinder对象,也分两种情况,一种是传输的是BBinder,另一种传输的是BpBinder。这两种情况还是有差异的,后面我们也会讲到。

IInterface.h文件中还定义了两个非常重要的宏:

#define DECLARE_META_INTERFACE(INTERFACE)                               \
    static const android::String16 descriptor;                          \
    static android::sp<I##INTERFACE> asInterface(                       \
            const android::sp<android::IBinder>& obj);                  \
    virtual const android::String16& getInterfaceDescriptor() const;    \
    I##INTERFACE();                                                     \
    virtual ~I##INTERFACE();                                            \


#define IMPLEMENT_META_INTERFACE(INTERFACE, NAME)                       \
    const android::String16 I##INTERFACE::descriptor(NAME);             \
    const android::String16&                                            \
            I##INTERFACE::getInterfaceDescriptor() const {              \
        return I##INTERFACE::descriptor;                                \
    }                                                                   \
    android::sp<I##INTERFACE> I##INTERFACE::asInterface(                \
            const android::sp<android::IBinder>& obj)                   \
    {                                                                   \
        android::sp<I##INTERFACE> intr;                                 \
        if (obj != NULL) {                                              \
            intr = static_cast<I##INTERFACE*>(                          \
                obj->queryLocalInterface(                               \
                        I##INTERFACE::descriptor).get());               \
            if (intr == NULL) {                                         \
                intr = new Bp##INTERFACE(obj);                          \
            }                                                           \
        }                                                               \
        return intr;                                                    \
    }                                                                   \
    I##INTERFACE::I##INTERFACE() { }                                    \
    I##INTERFACE::~I##INTERFACE() { }                                   \

当我第一次阅读binder代码,看到上面这两个宏的时候,确实是“懵”了。其实上面代码的逻辑还是很简单的,只是因为使用了宏定义所以显的比较复杂而已。DECLARE_META_INTERFACE就干了四件事情:

  • 为IInterface的子类IXXXXService声明了一个描述符,这个描述符用来唯一描述IXXXService。同时这个描述符在binder通信过程中,还充当了”校验码“码的功能,当service端收到数据后,首先就要从数据中读取一个描述符,验证描述符是否匹配自身的描述符。如果是,证明数据有效,否则就是个非法的数据包,不予处理。

  • 声明了函数asInterface,从一个IBinder对象获得一个IXXXService对象。

  • 声明函数getInterfaceDesCriptor用来获取声明的字符串描述符。

  • 声明IXXXService的构造函数和析构函数。

IMPLEMENT_META_INTERFACE宏展开之后,就是对DECLARE_META_INTERFACE宏声明的变量的定义和函数的实现了。我们只关注asInterface这个函数的实现,这里我们以Android多媒体服务的接口类IMediaPlayerService为例替换INTERFACE为MediaPlayerService,来看看这个函数的实现:

android::sp<IMediaPlayerService> IMediaPlayerService::asInterface(const android::sp<android::IBinder>& obj)
{
    android::sp<IMediaPlayerService> intr;
    if (obj != NULL) {
        intr = static_cast<IMediaPlayerService>(
            obj->queryLocalInterface(IMediaPlayerService::descriptor).get());

        if (intr == NULL) {
            intr = new BpMediaPlayerService(obj);
        }
    }

    return intr;
}

既然提到了MediaPlayerService,那就上一张IMediaPlayerService的类图,这样也方便接下来的代码分析:

IMediaPlayerService::asInterface这个函数首先会调用IBinder的queryLocalInterface函数检查IBinder的子类是否是一个本地service接口。在IBinder中提供了默认的实现:

sp<IInterface>  IBinder::queryLocalInterface(const String16& descriptor)
{
    return NULL;
}

IBinder有两个子类:BpBinder和BBinder,BpBinder并没有对这个函数进行了重写,沿用了基类的默认实现方式。在BBinder的子类BnInterface中对这个函数进行了重写,在函数实现体中,如果检查传递的描述符字符串和自身的描述符相同,就返回this指针。

template<typename INTERFACE>
inline sp<IInterface> BnInterface<INTERFACE>::queryLocalInterface(
        const String16& _descriptor)
{
    if (_descriptor == INTERFACE::descriptor) return this;
    return NULL;
}

所以对于IMediaPlayerService::asInterface(const android::sp<android::IBinder>& obj)函数,如果形参IBinder引用obj指向的是一个BpBinder对象,那么obj→queryLocalInterface函数就返回NULL,需要以obj为参数构造一个BpMediaPlayerService对象(继承自IMediaPlayerService);如果形参IBinder引用obj指向的是一个BBinder对象,返回的就是this指针。因为BnInterface<IMediaPlayerService>作为BBinder的子类的同时也是IMediaPlayerService的子类。

DECLARE_META_INTERFACE费劲心思声明的asInterface函数的作用是非常重要的,它可以直接从IBinder构造一个IMediaPlayerService对象,从而屏蔽了这个对象究竟是本地service对象,还是一个remote代理对象。用户直接使用IMediaPlayerService接口的指针就可以调用具体的函数实现,丝毫不用关心底层的实现细节。

DECLARE_META_INTERFACE和IMPLEMENT_META_INTERFACE宏要结合起来使用,DECLARE_META_INTERFACE在IXXXService.h的类定义体中使用;而IMPLEMENT_META_INTERFACE宏在IXXXService.cpp中使用。

IInterface.h中还定义了一个帮助函数,封装了上边讲到的asInterface。

template<typename INTERFACE>
inline sp<INTERFACE> interface_cast(const sp<IBinder>& obj)
{
    return INTERFACE::asInterface(obj);
}

比如调用interface_cast<IMediaPlayerService>(obj)就可以获得一个IMediaPlayerService指针。这里留一个悬念:interface_cast形参obj从哪里获得的呢?之后的章节会讲到。

2.3. BpBinder和BBinder简介

之前的章节已经简单的介绍了BpBinder和BBinder,我们已经知道BpBinder负责传输数据,BBinder负责接收数据和处理数据。这一小节,会深入的分析BpBinder和BBinder的关系。

在BpBinder类的内部有一个成员变量mHandle,它是一个int型变量。这个变量所代表的含义是什么呢?对于每一个经过binder driver传输的BBinder对象,binder driver都会在驱动层为它构建一个binder_node数据结构;同时为这个binder_node生成一个“引用”:binder_ref,每一个binder_ref都有一个int型的描述符。BpBinder的成员变量mHandle的值就是bidner_ref中的int型描述符,这样就建立起了用户层的Bpbinder和一个驱动空间的binder_ref数据结构的对应关系。通过 “BpBinder handle→binder_ref→binder_node→BBinder”这样的匹配关系,就可以建立一个Bpbinder对应一个BBinder的关系。 下面这张图描述了Handle和binder_ref以及binder_node的关系: 

一个binder_node可能有很多个binder_ref引用它,但是一个客户进程内,对于同一个binder_node的引用只会存在一份——也就是说。也就是说,如果进程A中有一个BBinder service,进程B持有多个BpXXXService的代理类,但是这些代理类都对应同一个BpBinder对象。这点其实在我们的“全家福”里面已经体现的很清楚了。BpRefBase和BpBinder的关系不是继承关系,而是一个聚合关系。这点其实很好理解,对同一个BBinder维持多个BpBinder是一件很浪费空间的事情。另外,BpBinder的数目也影响着BBinder的生命周期,同一个BBinder使用同一个BpBinder也简化了生命周期的管理。

上面我们讲到了通过binder的传输的BBinder,驱动都会为之建立一个binder_node数据结构。那么一个BBinder是如何由一个进程传递到另一个进程,进而得到一个匹配的BpBinder的呢?这就不得不讲到Parcel了。

2.3.1. binder传输容器Parcel

Parcel是Android提供的一个容器类,通过binder传输的数据都可以写进Parcel中。但是因为Parcel中的数据最终都会copy到驱动空间,而驱动空间为每一个进程分配的内存是有效的,如果Parcel中的数据量太大就会造成数据传输失败。所以,binder通信中不适宜用Parcel传输诸如图片这样的数据,如果有需求传输大容量数据,可以使用共享内存IMemory。这个我们会在本文的最后讲解。

Parcel中不仅可以装载int,float,string这样的基础数据类型,还可以装载IBinder对象,文件句柄等特俗对象。现在我们就来看看Parcel是如何装载IBinder对象。首先看一张图: 

对图中几个数据结构做下解释:

  • binder_write_read:用户层传递数据给binder驱动使用的是ioctl的方式(ioctl(fd,cmd,arg)),当cmd为BINDER_WRITE_READ的时候,arg对应的就是binder_write_read结构体。除了binder_write_read以外还有几个其他的命令,但是用的最频繁的就是binder_write_read。

  • binder_transaction_data:BINDER_WRITE_READ命令的数据也是以:命令+数据的格式存放。当cmd为BC_TRANSACTION的时候,对应的数据结构是binder_transaction_data。

  • flat_binder_object:Parcel中存放的binder对象,以此数据结构来表征。

每一个Parcel都对应一块内存buffer,Parcel内部有三个重要的变量:mData,mObjects,mObjectsSize。

  • mData:指向数据buffer,调用write接口写入的数据都依次存放在这块buffer中。

  • mObjects:指向一个动态分配的一维数组,这个数组的大小可以动态的扩展。里面存放的是mData的下标值。当数据buffer中写入了binder对象,就会在mObjects中存放一条下标记录,表示在数据buffer的某个地方存放的是一个binder对象。

  • mObjectsSize:mObjects数组的大小。

上图中的Parcel buffer中写入了一个IXXXService的描述符,接着是正常的参数参数,比如int之类的数据,紧接着写入了三个binder对象。第一个是BBinder,第二个是BpBinder,第三个是一个文件句柄。他们共同以flat_binder_object数据结构来表示,只是数据结构中的值不一样。当我们要传递一个IBinder到另外一个进程的时候,先要调用Parcel的writeStrongBinder函数将此IBinder对象写入到Parcel中:

status_t Parcel::writeStrongBinder(const sp<IBinder>& val)
{
    return flatten_binder(ProcessState::self(), val, this);
}

status_t flatten_binder(const sp<ProcessState>& proc,
    const sp<IBinder>& binder, Parcel* out)
{
    flat_binder_object obj;

    obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
    if (binder != NULL) {
        IBinder *local = binder->localBinder();
        if (!local) {
            BpBinder *proxy = binder->remoteBinder();
            if (proxy == NULL) {
                ALOGE("null proxy");
            }
            const int32_t handle = proxy ? proxy->handle() : 0;
            obj.type = BINDER_TYPE_HANDLE;
            obj.handle = handle;
            obj.cookie = NULL;
        } else {
            obj.type = BINDER_TYPE_BINDER;
            obj.binder = local->getWeakRefs();
            obj.cookie = local;
        }
    } else {
        obj.type = BINDER_TYPE_BINDER;
        obj.binder = NULL;
        obj.cookie = NULL;
    }

    return finish_flatten_binder(binder, obj, out);
}

因为IBinder的引用可能是指向BpBinder,也可能指向BBinder,所以在上述的代码中首先是通过IBinder的localBinder试图获取一个本地binder对象,如果不为空,则表示传递的是一个BBinder对象。如果为空则表示传递的是一个BpBinder。如果传递的是一个BBinder,则需要在驱动层生成binder_ref数据结构,同时把BBinder在用户空间所对应的地址保存在驱动层中。

在通过BpXXXService调用函数接口的时候,实际上是把参数写入到Parcel中,然后由Parcel构造一个bidner_transaction_data数据结构。然后将BC_TRANSACTION命令和binder_transaction_data写入Parcel,在由此Parcel构造最终的binder_write_read数据结构,通过ioctl将此数据结构传递给驱动。

2.3.2. binder的传输

从上小节的图中可以得知,IBinder对象是以flat_binder_object结构体在binder驱动中传递。binder驱动通过将用户空间的Parcel所对应的buffer和mObjects所对应的buffer拷贝到目标进程的驱动内存空间,然后通过mObjects数组的偏移量在数据buffer中找到对应的flat_binder_object对象,根据type的不同创建binder_noede或者是binder_ref数据结构予以描述binder对象。

对于BBinder来说,驱动会为之创建一个binder_node数据结构,同时创建的还有binder_ref数据结构。当传递BBinder的时候,flat_binder_object的type初始值是BINDER_TYPD_BINDER,当binder驱动将此数据结构拷贝到目标进程的内存空间的时候,将type替换为binder_type_handle,并且把handle赋值为binder_ref的int描述符。

用户空间通过从Parcel中读取出对应的flat_binder_object数据结构,然后创建一个BpBinder对象。

sp<IBinder> Parcel::readStrongBinder() const{ sp<IBinder> val; unflatten_binder(ProcessState::self(), *this, &val); return val;}

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->type) {
            case BINDER_TYPE_BINDER:
                *out = static_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(NULL, *flat, in);
            case BINDER_TYPE_HANDLE:
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }
    }
    return BAD_TYPE;
}

sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);

    handle_entry* e = lookupHandleLocked(handle);

    if (e != NULL) {
        // We need to create a new BpBinder if there isn't currently one, OR we
        // are unable to acquire a weak reference on this current one.  See comment
        // in getWeakProxyForHandle() for more info about this.
        IBinder* b = e->binder;
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
            b = new BpBinder(handle);
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            // This little bit of nastyness is to allow us to add a primary
            // reference to the remote proxy when this team doesn't have one
            // but another team is sending the handle to us.
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }

    return result;
}
  • 驱动通过将数据buffer 拷贝到目标进程的内存空间,用户空间以此buffer构造一个Parcel对象。所以才可以通过readStrongBinder来获取其他进程写入的binder对象。

  • unflatten_binder:当type是BINDER_TYPE_HANDLE的时候,以handle为参数调用ProcessState::getStrongProxyForHandle苟赞一个BpBinder。

  • ProcessState::getStrongProxyForHandle函数首先会检查此handle所对应的BpBinder是否已经创建过,如果已经创建,则返回保存的BpBinder指针;如果没有创建就重新构造一份BpBinder,并且保存在Vector中。因为在进程内ProcessState是单例的,所以对于同一个handle,整个系统只会存在一个BpBinder。

由此就完成了BBinder的传输:发送进程的BBinder,到了目标进程之后,将会得到一个BpBinder。同时驱动也会创建对应的数据结构:binder_node和binder_ref。

2.3.3. BBinder生命周期

Android对于c++ 对象的生命周期的管理是通过sp指针来管理的。一般对象的生命周期只受此对象的强引用计数的影响。当对象的强引用计数为0的时候,就会调用对象的析构函数释放对象的内存。

当我们通过要将一个BBinder传递到驱动的时候,首先要把这个BBinder对象写入到Parcel。Parcel提供了两个函数才管理BBinder对象的引用计数:

void acquire_object(const sp<ProcessState>& proc, const flat_binder_object& obj, const void* who);

void release_object(const sp<ProcessState>& proc, const flat_binder_object& obj, const void* who);

当写入BBinder到一个Parcel的时候,会调用acquire_object来增加BBinder的强引用计数,这样确保在写入驱动的过程中,用户空间的BBinder不会被析构。

当用户层和驱动的一次会话结束的时候,Parcel容器会被析构,在Parcel的析构函数中会调用release_object将对象强引用计数减1,然后释放掉自己所分配的buffer。

在驱动层检测到用户传输下来的数据中有BBinder对象,会使用命令BR_INCREFS和BR_ACQUIRE通知用户空间增加BBinder对象的弱引用计数和强引用计数。在驱动层binder_node保存着BBinder对象在用户空间的地址以及管理其在驱动层的计数关系。binder_ref相当于对binder_node引用,每增加一个binder_ref,binder_node的internal_strong_refs变量都会加1.每减少一个binder_ref的时候,binder_node的internal_strong_refs都会减1.当此变量为0的时候,就会以BR_RELEASE和BR_DECREFS通知BBinder对象,减少在用户空间的计数,如果BBinder的强引用计数减少为0就会导致BBinder的析构函数的执行。binder_ref的存在受BpBinder的影响,一个BpBinder对应着唯一的一个binder_ref,当BpBinder在用户空间的强引用计数变为0 的时候,binder_ref的引用计数也会变为0,导致binder_ref被删除。

关于binder_node和binder_ref的引用计数关系是可以从sys系统中查看:

cat sys/kernel/debug/binder/proc/PID

这里以一张传递BBinder过程中,binder_node,binder_ref计数变化的时序图结束本章的讲解: 

2.4. ProcessState和IPCThreadState简介

在每一个支持binder通信的进程的代码中,必然会有这么一句代码:

sp<ProcessState> proc(ProcessState::self());

上述代码创建了一个单例对象:ProcessState。这个对象在自己的构造函数中打开binder驱动文件:/dev/binder,获得一个文件句柄。以后和驱动的交互都是通过ioctl操作这个文件句柄来完成。同时在构造函数中,还使用mmap建立一块和驱动共享的内存。正是因为每个进程和驱动都有一块共享的内存,所以binder通信的数据拷贝次数才会为1.

此外ProcessState还有两个重要的任务:

  • 保存着全进程内所有的BpBinder对象

  • 扩展binder线程

一般情况下,我们在进程的入口函数main中,只会开辟两个线程用于binder通信,当服务进程同时处理多个请求的时候,binder线程可能会不够用,这个时候binder驱动会通知进程扩展binder线程。扩展线程的工作是由函数: void ProcessState::spawnPooledThread(bool isMain)来完成。

如果说,ProcessState是代表着binder通信的进程,那么IPCThreadState就代表着binder通信的线程。每一个使用binder通信机制的线程,都有一个线程特定的变量:IPCThreadState。这个对象用于和驱动的直接交互,整个binder通信协议的实现都是在这个类中。

IPCThreadState和驱动的通信使用ioctl(fd,cmd,data) 其中cmd有:

#define BINDER_WRITE_READ             _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT       _IOW('b', 3, int64_t)
#define BINDER_SET_MAX_THREADS        _IOW('b', 5, size_t)
#define BINDER_SET_IDLE_PRIORITY      _IOW('b', 6, int)
#define BINDER_SET_CONTEXT_MGR        _IOW('b', 7, int)
#define BINDER_THREAD_EXIT            _IOW('b', 8, int)
#define BINDER_VERSION                _IOWR('b', 9, struct binder_version)

平时客户端调用服务端的接口,使用的通信协议命令就是BINDER_WRITE_READ。有上述定义可知,每条命名都对应了不同的数据结构。每条通信命令 都对应一个唯一的整数,这个32位的整数的含义如下: 

以_IOW为例,其第一个参数对应着type,第二个参数对应这nr,第三个参数对应着size。其中_IOX,X可以是W或者是R,代表着数据传输的方向,W为1,R为2.

以上的每一条命令对应的数据包又是以cmd+数据的方式存放。具体的可以参考binder.h中的定义。

2.5. ServiceManager简介

每个binder service都应该有一个“入口地址”,这个“入口地址”,也就是handle句柄是由ServiceManager管理的。所有的客户端,如果想要调用binder service的服务,必然要先从SerivceManager处获得binder service的handle句柄,然后构造一个BpBinder,再接着以此BpBinder构造BpXXXService。而客户端和ServiceManager之间的通信也是透过binder进行,客户端必要也要知道ServiceManager的handle句柄,为了方便,Android规定ServiceManager的handle为0。

sp<IServiceManager> defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
    {
        AutoMutex _l(gDefaultServiceManagerLock);
        if (gDefaultServiceManager == NULL) {
            gDefaultServiceManager = interface_cast<IServiceManager>(
                ProcessState::self()->getContextObject(NULL));
        }
    }

    return gDefaultServiceManager;
}

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& caller)
{
    return getStrongProxyForHandle(0);
}

上述代码描述的就是IServiceManager创建的过程。通过指定handle为0,获得一个IBinder(其实是IBinder的子类BpBinder)对象,然后构造出一个IServiceManager对象(其实是其子类BpServiceManager)。用户通过IServiceManager对象来查询以及注册binder service。

下面有一张图,描述了ServiceManager和Client,Service之间的关系。 

  • Service进程调用addService函数来注册一个IBinder(BBinder),ServiceManager中保存着IBinder(BBinder)对应的handle句柄

  • Client进程通过getService函数来获得一个BBinder对应的句柄,构造出代理对象。

是不是所有的进程都可以注册binder service呢?也不是。ServiceManager中对于注册service的权限是有限制的。

static struct {
    unsigned uid;
    const char *name;
} allowed[] = {
    { AID_MEDIA, "media.audio_flingIMemoryer" },
    { AID_MEDIA, "media.player" },
    { AID_MEDIA, "media.camera" },
    { AID_MEDIA, "media.audio_policy" },
    { AID_DRM,   "drm.drmManager" },
    { AID_NFC,   "nfc" },
    { AID_BLUETOOTH, "bluetooth" },
    { AID_RADIO, "radio.phone" },
    { AID_RADIO, "radio.sms" },
    { AID_RADIO, "radio.phonesubinfo" },
    { AID_RADIO, "radio.simphonebook" },
    { AID_RADIO, "phone" },
    { AID_RADIO, "sip" },
    { AID_RADIO, "isms" },
    { AID_RADIO, "iphonesubinfo" },
    { AID_RADIO, "simphonebook" },
    { AID_MEDIA, "common_time.clock" },
    { AID_MEDIA, "common_time.config" },
};

int svc_can_register(unsigned uid, uint16_t *name)
{
    unsigned n;

    if ((uid == 0) || (uid == AID_SYSTEM))
        return 1;

    for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
        if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
            return 1;

    return 0;
}

ServiceManager在接收到Serivce进程的注册请求之后,会对Service进程的uid进程检查。检查规则如下:

  • 如果Service进程的uid为root,或者AID_SYSTEM,可以注册;

  • 如果uid在allowed数组中,并且注册的binder service的名字也和allowed数组中的字符串匹配,就允许注册;

  • 除上述两种情况以外,都不允许注册

也就是说,只有指定的uid才是可以注册binder service。

2.6. IMemory简介

因为binder驱动为每个参与通信的进程只分配了总数为1M的共享内存空间,所以在传输大容量的数据的时候,不能直接数据写入Parcel传递给binder驱动。

如果我们需要传递大容量数据,比如一张图片,这个时候就需要使用IMemory类了。IMemory象征着一块共享内存,用于在两个进程之间交换数据,我们先来看一个例子。

sp<MemoryHeapBase> heap = new MemoryHeapBase(size, 0, "MetadataRetrieverClient");
sp<IMemory> mAlbumArt = new MemoryBase(heap, 0, size);
memcpy(mAlbumArt->pointer(), albumArtata, albumArtmSize);

上面三行代码表示了IMemory最简单的用法:

  • MemoryHeapBase:代表一块堆内存。通过构造MemoryHeapBase,获得一份共享内存。其构造函数第一个参数size,指定了创建内存的大小;第二个变量是flag标记,指定为0即可;第三个是这个共享内存的名字。

  • MemoryBase:代表上述分配的内存中的一个小块内存。在需要创建多份共享内存的时候,为了避免创建MemoryHeapBase带来的开销,一般是创建一份MemoryHeapBase,然后将其分为多个小的MemoryBase。

  • 获得一个IMemory对象之后,就可以同通过其pointer()方法获得其指针地址,然后往这个指针所指向的地址写入数据。

在写入数据完毕之后,可以把这个IMemory写入到Parcel传递到client进程。client就可以采取相反的动作,从这个IMemory中读取数据。

为什么IMemory可以跨进程传递?因为它是基于binder实现,下面是它的类图关系: 

从图中可以发现,绿色部分标记的类IMemory和IMemoryHeap是顶层接口类。其中IMemoryHeap代表分配的大内存,而IMemory代表在IMemoryHeap上分配的一小块内存。IMemoryHeap的子类MemoryHeapBase负责实现内存的分配。

IMemory和IMemoryHeap的关系可以用下图来描述: 

在客户端,就可以通过以下代码获取一个IMemory和这块内存在本进程所对应的地址和大小:

sp<IMemory> artWorkMemory = interface_cast<IMemory>(ibinder);

void *data = artWorkMemory->pointer();
int size = artWorkMemory->size();

其实关于IMemory的使用还是很简单的,第一步创建一个MemoryHeapBase,负责创建整块内存;第二步创建一个MemoryBase,从整块内存中分配一小块内存;第三步使用pointer()方法获取到MemoryBase所对应的地址,往地址中写入数据;第四步将MemoryBase的基类IBinder写入到Parcel中传递到客户端;第五步客户端从Parcel中读取一个IBinder,构造一个IMemory对象,同样调用pointer()方法获得内存地址,以及调用size()大小获得内存的大小。 \ 如果向创建多个IMemory,那就设计到如何有效的从一整块内存中分配小内存的问题了,必然要引入一个内存管理器,好在Android已经帮我们写好了帮助内。下面的代码是一个试例:

sp<MemoryDealer> memoryDealer = new MemoryDealer(length1+length2, "ArtWork");
sp<IMemory> artwokMemory = memoryDealer->allocate(length1);
sp<IMemory> artwokMemory = memoryDealer->allocate(length2);

这段代码和最初的代码不同之处在于IMemory的分配不在由我们手动分配,而是使用帮助类MemoryDealer来处理。下面是MemoryDealer的构造函数:

MemoryDealer::MemoryDealer(size_t size, const char* name)
    : mHeap(new MemoryHeapBase(size, 0, name)),
    mAllocator(new SimpleBestFitAllocator(size))
{
}

在其构造函数中,构造了一份MemoryHeapBase,同时构造了一份内存分配器:SimpleBeastFitAllocator,负责从一块大内存中分配小的内存。

上面我们提到了MemoryHeapBse负责分配一块大的共享内存,用来在两个进程之间交换数据。我们来回想下Linux上共享内存的实现方式。两个进程都打开一个文件,然后将文件映射到彼此的虚拟内存空间。但是打开文件需要知道文件的路径,同时涉及到权限等问题。在Android中提供了另外一种方式来创建共享内存:ashmem。ashmem是一个设备文件,其路径为/dev/ashmem。创建MemoryHeapBse的时候,打开这个设备,获得一份句柄fd,service端首先将此文件句柄映射到虚拟内存空间,以此来获得一份共享内存。然后通过透过binder将fd跨进程传递,客户端接收到fd之后,也将此文件句柄映射到自己的虚拟内存空间。于是两个进程映射了同一份文件句柄到彼此的虚拟内存空间,从而实现了数据的交换,而不用管理路径和权限的问题。

客户端和服务端利用ashmem交换数据示意图如下: 

服务端是在构造MemoryHeapBase对象的时候,打开/dev/ashmem,然后映射文件句柄到内存空间:

MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
    : mFD(-1), mSize(0), mBase(MAP_FAILED), mFlags(flags),
      mDevice(0), mNeedUnmap(false), mOffset(0)
{
    const size_t pagesize = getpagesize();
    size = ((size + pagesize-1) & ~(pagesize-1));
    int fd = ashmem_create_region(name == NULL ? "MemoryHeapBase" : name, size);
    ALOGE_IF(fd<0, "error creating ashmem region: %s", strerror(errno));
    if (fd >= 0) {
        if (mapfd(fd, size) == NO_ERROR) {
            if (flags & READ_ONLY) {
                ashmem_set_prot_region(fd, PROT_READ);
            }
        }
    }
}
status_t MemoryHeapBase::mapfd(int fd, size_t size, uint32_t offset)
{
    if (size == 0) {
        // try to figure out the size automatically
#ifdef HAVE_ANDROID_OS
        // first try the PMEM ioctl
        pmem_region reg;
        int err = ioctl(fd, PMEM_GET_TOTAL_SIZE, &reg);
        if (err == 0)
            size = reg.len;
#endif
        if (size == 0) { // try fstat
            struct stat sb;
            if (fstat(fd, &sb) == 0)
                size = sb.st_size;
        }
        // if it didn't work, let mmap() fail.
    }

    if ((mFlags & DONT_MAP_LOCALLY) == 0) {
        void* base = (uint8_t*)mmap(0, size,
                PROT_READ|PROT_WRITE, MAP_SHARED, fd, offset);
        if (base == MAP_FAILED) {
            ALOGE("mmap(fd=%d, size=%u) failed (%s)",
                    fd, uint32_t(size), strerror(errno));
            close(fd);
            return -errno;
        }
        //ALOGD("mmap(fd=%d, base=%p, size=%lu)", fd, base, size);
        mBase = base;
        mNeedUnmap = true;
    } else  {
        mBase = 0; // not MAP_FAILED
        mNeedUnmap = false;
    }
    mFD = fd;
    mSize = size;
    mOffset = offset;
    return NO_ERROR;
}
  • ashmem_create_region 打开/dev/ashmem

  • mapfd 映射fd到内存空间

客户端是在调用pointer的时候,映射fd到内存空间:

void* IMemory::pointer() const {
    ssize_t offset;
    sp<IMemoryHeap> heap = getMemory(&offset);
    void* const base = heap!=0 ? heap->base() : MAP_FAILED;
    if (base == MAP_FAILED)
        return 0;
    return static_cast<char*>(base) + offset;
}

pointer函数中会调用虚函数getMemory获得一个IMemoryHeap对象。其中getMemory函数由子类BpMemory来实现。之后调用IMemoryHeap的base方法,获得基地址指针。我们来看看base方法的实现:

void*   base() const  { return getBase(); }

IMemoryHeap又调用了子类的getBase实现,这下又需要看看子类BpMemoryHeap的getBase方法的实现了:

void* BpMemoryHeap::getBase() const {
    assertMapped();
    return mBase;
}
void BpMemoryHeap::assertMapped() const
{
    if (mHeapId == -1) {
        sp<IBinder> binder(const_cast<BpMemoryHeap*>(this)->asBinder());
        sp<BpMemoryHeap> heap(static_cast<BpMemoryHeap*>(find_heap(binder).get()));
        heap->assertReallyMapped();
        if (heap->mBase != MAP_FAILED) {
            Mutex::Autolock _l(mLock);
            if (mHeapId == -1) {
                mBase   = heap->mBase;
                mSize   = heap->mSize;
                mOffset = heap->mOffset;
                android_atomic_write( dup( heap->mHeapId ), &mHeapId );
            }
        } else {
            // something went wrong
            free_heap(binder);
        }
    }
}

void BpMemoryHeap::assertReallyMapped() const
{
    if (mHeapId == -1) {

        // remote call without mLock held, worse case scenario, we end up
        // calling transact() from multiple threads, but that's not a problem,
        // only mmap below must be in the critical section.

        Parcel data, reply;
        data.writeInterfaceToken(IMemoryHeap::getInterfaceDescriptor());
        status_t err = remote()->transact(HEAP_ID, data, &reply);
        int parcel_fd = reply.readFileDescriptor();
        ssize_t size = reply.readInt32();
        uint32_t flags = reply.readInt32();
        uint32_t offset = reply.readInt32();

        ALOGE_IF(err, "binder=%p transaction failed fd=%d, size=%ld, err=%d (%s)",
                asBinder().get(), parcel_fd, size, err, strerror(-err));

        int fd = dup( parcel_fd );
        ALOGE_IF(fd==-1, "cannot dup fd=%d, size=%ld, err=%d (%s)",
                parcel_fd, size, err, strerror(errno));

        int access = PROT_READ;
        if (!(flags & READ_ONLY)) {
            access |= PROT_WRITE;
        }

        Mutex::Autolock _l(mLock);
        if (mHeapId == -1) {
            mRealHeap = true;
            mBase = mmap(0, size, access, MAP_SHARED, fd, offset);
            if (mBase == MAP_FAILED) {
                ALOGE("cannot map BpMemoryHeap (binder=%p), size=%ld, fd=%d (%s)",
                        asBinder().get(), size, fd, strerror(errno));
                close(fd);
            } else {
                mSize = size;
                mFlags = flags;
                mOffset = offset;
                android_atomic_write(fd, &mHeapId);
            }
        }
    }
}

上述方法的调用流程为:

IMemory::pointer() → IMemoryHeap::base() → BpMemoryHeap::getBase() →BpMemoryHeap::assertMapped() → BpMemoryHeap::assertReallyMapped(),最终在assertReallyMapped方法中获取到MemoryHeapBase映射文件所对应的句柄fd,然后将此fd映射到客户端的虚拟地址空间。

到此,服务端和客户端都映射了同一份/dev/ashmem文件到自己的虚拟地址空间,他们可以愉快的进行跨进程的大容量数据交换了。




PDF下载:点击打开链接

作者:sky_pjf 发表于2016/10/26 17:30:26 原文链接
阅读:27 评论:0 查看评论

cocos2d-X:RPG遥感控制英雄

$
0
0
RPG游戏的遥控杆控制主角
先看代码:(参考赵云要格斗)
#ifndef __HROCKER_H__
#define __HROCKER_H__  
#include "cocos2d.h"  
using namespace cocos2d;

//用于标识摇杆与摇杆的背景  
typedef enum{
	tag_rocker,
	tag_rockerBG,
}tagForHRocker;
//用于标识摇杆方向  
typedef enum{
	rocker_stay,
	rocker_right,
	rocker_up,
	rocker_left,
	rocker_down,
}tagDirecton;
class HRocker :public Layer
{
public:
	HRocker(void);
	~HRocker(void);

	//创建摇杆(摇杆的操作题图片资源名,摇杆背景图片资源名,起始坐标)  
	static HRocker* createHRocker(const char *rockerImageName, const char *rockerBGImageName, Point position);
	//启动摇杆(显示摇杆、监听摇杆触屏事件)  
	void startRocker(bool _isStopOther);
	//停止摇杆(隐藏摇杆,取消摇杆的触屏监听)  
	void stopRocker();
	//判断控制杆方向,用来判断精灵上、下、左、右运动  
	int rocketDirection;
	//当前人物行走方向,用来判断精灵的朝向,精灵脸朝右还是朝左  
	bool rocketRun;
	CREATE_FUNC(HRocker);
private:
	//自定义初始化函数  
	void rockerInit(const char* rockerImageName, const char* rockerBGImageName, Point position);
	//是否可操作摇杆  
	bool isCanMove;
	//获取当前摇杆与用户触屏点的角度  
	float getRad(Point pos1, Point pos2);
	//摇杆背景的坐标  
	Point rockerBGPosition;
	//摇杆背景的半径  
	float rockerBGR;
	//触屏事件  
	virtual bool onTouchBegan(Touch *Touch, Event *Event);
	virtual void onTouchMoved(Touch *Touch, Event *Event);
	virtual void onTouchEnded(Touch *Touch, Event *Event);
};
#endif</span>
接下来是 遥控杆.cpp:
#include "HRocker.h"
#define PI 3.1415926535898

HRocker::HRocker(void)
{
	rocketRun = false;
}

HRocker::~HRocker(void)
{
}

//创建摇杆(摇杆的操作题图片资源名,摇杆背景图片资源名,起始坐标)
HRocker* HRocker::createHRocker(const char *rockerImageName, const char *rockerBGImageName, Point position)
{
	HRocker *layer = HRocker::create();
	if (layer)
	{
		layer->rockerInit(rockerImageName, rockerBGImageName, position);
		return layer;
	}
	CC_SAFE_DELETE(layer);
	return NULL;
}

//自定义初始化函数
void HRocker::rockerInit(const char* rockerImageName, const char* rockerBGImageName, Point position)
{
	Sprite *spRockerBG = Sprite::create(rockerBGImageName);
	spRockerBG->setPosition(position);
	spRockerBG->setVisible(false);
	addChild(spRockerBG, 0, tag_rockerBG);

	Sprite *spRocker = Sprite::create(rockerImageName);
	spRocker->setPosition(position);
	spRocker->setVisible(false);
	addChild(spRocker, 1, tag_rocker);

	rockerBGPosition = position;
	rockerBGR = spRockerBG->getContentSize().width*0.5;//
	rocketDirection = -1;//表示摇杆方向不变
}

//启动摇杆(显示摇杆、监听摇杆触屏事件)
void HRocker::startRocker(bool _isStopOther)
{
	Sprite *rocker = (Sprite*)this->getChildByTag(tag_rocker);
	rocker->setVisible(true);

	Sprite *rockerBG = (Sprite *)this->getChildByTag(tag_rockerBG);
	rockerBG->setVisible(true);

	auto touchListener = EventListenerTouchOneByOne::create();
	touchListener->setSwallowTouches(true);
	touchListener->onTouchBegan = CC_CALLBACK_2(HRocker::onTouchBegan, this);
	touchListener->onTouchMoved = CC_CALLBACK_2(HRocker::onTouchMoved, this);
	touchListener->onTouchEnded = CC_CALLBACK_2(HRocker::onTouchEnded, this);
	_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);

	//this->getEventDispatcher()->addEventListenerWithSceneGraphPriority(mTouchlistener, this);
	//CCDirector::sharedDirector()->getTouchDispatcher()->addTargetedDelegate(this, -1, _isStopOther);
}

//停止摇杆(隐藏摇杆,取消摇杆的触屏监听)
void HRocker::stopRocker()
{
	Sprite *rocker = (Sprite *)this->getChildByTag(tag_rocker);
	rocker->setVisible(false);

	Sprite * rockerBG = (Sprite *)this->getChildByTag(tag_rockerBG);
	rockerBG->setVisible(false);

	//CCDirector::sharedDirector()->getTouchDispatcher()->removeDelegate(this);
	//this->getEventDispatcher()->removeAllEventListeners();
	_eventDispatcher->removeEventListenersForTarget(this);
}


//获取当前摇杆与用户触屏点的角度
float HRocker::getRad(Point pos1, Point pos2)
{
	float px1 = pos1.x;
	float py1 = pos1.y;
	float px2 = pos2.x;
	float py2 = pos2.y;

	//得到两点x的距离
	float x = px2 - px1;
	//得到两点y的距离
	float y = py1 - py2;
	//算出斜边长度
	float xie = sqrt(pow(x, 2) + pow(y, 2));
	//得到这个角度的余弦值(通过三角函数中的店里:角度余弦值=斜边/斜边)
	float cosAngle = x / xie;
	//通过反余弦定理获取到期角度的弧度
	float rad = acos(cosAngle);
	//注意:当触屏的位置Y坐标<摇杆的Y坐标,我们要去反值-0~-180
	if (py2 < py1)
	{
		rad = -rad;
	}
	return rad;
}

Point getAngelePosition(float r, float angle){
	return Vec2(r*cos(angle), r*sin(angle));
}

//抬起事件
bool HRocker::onTouchBegan(Touch *Touch, Event *Event)
{
	Point point = Touch->getLocation();
	Sprite *rocker = (Sprite *)this->getChildByTag(tag_rocker);
	if (rocker->boundingBox().containsPoint(point))
		isCanMove = true;
	return true;
}
//移动事件
void HRocker::onTouchMoved(Touch *Touch, Event *Event)
{
	if (!isCanMove)
	{
		return;
	}
	Point point = Touch->getLocation();
	Sprite *rocker = (Sprite *)this->getChildByTag(tag_rocker);
	//得到摇杆与触屏点所形成的角度
	float angle = getRad(rockerBGPosition, point);
	//判断两个圆的圆心距是否大于摇杆背景的半径
	if (sqrt(pow((rockerBGPosition.x - point.x), 2) + pow((rockerBGPosition.y - point.y), 2)) >= rockerBGR)
	{

		//保证内部小圆运动的长度限制
		rocker->setPosition(ccpAdd(getAngelePosition(rockerBGR, angle), Vec2(rockerBGPosition.x, rockerBGPosition.y)));
	}
	else
		rocker->setPosition(point);
	//判断方向
	if (angle >= -PI / 4 && angle<PI / 4)
	{
		rocketDirection = rocker_right;
		rocketRun = false;
	}
	else if (angle >= PI / 4 && angle<3 * PI / 4)
	{
		rocketDirection = rocker_up;
	}
	else if ((angle >= 3 * PI / 4 && angle <= PI) || (angle >= -PI&&angle<-3 * PI / 4))
	{
		rocketDirection = rocker_left;
		rocketRun = true;
	}
	else if (angle >= -3 * PI / 4 && angle<-PI / 4)
	{
		rocketDirection = rocker_down;
	}
}
//离开事件
void HRocker::onTouchEnded(Touch *Touch, Event *Event)
{
	if (!isCanMove)
	{
		return;
	}
	Sprite *rockerBG = (Sprite*)this->getChildByTag(tag_rockerBG);
	Sprite *rocker = (Sprite*)this->getChildByTag(tag_rocker);
	rocker->stopAllActions();
	rocker->runAction(MoveTo::create(0.08f, rockerBG->getPosition()));
	isCanMove = false;
	rocketDirection = rocker_stay;
}</span>
英雄.h:
#ifndef __HERO_H__  
#define __HERO_H__  
#include "cocos2d.h"  
#include"HRocker.h"
#include "KnapsackData.h"
using namespace std;
using namespace cocos2d;

class Hero :public Layer
{
public:
	Hero();
	~Hero();

	//根据图片创建英雄  
	void InitHeroSprite(char *hero_name);
	//设置动画,num为图片数目, run_direction为精灵脸朝向, false朝右, name_each为name——png中的每一小张图片的公共名称部分  
	void SetAnimation(const char *name_plist, const char *name_png, const char *name_each, const unsigned int num, bool run_direction);
	//停止动画  
	void StopAnimation();
	//判断是否在跑动画  
	bool IsRunning;
	//英雄运动方向  
	bool HeroDirection;

	//英雄是否碰撞
	bool isCrash = false;

	//把精灵返回出去
	Sprite* getHero();

	//背包道具拥有类
	vector<KnapsackData*> knaDataVec;

	CREATE_FUNC(Hero);

private:
	Size visibleSize = Director::getInstance()->getVisibleSize();//获取舞台高度宽度
	//用来保存初始状态的精灵图片名称  
	char *Hero_name;
	//精灵  
	Sprite *m_HeroSprite;
};

#endif

英雄.cpp:
#include "Hero.h"  
USING_NS_CC;

Hero::Hero()
{
	IsRunning = false;
	HeroDirection = false;
	Hero_name = NULL;
}

Hero::~Hero()
{
}

void Hero::InitHeroSprite(char *hero_name)
{
	Hero_name = hero_name;
	this->m_HeroSprite = Sprite::create(Hero_name);
	this->addChild(m_HeroSprite);
	this->setPosition(Vec2(visibleSize.width*0.39, visibleSize.height*0.63));
	this->setContentSize(Size(40, 43));
}

//动画播放,可以是跑、攻击、死亡、受伤等  
void Hero::SetAnimation(const char *name_plist, const char *name_png, const char *name_each, const unsigned int num, bool run_direction)
{
	if (HeroDirection != run_direction)
	{
		HeroDirection = run_direction;
		m_HeroSprite->setFlippedX(run_direction);
	}
	if (IsRunning)
		return;

	//将图片加载到精灵帧缓存池  
	SpriteFrameCache *m_frameCache = SpriteFrameCache::getInstance();
	m_frameCache->addSpriteFramesWithFile(name_plist, name_png);
	//用一个列表保存所有的CCSpriteFrameCache  
	Vector<SpriteFrame*> frameArray;
	unsigned int i;
	for (i = 2; i <= num; i++)
	{
		SpriteFrame *frame = m_frameCache->getSpriteFrameByName(__String::createWithFormat("%s%d.png", name_each, i)->getCString());
		frameArray.pushBack(frame);
	}

	//使用列表创建动画对象  
	Animation *animation = Animation::createWithSpriteFrames(frameArray);
	if (HeroDirection != run_direction)
	{
		HeroDirection = run_direction;
	}
	//表示无限循环播放  
	animation->setLoops(-1);
	//每两张图片的时间隔,图片数目越少,间隔最小就越小  
	animation->setDelayPerUnit(0.1f);

	//将动画包装成一个动作  
	Animate *animate = Animate::create(animation);
	m_HeroSprite->runAction(animate);
	IsRunning = true;
}


//自己设定英雄包围盒
/*Rect Hero::getBoundingBoxSp(){
return Rect(m_HeroSprite->getPositionX(), m_HeroSprite->getPositionY(), 80, 83);
}*/

Sprite* Hero::getHero()
{

	if (m_HeroSprite)
	{
		return m_HeroSprite;
	}
	else
	{
		m_HeroSprite = Sprite::create("zhoayun.png");
		return m_HeroSprite;
	}
}

void Hero::StopAnimation()
{
	if (!IsRunning)

	{
		return;
	}
	m_HeroSprite->stopAllActions();

	//恢复精灵原来的初始化贴图  
	//把原来的精灵删除掉  
	this->removeChild(m_HeroSprite, true);

	//恢复精灵原来的贴图样子  
	m_HeroSprite = CCSprite::create(Hero_name);//恢复精灵原来的贴图样子  
	m_HeroSprite->setFlippedX(HeroDirection);
	this->addChild(m_HeroSprite);
	IsRunning = false;
}
分别在GameScene中创建层对象后:
帧计时器中加入:
switch (Hrocker->rocketDirection)
	{
	case 1:
		hero->SetAnimation("run_animation.plist", "run_animation.png", "run_", 8, Hrocker->rocketRun);
		hero->setPosition(Vec2(hero->getPosition().x + 3, hero->getPosition().y)); //向右走
		break;
	case  2:
		hero->SetAnimation("run_animation.plist", "run_animation.png", "run_", 8, Hrocker->rocketRun);
		hero->setPosition(Vec2(hero->getPosition().x, hero->getPosition().y + 3));   //向上走
		break;
	case 3:
		hero->SetAnimation("run_animation.plist", "run_animation.png", "run_", 8, Hrocker->rocketRun);
		hero->setPosition(Vec2(hero->getPosition().x - 3, hero->getPosition().y));   //向左走
		break;
	case 4:
		hero->SetAnimation("run_animation.plist", "run_animation.png", "run_", 8, Hrocker->rocketRun);
		hero->setPosition(Vec2(hero->getPosition().x, hero->getPosition().y - 3));   //向下走
		break;
	default:
		hero->StopAnimation();//停止所有动画和运动
		break;
	}

这样,遥控杆的方向就 和 英雄主角绑定了



作者:qq_35576100 发表于2016/10/26 17:33:39 原文链接
阅读:29 评论:0 查看评论

Java反射机制学习

$
0
0

一.概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

JAVA反射(放射)机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

二.Class类的使用

在面向对象的世界中万事万物皆对象,在java中除了普通数据(但有封装类)和静态类(属于类)不是对象外 其它都为对象。

那么类是对象吗?是谁的对象呢?类是对象,是java.lang.class的类。

反射机制获取类的3中方法

首先我们可以看看class的源代码,会发现这行代码


私有的构造方法,只有java虚拟机才能创建对象   所以我们不可以通过new 来创建对象

但是还有三种其它的方法可以创建对象

package reflect;

/**
 * @author scx
 *Class类的使用
 */
public class Main {
	public static void main(String[] args) {
		Fool fool=new Fool();
		//第一种方式  任何一个类都有一个隐含的静态成员变量class
		Class c1=Fool.class;
		//第二种方式  可以通过类的对象getClass方法
		Class c2=fool.getClass();
		//第三种方式  通过完整的包名和类名
		Class c3=null;
		try {
			c3=Class.forName("reflect.Fool");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		/*
		 * c1,c2,c3代表了Fool类的类类型   是class的实例对象
		 * 这个对象我们称为该类的类类型
		 */
		System.out.println("c1="+c1);
		System.out.println("c2="+c2);
		System.out.println("c3="+c3);
	}
}
//Fool  是java.lang.class的对象
class Fool{}

运行结果

c1=class reflect.Fool

c2=class reflect.Fool
      c3=class reflect.Fool


三.创建对象 newInstance

  
try {
			Fool fool2=(Fool) c1.newInstance();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}


四.动态加载类

1.什么是动态加载类 什么是静态加载类

Class.forName 不仅表示类的类类型,还代表了动态加载类。编译时加载是静态加载类,

运行时加载是动态加载类。

请大家区分编译 运行。

2.为何要使用动态加载类

我们写了一个程序 并没有写A类和B类以及start方法 

public class Main{
	public static void main(String args[]){
		if("A".equals(args[0])){
			A a=new A();
			a.start();
		}
		if("B".equals(args[0])){
			B b=new B();
			b.start();
		}
	}
}


编译:


我们会发现,我们并不一定用到A功能或B功能,可是编译却不能通过。而在日常的项目中,如果我们写了100个功能,因为一个功能的原因而导致所有功能不能使用,明显使我们不希望的。在这里,为什么会在编译时报错呢?new 是静态加载类,在编译时刻就需要加载所有可能使用到的功能。所以会报错。而在日常中我们希望用到哪个就加载哪个,不用不加载,就需要动态加载类。

使用动态加载类时,我们不用定义100种功能,只需要通过实现某种标准(实现某个接口)。

代码:

public class Main{
	public static void main(String args[]){
		try{
			
			Class c=Class.forName(args[0]);
		
			All a=(All)c.newInstance();
			a.start();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
}
class A implements All{
	public void start(){
		System.out.println("A....START");
	}
}
class B implements All{
	public void start(){
		System.out.println("B....START");
	}
}
//接口
interface All{
	public void start();
}


总结:推荐大家使用动态加载类。原因如上,即节省了代码,又简洁方便,安全。

学习java反射的时候 看到了这 理解的比较浅显  ,肯定有许多要补充的。慢慢学习 慢慢积累  欢迎评论

五.获取类的方法信息

  getMethods()方法获取的是所有的public的函数 包括父类继承
 getDeclaredMethods()方法获取的是所有该类自己声明的方法

package reflect;

import java.lang.reflect.Method;

public class ClassDemo {
	public static void main(String[] args) {
		//获取类的信息 首先获取类类型
		Class c=Book.class;
		//获取包名+类名
		System.out.println(c.getName());
		//获取类名
		System.out.println(c.getSimpleName());
		/*
		 * Method类 方法对象
		 * 一个成员就是一个Method对象
		 * getMethods()方法获取的是所有的public的函数 包括父类继承
		 * getDeclaredMethods()方法获取的是所有该类自己声明的方法
		 */
		Method []ms=c.getDeclaredMethods();
		for(Method method:ms){
			//得到方法的返回值类型的类类型
			Class returnType=method.getReturnType();
			System.out.print("返回值类型:"+returnType+"\t");
			//得到方法的名称
			System.out.print(method.getName()+"(");
			//获取参数的类类型
			Class[] paramTypes=method.getParameterTypes();
			for (Class paramType : paramTypes) {
				System.out.print(paramType.getName()+" ");
			}
			System.out.println(")");
		}
	}
}
class Book extends Rect{
	private int price;
	private String name;
	public int getPrice() {
		return price;
	}
	public void setPrice(int price) {
		this.price = price;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}
class Rect{
	private int width;
	private int height;
	public void setRect(int width,int height){
		this.width=width;
		this.height=height;
	}
}

运行结果:


当然还有获得类的成员变量  构造方法 ,实现接口,父类,访问权限,包名,等等。

只要我们记得 无论获得什么  首先要获取类的类类型 ,其它通过阅读api文档  轻而易举~


六.获取指定的方法

getDeclaredMethod(name, parameterTypes)  name为方法名  parameterTypes为参数类型数组

举个例子

package reflect;
import java.lang.reflect.Method;

public class MethodDemo1 {
	public static void main(String[] args) throws Exception {
		/*
		 * 若想获取print方法 首先获得类的信息 若想获得类的信息 首先获取类类型
		 */
		
		A a = new A();
		Class c = A.class;
		Method m1 = c.getDeclaredMethod("print", int.class, int.class);
		// 方法的反射操作 invoke方法  第一个参数为要操作的对象
		Object o = m1.invoke(a, 5, 6);// 和a1.print(5,6) 同样效果 
		Method m2=c.getDeclaredMethod("print",String.class,String.class);
		Object o2=m2.invoke(a,"Hello","World");//和a1.print(Hello,World) 同样效果
		
		Method m3=c.getDeclaredMethod("print");
		Object o3=m3.invoke(a);//和a1.print() 同样效果 
	}
}

class A {
	public void print(){
		System.out.println("A");
	}
	public void print(int a, int b) {
		System.out.println(a + b);
	}

	public void print(String a, String b) {
		System.out.println(a.toLowerCase() + b.toUpperCase());
	}
}

运行结果:

11
helloWORLD
A


作者:su20145104009 发表于2016/10/26 17:39:19 原文链接
阅读:45 评论:0 查看评论

简易实现Listview滑动删除 (通用任意view)

$
0
0

惯例图示 动画皆用原生自带。方便理解复用

本示例 简化了 outouch之间的复杂判断。运用一些自带的属性动画 做出比较圆润的效果。
分享给大家

滑动删除示例

基本图示效果。
1.拉到按钮一半 还原超过一半自动显示全部
2.显示全部点击非删除区域还原
3.点击删除下面的item上移 自身缩小Y消失方便看出变化间隙

Module下载

滑动删除下载

1.用于兼容outouch和子view点击事件的容器

这里作为你item根的容器

package com.rex;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.LinearLayout;

/**
 * Created by  Rex on 2016/10/25.
 * 能兼容子view点击事件和自身onTouch事件的容器-LinearLayout可改
 */
public class ParentOnTouchChildClickLinearLayout extends LinearLayout {


    public ParentOnTouchChildClickLinearLayout(Context context) {
        super(context);
    }

    public ParentOnTouchChildClickLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ParentOnTouchChildClickLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private int yyy = -1;
    private int xxx = -1;
    private boolean isMove = false;

    /**
     * 核心方法
     *
     * @param event
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isMove = false;
                //此处为break所以 onTouch中没有Down
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (!isMove)
                    return false;
                isMove = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (!isMove) {
                    yyy = (int) event.getRawY();
                    xxx = (int) event.getRawX();
                }
                isMove = true;
                //细节优化 短距离移除
                float moveY = event.getRawY();
                float moveX = event.getRawX();
                //如果是非点击事件就拦截 让父布局接手onTouch 否则执行子ViewOnClick
                if (Math.abs(moveY - yyy) > dip2px(getContext(), 20) || Math.abs(moveX - xxx) > dip2px(getContext(), 20)) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    return true;
                }
                break;

        }
        return false;
    }
    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

Item XML

item.xml
为方便大家拓展 删除区域和内容区域都用了容器装了起来。可能大家还会自定义这些区域 而不是一个按钮

<?xml version="1.0" encoding="utf-8"?>
<com.rex.ParentOnTouchChildClickLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                             android:id="@+id/root"
                                             android:layout_width="match_parent"
                                             android:layout_height="wrap_content"
                                             android:background="@android:color/holo_green_light"
                                             android:clipChildren="false"
                                             android:orientation="horizontal">

    <LinearLayout
        android:id="@+id/llContext"
        android:layout_width="match_parent"
        android:layout_height="90dp"
        android:layout_margin="1dp"
        android:gravity="center_vertical"
        android:orientation="horizontal">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="测试文本"
            android:textColor="#fff"
            android:textSize="18sp"/>

        <TextView
            android:id="@+id/tvTestClick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:text="子view点击兼容性测试"
            android:textSize="18sp"/>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/llDelete"
        android:layout_width="100dp"
        android:layout_height="match_parent"
        android:background="#456431">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_orange_light"
            android:gravity="center"
            android:text="删除"
            android:textSize="20sp"/>

    </LinearLayout>
</com.rex.ParentOnTouchChildClickLinearLayout>

JAVA代码

package com.rex;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.BaseAdapter;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

/**
 * 一个较容易理解
 * 多用系统方法的少bug的 横滑删除listview
 * 此方法也适用于任意view
 */
public class MainActivity extends Activity {

    private float max = 300;//你想滑动的极限长度默认  本demo以删除布局宽度为max
    private ArrayList<String> data = new ArrayList<String>() {{
        add("str01");
        add("str02");
        add("str03");
        add("str04");
        add("str05");
        add("str06");
        add("str07");
        add("str08");
    }};
    private ListView lv;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = (ListView) findViewById(R.id.lv);
        lv.setAdapter(new IAdapter());
    }

    class IAdapter extends BaseAdapter {

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object getItem(int i) {
            return null;
        }

        @Override
        public long getItemId(int i) {
            return 0;
        }

        @Override
        public View getView(final int position, View view, ViewGroup viewGroup) {
            if (view == null) {
                view = View.inflate(MainActivity.this, R.layout.item, null);
            }
            ParentOnTouchChildClickLinearLayout root = (ParentOnTouchChildClickLinearLayout) view.findViewById(R.id.root);
            TextView tvTestClick = (TextView) view.findViewById(R.id.tvTestClick);
            final LinearLayout llContext = (LinearLayout) view.findViewById(R.id.llContext);
            final LinearLayout llDelete = (LinearLayout) view.findViewById(R.id.llDelete);

            tvTestClick.setText(data.get(position));
            ViewTreeObserver vto = llDelete.getViewTreeObserver();
            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {

                    //监听一次马上结束

                    if (Build.VERSION.SDK_INT < 16) {
                        llDelete.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                    } else {
                        llDelete.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    }
                    max = llDelete.getWidth();
                    //得到删除按钮长度 得到最大拖动限定
                    Log.i("rex", "max--" + max);

                }
            });


            llContext.setTranslationX(0);
            llDelete.setTranslationX(0);
            view.setScaleY(1);
            view.setTranslationY(0);

            final View finalView = view;
            llDelete.setOnClickListener(new View.OnClickListener() {
                @Override

                public void onClick(View v) {
                    //删除
                    ObjectAnimator scaleY = ObjectAnimator.ofFloat(finalView, "scaleY", 1, 0);
                    scaleY.addListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            super.onAnimationEnd(animation);
                            data.remove(position);
                            IAdapter.this.notifyDataSetChanged();
                        }
                    });
                    scaleY.setDuration(800).start();
                    for (int i = 1; i < lv.getChildCount() - position; i++) {
                        ObjectAnimator.ofFloat(lv.getChildAt(i + position), "translationY", 0, -finalView.getMeasuredHeight()).setDuration(800).start();
                    }


                }
            });
            tvTestClick.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity.this, "测试按钮被调用!", Toast.LENGTH_SHORT).show();
                }
            });


            llContext.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View v) {
                    Toast.makeText(MainActivity.this, "item 长按被调用!", Toast.LENGTH_SHORT).show();
                    return true;
                }
            });


            //点击内容让item回到最初的位置
            llContext.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //点击归位
                    ObjectAnimator.ofFloat(llContext, "translationX", llContext.getTranslationX(), 0).setDuration(600).start();
                    ObjectAnimator.ofFloat(llDelete, "translationX", llDelete.getTranslationX(), 0).setDuration(600).start();
                }
            });


            root.setOnTouchListener(new View.OnTouchListener() {

                private float diff;
                float x = -1;
                float mx;
                boolean isMove;

                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    if (max == 0) {
                        return false;
                    }
                    //当按下时处理
                    if (event.getAction() == MotionEvent.ACTION_MOVE) {
                        //由于父onInterceptTouchEvent 为false所以down无效 且不需要 以-1作为初始X
                        //这里类似一般写法的ACTION_DOWN初始化
                        if (x == -1)
                            x = event.getRawX();

                        mx = event.getRawX();
                        isMove = true;
                        diff = mx - x;

                        if (diff < -max)
                            diff = -max;

                        if (llContext.getTranslationX() > 0 && diff > llContext.getTranslationX())
                            diff = llContext.getTranslationX();

                        llContext.setTranslationX(diff);
                        llDelete.setTranslationX(diff);

                        return true;
                    } else if (event.getAction() == MotionEvent.ACTION_UP) {
                        x = -1;
                        if (isMove) {
                            //自动归位  过半则全部显示删除布局  反之则回收为正常
                            if (diff < -max / 2.0f) {
                                ObjectAnimator.ofFloat(llContext, "translationX", diff, -max).setDuration(600).start();
                                ObjectAnimator.ofFloat(llDelete, "translationX", diff, -max).setDuration(600).start();
                            } else {
                                ObjectAnimator.ofFloat(llContext, "translationX", diff, 0).setDuration(600).start();
                                ObjectAnimator.ofFloat(llDelete, "translationX", diff, 0).setDuration(600).start();
                            }
                            return true;
                        } else {
                            return false;
                        }

                    } else {//其他模式
                        //设置背景为未选中正常状态
                        //v.setBackgroundResource(R.drawable.mm_listitem_simple);

                    }
                    return true;
                }
            });


            return view;
        }
    }
}
作者:qq_28844947 发表于2016/10/26 17:42:11 原文链接
阅读:42 评论:0 查看评论

Android 动画总结

$
0
0

在日常的Android开发中,经常会使用到动画,这里就对Android开发中的动画做一下总结。

Android 动画分类

总的来说,Android动画可以分为两类,最初的传统动画和Android3.0 之后出现的属性动画
传统动画又包括 帧动画(Frame Animation)和补间动画(Tweened Animation)。

传统动画

帧动画

帧动画是最容易实现的一种动画,这种动画更多的依赖于完善的UI资源,他的原理就是将一张张单独的图片连贯的进行播放,
从而在视觉上产生一种动画的效果;有点类似于某些软件制作gif动画的方式。

frame.gif

如上图中的京东加载动画,代码要做的事情就是把一幅幅的图片按顺序显示,造成动画的视觉效果。

京东动画实现

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/a_0"
        android:duration="100" />
    <item
        android:drawable="@drawable/a_1"
        android:duration="100" />
    <item
        android:drawable="@drawable/a_2"
        android:duration="100" />
</animation-list>
protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_frame_animation);
        ImageView animationImg1 = (ImageView) findViewById(R.id.animation1);
        animationImg1.setImageResource(R.drawable.frame_anim1);
        AnimationDrawable animationDrawable1 = (AnimationDrawable) animationImg1.getDrawable();
        animationDrawable1.start();
    }

可以说,图片资源决定了这种方式可以实现怎样的动画

在有些代码中,我们还会看到android:oneshot="false" ,这个oneshot 的含义就是动画执行一次(true)还是循环执行多次。

这里其他几个动画实现方式都是一样,无非就是图片资源的差异。

补间动画

补间动画又可以分为四种形式,分别是 alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)
补间动画的实现,一般会采用xml 文件的形式;代码会更容易书写和阅读,同时也更容易复用。

XML 实现

首先,在res/anim/ 文件夹下定义如下的动画实现方式

alpha_anim.xml 动画实现

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromAlpha="1.0"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:toAlpha="0.0" />

scale.xml 动画实现

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXScale="0.0"
    android:fromYScale="0.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.0"
    android:toYScale="1.0"/>

然后,在Activity中

Animation animation = AnimationUtils.loadAnimation(mContext, R.anim.alpha_anim);
img = (ImageView) findViewById(R.id.img);
img.startAnimation(animation);

这样就可以实现ImageView alpha 透明变化的动画效果。

也可以使用set 标签将多个动画组合(代码源自Android SDK API)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:shareInterpolator=["true" | "false"] >
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        android:pivotX="float"
        android:pivotY="float" />
    <translate
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

可以看到组合动画是可以嵌套使用的。

各个动画属性的含义结合动画自身的特点应该很好理解,就不一一阐述了;这里主要说一下interpolator和 pivot

Interpolator 主要作用是可以控制动画的变化速率 ,就是动画进行的快慢节奏。

Android 系统已经为我们提供了一些Interpolator ,比如 accelerate_decelerate_interpolator,accelerate_interpolator等。更多的interpolator 及其含义可以在Android SDK 中查看。同时这个Interpolator也是可以自定义的,这个后面还会提到。

pivot 决定了当前动画执行的参考位置

pivot 这个属性主要是在translate 和 scale 动画中,这两种动画都牵扯到view 的“物理位置“发生变化,所以需要一个参考点。而pivotX和pivotY就共同决定了这个点;它的值可以是float或者是百分比数值。

我们以pivotX为例,

pivotX取值 含义
10 距离动画所在view自身左边缘10像素
10% 距离动画所在view自身左边缘 的距离是整个view宽度的10%
10%p 距离动画所在view父控件左边缘的距离是整个view宽度的10%

pivotY 也是相同的原理,只不过变成的纵向的位置。如果还是不明白可以参考源码,在Tweened Animation中结合seekbar的滑动观察rotate的变化理解。

rotate1.gif

Java Code 实现

有时候,动画的属性值可能需要动态的调整,这个时候使用xml 就不合适了,需要使用java代码实现

private void RotateAnimation() {
        animation = new RotateAnimation(-deValue, deValue, Animation.RELATIVE_TO_SELF,
                pxValue, Animation.RELATIVE_TO_SELF, pyValue);
        animation.setDuration(timeValue);

        if (keep.isChecked()) {
            animation.setFillAfter(true);
        } else {
            animation.setFillAfter(false);
        }
        if (loop.isChecked()) {
            animation.setRepeatCount(-1);
        } else {
            animation.setRepeatCount(0);
        }

        if (reverse.isChecked()) {
            animation.setRepeatMode(Animation.REVERSE);
        } else {
            animation.setRepeatMode(Animation.RESTART);
        }
        img.startAnimation(animation);
    }

这里animation.setFillAfter决定了动画在播放结束时是否保持最终的状态;animation.setRepeatCount和animation.setRepeatMode 决定了动画的重复次数及重复方式,具体细节可查看源码理解。

好了,传统动画的内容就说到这里了。

属性动画

属性动画,顾名思义它是对于对象属性的动画。因此,所有补间动画的内容,都可以通过属性动画实现。

属性动画入门

首先我们来看看如何用属性动画实现上面补间动画的效果

    private void RotateAnimation() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
        anim.setDuration(1000);
        anim.start();
    }

    private void AlpahAnimation() {
        ObjectAnimator anim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.8f, 0.6f, 0.4f, 0.2f, 0.0f);
        anim.setRepeatCount(-1);
        anim.setRepeatMode(ObjectAnimator.REVERSE);
        anim.setDuration(2000);
        anim.start();
    }

这两个方法用属性动画的方式分别实现了旋转动画和淡入淡出动画,其中setDuration、setRepeatMode及setRepeatCount和补间动画中的概念是一样的。

可以看到,属性动画貌似强大了许多,实现很方便,同时动画可变化的值也有了更多的选择,动画所能呈现的细节也更多。

当然属性动画也是可以组合实现的

                ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(myView, "alpha", 1.0f, 0.5f, 0.8f, 1.0f);
                ObjectAnimator scaleXAnim = ObjectAnimator.ofFloat(myView, "scaleX", 0.0f, 1.0f);
                ObjectAnimator scaleYAnim = ObjectAnimator.ofFloat(myView, "scaleY", 0.0f, 2.0f);
                ObjectAnimator rotateAnim = ObjectAnimator.ofFloat(myView, "rotation", 0, 360);
                ObjectAnimator transXAnim = ObjectAnimator.ofFloat(myView, "translationX", 100, 400);
                ObjectAnimator transYAnim = ObjectAnimator.ofFloat(myView, "tranlsationY", 100, 750);
                AnimatorSet set = new AnimatorSet();
                set.playTogether(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
//                set.playSequentially(alphaAnim, scaleXAnim, scaleYAnim, rotateAnim, transXAnim, transYAnim);
                set.setDuration(3000);
                set.start();

可以看到这些动画可以同时播放,或者是按序播放。

属性动画核心原理

在上面实现属性动画的时候,我们反复的使用到了ObjectAnimator 这个类,这个类继承自ValueAnimator,使用这个类可以对任意对象的任意属性进行动画操作。而ValueAnimator是整个属性动画机制当中最核心的一个类;这点从下面的图片也可以看出。

valueanimator.png

属性动画核心原理,此图来自于Android SDK API 文档。

属性动画的运行机制是通过不断地对值进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类来负责计算的。它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮我们完成从初始值平滑地过渡到结束值这样的效果。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式、以及对动画设置监听器等。

从上图我们可以了解到,通过duration、startPropertyValue和endPropertyValue 等值,我们就可以定义动画运行时长,初始值和结束值。然后通过start方法开始动画。
那么ValueAnimator 到底是怎样实现从初始值平滑过渡到结束值的呢?这个就是由TypeEvaluator 和TimeInterpolator 共同决定的。

具体来说,TypeEvaluator 决定了动画如何从初始值过渡到结束值。
TimeInterpolator 决定了动画从初始值过渡到结束值的节奏。

说的通俗一点,你每天早晨出门去公司上班,TypeEvaluator决定了你是坐公交、坐地铁还是骑车;而当你决定骑车后,TimeInterpolator决定了你一路上骑行的方式,你可以匀速的一路骑到公司,你也可以前半程骑得飞快,后半程骑得慢悠悠。

如果,还是不理解,那么就看下面的代码吧。首先看一下下面的这两个gif动画,一个小球在屏幕上以 y=sin(x) 的数学函数轨迹运行,同时小球的颜色和半径也发生着变化,可以发现,两幅图动画变化的节奏也是不一样的。

anim1.gif

anim2.gif

如果不考虑属性动画,这样的一个动画纯粹的使用Canvas+Handler的方式绘制也是有可能实现的。但是会复杂很多,而且加上各种线程,会带来很多意想不到的问题。

这里就通过自定义属性动画的方式看看这个动画是如何实现的。

属性动画自定义实现

这个动画最关键的三点就是 运动轨迹、小球半径及颜色的变化;我们就从这三个方面展开。最后我们在结合Interpolator说一下TimeInterpolator的意义。

用TypeEvaluator 确定运动轨迹

前面说了,TypeEvaluator决定了动画如何从初始值过渡到结束值。这个TypeEvaluator是个接口,我们可以实现这个接口。

public class PointSinEvaluator implements TypeEvaluator {

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());

        float y = (float) (Math.sin(x * Math.PI / 180) * 100) + endPoint.getY() / 2;
        Point point = new Point(x, y);
        return point;
    }
}

PointSinEvaluator 继承了TypeEvaluator类,并实现了他唯一的方法evaluate;这个方法有三个参数,第一个参数fraction 代表当前动画完成的百分比,这个值是如何变化的后面还会提到;第二个和第三个参数代表动画的初始值和结束值。这里我们的逻辑很简单,x的值随着fraction 不断变化,并最终达到结束值;y的值就是当前x值所对应的sin(x) 值,然后用x 和 y 产生一个新的点(Point对象)返回。

这样我们就可以使用这个PointSinEvaluator 生成属性动画的实例了。

        Point startP = new Point(RADIUS, RADIUS);//初始值(起点)
        Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//结束值(终点)
        final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentPoint = (Point) animation.getAnimatedValue();
                postInvalidate();
            }
        });

这样我们就完成了动画轨迹的定义,现在只要调用valueAnimator.start() 方法,就会绘制出一个正弦曲线的轨迹。

颜色及半径动画实现

之前我们说过,使用ObjectAnimator 可以对任意对象的任意属性进行动画操作,这句话是不太严谨的,这个任意属性还需要有get 和 set 方法。

public class PointAnimView extends View {

    /**
     * 实现关于color 的属性动画
     */
    private int color;
    private float radius = RADIUS;
   
    .....

}

这里在我们的自定义view中,定义了两个属性color 和 radius,并实现了他们各自的get set 方法,这样我们就可以使用属性动画的特点实现小球颜色变化的动画和半径变化的动画。

        ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
                Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
        animColor.setRepeatCount(-1);
        animColor.setRepeatMode(ValueAnimator.REVERSE);


        ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
        animScale.setRepeatCount(-1);
        animScale.setRepeatMode(ValueAnimator.REVERSE);
        animScale.setDuration(5000);
        animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radius = (float) animation.getAnimatedValue();
            }
        });

这里,我们使用ObjectAnimator 实现对color 属性的值按照ArgbEvaluator 这个类的规律在给定的颜色值之间变化,这个ArgbEvaluator 和我们之前定义的PointSinEvaluator一样,都是决定动画如何从初始值过渡到结束值的,只不过这个类是系统自带的,我们直接拿来用就可以,他可以实现各种颜色间的自由过渡。

对radius 这个属性使用了ValueAnimator,使用了其ofFloat方法实现了一系列float值的变化;同时为其添加了动画变化的监听器,在属性值更新的过程中,我们可以将变化的结果赋给radius,这样就实现了半径动态的变化。

这里radius 也可以使用和color相同的方式,只需要把ArgbEvaluator 替换为FloatEvaluator,同时修改动画的变化值即可;使用添加监听器的方式,只是为了介绍监听器的使用方法而已

好了,到这里我们已经定义出了所有需要的动画,前面说过,属性动画也是可以组合使用的。因此,在动画启动的时候,同时播放这三个动画,就可以实现图中的效果了。

        animSet = new AnimatorSet();
        animSet.play(valueAnimator).with(animColor).with(animScale);
        animSet.setDuration(5000);
        animSet.setInterpolator(interpolatorType);
        animSet.start();

PointAnimView 源码

public class PointAnimView extends View {

    public static final float RADIUS = 20f;

    private Point currentPoint;

    private Paint mPaint;
    private Paint linePaint;

    private AnimatorSet animSet;
    private TimeInterpolator interpolatorType = new LinearInterpolator();

    /**
     * 实现关于color 的属性动画
     */
    private int color;
    private float radius = RADIUS;

    public PointAnimView(Context context) {
        super(context);
        init();
    }


    public PointAnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PointAnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }


    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
        mPaint.setColor(this.color);
    }

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }

    private void init() {
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.TRANSPARENT);

        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(Color.BLACK);
        linePaint.setStrokeWidth(5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (currentPoint == null) {
            currentPoint = new Point(RADIUS, RADIUS);
            drawCircle(canvas);
//            StartAnimation();
        } else {
            drawCircle(canvas);
        }

        drawLine(canvas);
    }

    private void drawLine(Canvas canvas) {
        canvas.drawLine(10, getHeight() / 2, getWidth(), getHeight() / 2, linePaint);
        canvas.drawLine(10, getHeight() / 2 - 150, 10, getHeight() / 2 + 150, linePaint);
        canvas.drawPoint(currentPoint.getX(), currentPoint.getY(), linePaint);

    }

    public void StartAnimation() {
        Point startP = new Point(RADIUS, RADIUS);
        Point endP = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
        final ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointSinEvaluator(), startP, endP);
        valueAnimator.setRepeatCount(-1);
        valueAnimator.setRepeatMode(ValueAnimator.REVERSE);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                currentPoint = (Point) animation.getAnimatedValue();
                postInvalidate();
            }
        });

//
        ObjectAnimator animColor = ObjectAnimator.ofObject(this, "color", new ArgbEvaluator(), Color.GREEN,
                Color.YELLOW, Color.BLUE, Color.WHITE, Color.RED);
        animColor.setRepeatCount(-1);
        animColor.setRepeatMode(ValueAnimator.REVERSE);


        ValueAnimator animScale = ValueAnimator.ofFloat(20f, 80f, 60f, 10f, 35f,55f,10f);
        animScale.setRepeatCount(-1);
        animScale.setRepeatMode(ValueAnimator.REVERSE);
        animScale.setDuration(5000);
        animScale.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                radius = (float) animation.getAnimatedValue();
            }
        });


        animSet = new AnimatorSet();
        animSet.play(valueAnimator).with(animColor).with(animScale);
        animSet.setDuration(5000);
        animSet.setInterpolator(interpolatorType);
        animSet.start();

    }

    private void drawCircle(Canvas canvas) {
        float x = currentPoint.getX();
        float y = currentPoint.getY();
        canvas.drawCircle(x, y, radius, mPaint);
    }


    public void setInterpolatorType(int type ) {
        switch (type) {
            case 1:
                interpolatorType = new BounceInterpolator();
                break;
            case 2:
                interpolatorType = new AccelerateDecelerateInterpolator();
                break;
            case 3:
                interpolatorType = new DecelerateInterpolator();
                break;
            case 4:
                interpolatorType = new AnticipateInterpolator();
                break;
            case 5:
                interpolatorType = new LinearInterpolator();
                break;
            case 6:
                interpolatorType=new LinearOutSlowInInterpolator();
                break;
            case 7:
                interpolatorType = new OvershootInterpolator();
            default:
                interpolatorType = new LinearInterpolator();
                break;
        }
    }


    @TargetApi(Build.VERSION_CODES.KITKAT)
    public void pauseAnimation() {
        if (animSet != null) {
            animSet.pause();
        }
    }


    public void stopAnimation() {
        if (animSet != null) {
            animSet.cancel();
            this.clearAnimation();
        }
    }
}

TimeInterpolator 介绍

Interpolator的概念其实我们并不陌生,在补间动画中我们就使用到了。他就是用来控制动画快慢节奏的;而在属性动画中,TimeInterpolator 也是类似的作用;TimeInterpolator 继承自Interpolator。我们可以继承TimerInterpolator 以自己的方式控制动画变化的节奏,也可以使用Android 系统提供的Interpolator。

下面都是系统帮我们定义好的一些Interpolator,我们可以通过setInterpolator 设置不同的Interpolator。

系统自带Interpolator

这里我们使用的Interpolator就决定了 前面我们提到的fraction。变化的节奏决定了动画所执行的百分比。不得不说,这么ValueAnimator的设计的确是很巧妙。

XML 属性动画

这里提一下,属性动画当然也可以使用xml文件的方式实现,但是属性动画的属性值一般会牵扯到对象具体的属性,更多是通过代码动态获取,所以xml文件的实现会有些不方便。

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

使用方式:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
    R.anim.property_animator);
set.setTarget(myObject);
set.start();

xml 文件中的标签也和属性动画的类相对应。

 ValueAnimator --- <animator> 
 ObjectAnimator --- <objectAnimator> 
 AnimatorSet --- <set> 

这些就是属性动画的核心内容。现在使用属性动画的特性自定义动画应该不是难事了。其余便签的含义,结合之前的内容应该不难理解了。

传统动画 VS 属性动画

相较于传统动画,属性动画有很多优势。那是否意味着属性动画可以完全替代传统动画呢。其实不然,两种动画都有各自的优势,属性动画如此强大,也不是没有缺点。

补间动画点击事件

属性动画点击事件

  • 从上面两幅图比较可以发现,补间动画中,虽然使用translate将图片移动了,但是点击原来的位置,依旧可以发生点击事件,而属性动画却不是。因此我们可以确定,属性动画才是真正的实现了view的移动,补间动画对view的移动更像是在不同地方绘制了一个影子,实际的对象还是处于原来的地方。

  • 当我们把动画的repeatCount设置为无限循环时,如果在Activity退出时没有及时将动画停止,属性动画会导致Activity无法释放而导致内存泄漏,而补间动画却没有问题。因此,使用属性动画时切记在Activity执行 onStop 方法时顺便将动画停止。(对这个怀疑的同学可以自己通过在动画的Update 回调方法打印日志的方式进行验证)。

  • xml 文件实现的补间动画,复用率极高。在Activity切换,窗口弹出时等情景中有着很好的效果。

  • 使用帧动画时需要注意,不要使用过多特别大的图,容易导致内存不足。

好了,关于Android 动画的总结就到这里。

作者:gvvbn 发表于2016/10/26 17:56:19 原文链接
阅读:40 评论:1 查看评论

IAR for STM8介绍、下载、安装与注册

$
0
0

 

Ⅰ、写在前面

本文讲述的内容是IAR for STM8的介绍、下载、安装与注册,其安装、注册过程和IAR for ARM类似,如果需要了解IAR for ARM相关的文章,可以到我博客,或微信公众号查看并下载。


IAR for ARM介绍、下载、安装与注册:

http://blog.csdn.net/ybhuangfugui/article/details/52562533


本文内容已经整理成PDF文件,提供给大家下载:

http://pan.baidu.com/s/1geClBrH

 

作者:strongerHuang

本文版权所有,未经允许,禁止用于其它商业用途!!!

 

关于本文的更多详情请往下看。

 

Ⅱ、IAR介绍

1.关于IAR

IAR是一家公司的名称,也是一种集成开发环境的名称,我们平时所说的IAR主要是指集成开发环境。


IAR这家公司的发展也是经历了一系列历史变化,从开始针对8051C编译器,逐渐发展至今,已经是一家庞大的、技术力量雄厚的公司。而IAR集成开发环境也是从单一到现在针对不同处理器,拥有多种IAR版本的集成开发环境。


本文主要讲述IAR for STM8这一款开发工具,而IAR拥有多个版本,支持的芯片有上万种,请参看官网:

https://www.iar.com/device-search/#!?tab=devices


IAR针对不同内核处理器,是有不同的集成开发环境,下面截取部分IAR开发环境(如下图):



2.关于IAR for STM8

IAR for STM8集成开发工具主要用于STM8系列芯片的开发,我们所说的IAR for STM8其实是Embedded Workbench for STM8,即嵌入式工作平台,在有些地方也会看见IAR EWSTM8,其实它们都是同一个集成开发工具软件,只是叫法不一样而已。

Embedded Workbench for STM8IAR Systems 公司为STM8 微处理器开发的一个集成开发环境(简称IAR EWSTM8,也简称为IAR forSTM8)。比较其他的STM8 开发环境,IAR EWSTM8 具有入门容易、使用方便和代码紧凑等特点。

 

3.支持芯片

IAR for STM8现在(201610)最新版本V2.20,支持市面上所有的STM8系列芯片,有一百多种之多,具体可以到IAR官方网站查看。

 

https://www.iar.com/device-search/#!?tab=devices


 

Ⅲ、下载

IAR for STM8这个软件可以到官网下载,也可以到我百度网盘下载。下面章节讲述的安装、注册也是从官方下载的软件,为了方便大家下载,我将其上传至百度网盘(和官网的一样)。

 

1.官方下载

目前(201610月)IAR for M8官方最新版本是V2.20

下载地址:https://www.iar.com/iar-embedded-workbench/#!?currentTab=free-trials


 

2.百度网盘下载

百度云盘下载方便、快捷,里面包含注册机。我在百度网盘提供下载的版本也是从官网下载,上传至百度网盘【定期更新至最新版本】。

 

百度网盘地址:http://pan.baidu.com/s/1slF5kYx

 

注意:由于许多网盘近年来受到影响都相继关闭了,如果网盘链接失效,可以微信公众号查看更新链接,或微信联系作者。

 

Ⅳ、安装

IAR for STM8集成开发环境的安装比较简单(基本上就是一路Next下去)。还是按照常规安装教程(截图)讲述一下吧,以上面下载的“EWSTM8-2202- Autorun.exe”软件为例讲述。

 

1.下载软件,双击安装包,进入准备安装(解压)过程


 

2.进入安装就绪界面,点击“安装IAR


 

3.进入安装向导界面,点击“Next


 

4.选择“I accept the ...”,点击“Next


 

5.点击“Change”选择安装路径(默认C盘,可以不用修改,我这里改为D盘),点击“Next


 

6.勾选需要安装调试的驱动(默认安装所以驱动),点击“Next


 

7.选择安装程序文件夹(默认),点击“Next


 

8.提示(安装路径、驱动等)是否准备好安装,点击“Install”开始安装


 

9.安装过程有两三分钟,需要耐心等待


 

10.软件安装完成,提示安装驱动,点击“是”


 

11.驱动安装过程







 

12.点击“Finish”,软件安装完成,点击“Exit”,退出安装向导界面




至此,IAR for STM8的软件就算安装完成了。

 

Ⅴ、注册软件

IAR for STM8是一个收费的软件,官方严厉打击盗版,若你是商业用途,建议购买正版软件。当然,我这里是针对个人开发学习的朋友而言,进行非商业用途的使用。


我上面百度网盘提供下载的地址里面有一个IAR注册机”文件,本章将利用该注册机进行注册。


1.打开上面安装好的IAR for STM8软件(没有创建桌面快捷方式,可以从开始菜单打开软件)。 Help -> License Manager进入许可管理。


 

2.打开许可管理会弹出如下“许可向导”,点击“取消”。



 

3.使用离线激活方式:License -> Offline Activation进入离线激活界面。



 

4.打开注册机:(1)IAR类型(STM8); (2)生产许可码; (3)复制许可码。


 

5.回到“离线激活界面”界面,粘贴注册码,点击“下一步”


 

6.“不”选择锁定,点击“下一步”


 

7.保存激活信息“ActivationInfo.txt”在安装目录下,点击“下一步”




 

8.请求激活信息文件,点击“下一步”,进入浏览“注册码”界面(备用)



 

9.切换到前面打开的注册机:在注册机上打开ActivationInfo.txt(上面生产的)



 

10.生成“ActivationResponse.txt”文件,保存在软件安装目录下



 

11.回到软件注册界面,打开上一步生成的“ActivationResponse.txt”的文件,点击“下一步”




 

12.点击“Done”,注册完成


 

13.查看注册状态(出现以下信息说明注册成功)



 

 

至此,注册已经完成了。 IAR软件的注册过程相比Keil要复杂一些。

 

Ⅵ、说明

IAR for STM8这个工具是一款收费的软件,官方严厉打击盗版,这里主要针对个人学习使用的朋友,若你是商业用途,建议购买正版软件。

 

以上总结仅供参考,若有不对之处,敬请谅解。


Ⅶ、最后

我的博客:http://blog.csdn.net/ybhuangfugui

微信公众号:EmbeddDeveloper

 

更多精彩文章我将第一时间在微信公众号里面分享,如果不想错过,可以关注我的微信公众号。

 

本着免费分享的原则,方便大家手机学习知识,定期在微信平台分享技术知识。如果觉得文章的内容对你有用,又想了解更多相关的文章,请用微信搜索EmbeddDeveloper” 或者扫描下面二维码、关注,将有更多精彩内容等着你。

 

作者:ybhuangfugui 发表于2016/10/26 18:01:45 原文链接
阅读:40 评论:0 查看评论

android之OkHttp简单使用

$
0
0

okHttp 的简单使用

引入Gradle依赖:compile 'com.squareup.okhttp3:okhttp:3.4.1'

HTTP GET请求

MainActivity代码如下:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

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

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

    /**
     * http-get请求
     *绑定按钮的事件
     * @param view
     */
    public void httpGet(View view) {

        new Thread() {
            @Override
            public void run() {
                super.run();
                String url = "http://www.baidu.com";
                OkHttpClient client = new OkHttpClient();//创建okhttp实例
                Request request = new Request.Builder()
                        .url(url).build();
                Call call = client.newCall(request);
                try {
                    Response response = call.execute();
                    if (response.isSuccessful()) {
                        Log.i(TAG, "httpGet: " + response.body().string());
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

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

Response response = call.execute();这个是异步方式的,所以我开启了个子线程来执行,
否则会崩掉。。也可以不开启线程,那就可以这样做:

修改后MainActivity代码如下:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;


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

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

    /**
     * get请求
     *
     * @param view
     */
    public void httpGet(View view) {
        String url = "http://www.baidu.com";
                OkHttpClient client = new OkHttpClient();//创建okhttp实例
                Request request = new Request.Builder()
                        .url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            //请求失败时调用
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + e);
            }
            //请求成功时调用
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "onResponse: " + "ss");
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            }
        });

    }

}

运行结果是一样的,其实这个也是异步操作的。、

HTTP POST请求

自己搭个服务器吧。我用的是tomcat,访问自己的服务器。老是访问别人的服务器多没意思。哈哈。。

在eclipse先建个web工程,再创建个LoginServlet代码如下:

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class LoginServlet
 */

public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public LoginServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("GET请求成功");
        response.getWriter().append("GET请求成功");
    }

    /**
     * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /**
         * 防止乱码
         */
        response.setContentType("text/html;charset=utf-8");
        response. setCharacterEncoding("UTF-8");
        request. setCharacterEncoding("UTF-8");

        System.out.println("Post请求成功");
        String name=request.getParameter("name");

        System.out.println(name);
        response.getWriter().append("Post请求成功");
    }
}

然后把这个web项目部署到tomcat中就好了,这样我们的简单的服务器就搭建好了。

修改后MainActivity代码如下:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;


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

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

    /**
     * get请求
     *
     * @param view
     */
    public void httpGet(View view) {
        //换成自己的ip就行
        String url = "http://10.104.4.1:8080/okhttp/LoginServlet";
                OkHttpClient client = new OkHttpClient();//创建okhttp实例
                Request request = new Request.Builder()
                        .url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            //请求失败时调用
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + e);
            }
            //请求成功时调用
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "onResponse: " + "ss");
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            }
        });

    }

    /**
     * post请求
     * @param view
     */
    public void httpPost(View view){
        //换成自己的ip就行
        String url = "http://10.104.4.1:8080/okhttp/LoginServlet";
        OkHttpClient client = new OkHttpClient();//创建okhttp实例
        FormBody body=new FormBody.Builder()
                .add("name","张三")
                .add("age","23")
                .build();
        Request request=new Request.Builder().post(body).url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            //请求失败时调用
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + e);
            }
            //请求成功时调用
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            }
        });
    }
}

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

好了,一个简单的post请求演示完了,只是提交了个键值对到服务器中,接下来演示提交json数据到服务器中

修改后MainActivity代码如下:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.io.IOException;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;


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

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

    /**
     * get请求
     *
     * @param view
     */
    public void httpGet(View view) {
        //换成自己的ip就行
        String url = "http://10.104.4.1:8080/okhttp/LoginServlet";
                OkHttpClient client = new OkHttpClient();//创建okhttp实例
                Request request = new Request.Builder()
                        .url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            //请求失败时调用
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + e);
            }
            //请求成功时调用
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                Log.i(TAG, "onResponse: " + "ss");
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            }
        });

    }

    /**
     * post请求
     * @param view
     */
    public void httpPost(View view){
        //换成自己的ip就行
        String url = "http://10.104.4.1:8080/okhttp/LoginServlet";
        OkHttpClient client = new OkHttpClient();//创建okhttp实例
        FormBody body=new FormBody.Builder()
                .add("name","张三")
                .add("age","23")
                .build();
        Request request=new Request.Builder().post(body).url(url).build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            //请求失败时调用
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + e);
            }
            //请求成功时调用
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            }
        });
    }

    /**
     * post请求 提交数据到服务器
     * @param view
     */
    public void httpPostJSON(View view){
        String json="{\n" +
                "    \"name\": \"张三 \"\"age\": \"23 \"\n" +
                "}";
        MediaType JSON = MediaType.parse("application/json; charset=utf-8");
        //换成自己的ip就行
        String url = "http://10.104.4.1:8080/okhttp/LoginServlet";
        OkHttpClient client = new OkHttpClient();//创建okhttp实例
        RequestBody body=RequestBody.create(JSON,json);
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            //请求失败时调用
            @Override
            public void onFailure(Call call, IOException e) {
                Log.i(TAG, "onFailure: " + e);
            }
            //请求成功时调用
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    Log.i(TAG, "onResponse: " + response.body().string());
                }
            }
        });

    }
}

web工程下LoginServlet 类doPost方法
修改后如下:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /**
         * 防止乱码
         */
        response.setContentType("text/html;charset=utf-8");
        response. setCharacterEncoding("UTF-8");
        request. setCharacterEncoding("UTF-8");
        /**
         * 把请求的json数据读取出来。
         */

        InputStream is=request.getInputStream();
        BufferedReader reader=new BufferedReader(new InputStreamReader(is, "utf-8"));
        String line=null;
        StringBuffer sb=new StringBuffer();
        while((line=reader.readLine())!=null){
            sb.append(line);
        }
        System.out.println(sb.toString());
        response.getWriter().append("Post请求成功");
    }

运行效果:

这里写图片描述

总结:

终于写完了,我是名初学者,有不对的地方请指正。

作者:song_shui_lin 发表于2016/10/26 18:17:18 原文链接
阅读:71 评论:0 查看评论

电容触摸按键原理

$
0
0

前置技能

   
  输入按键-GPIO输入

  通用定时器原理


RC充放电电路原理

RC充放电电路

如图:
    电阻R和电容C串联
    当开关断开时,电阻R,电容0两端电压都是0,无电流
    当开关闭合时(瞬间),电阻R两端V1(上端)和0(下端)有电压差,产生电流
    此时电子通过电阻R积累在电容正极(上端),直到电容C电压为V1,充电完成
    此时R两端电压均为V1,无压差,不再有电流经过

电容C的电压从0-V1,充电过程中,有充电时间t和电容C的电压Vt之间的关系(右图)
看以看出随充电时间越来越长,充电的效率(斜率)越来越低

分析:
    当按键按下瞬间,电阻R两端电压V1和0,此时瞬间电流为I = V1 / R
    当电容C充电一段时间后,假设电容C此时的电压为Vc,那么此时电阻C两端的电压为V1和Vc,此时的瞬间电流为I = (V1-Vc) / R
    所以随着充电时间的增加,电容C的电压增加,电阻R两端压差减小,电流降低,电子积累速度减慢,充电效率降低

RC电路充放电公式

Vt = V0 + (V1 - V0) * [1 - exp(-t / RC)]

V0:电容的初始电压值
V1:电容最终可以冲/放到的电压值
Vt:电容t时刻的电压值

若电容C从0V开始充电,那么V0 = 0,则公式简化为:
Vt =V1 * [1 - exp(-t / RC)]

公式中V1, R均为常数,要达到相同的充电电压Vt,那么充电时间t和电容值C,成正比

RC电路电容与电阻关系

结论:
    同样条件下,电容C和充电时间t成正比
    达到相同的电压,电容越大,所需要的充电时间越长

电容触摸按键原理

电容触摸按键原理

R:外接电容充放电电阻
Cs:TPAD与PCB之间存在杂散电容
Cx:当手指按下时,手指和TPAD之间的电容

图A:
    手指未按下时的电路,TPAD与PCB之间存在电容Cs

图B:
    当手指按下时,手指与TPAD之间存在电容Cx,此时相当于Cs与Cx并联,电容总值=Cs+Cx

根据上边”RC充放电电路原理”可知:

    当R一定时,达到相同的电压,电容越大,需要的充电时间越长
    所以,我们假设未触摸时充电时间为T1,触摸时充电时间为T2(T2 > T1)

检测电容触摸按键过程

1,电容放电到0
     TPAD引脚设置为推挽输出,放电
     放电到0V后,将计数器值设置为0,充电计时使用

2,电容充电
     TPAD引脚设置为浮空输入(IO复位后的状态)

3,充电完成(Vx)进入输入捕获
     TPAD引脚开启输入捕获
     因为放完电的时候C的电压为 0,所以设置上升沿捕获

4,是否按下-计算充电时间,对比是否按下
     当未按下时,记录充电完成的时间T1(计数器频路*数值)
     检测当次充电时间T2,与T1对比,如果超过预设时间T3,说明按键按下

电容触摸按键的硬件连接

电容触摸按键的硬件连接

PA1引脚说明:

PA1引脚说明

如图:
     电容触摸按键TPAD(黄色部分),TPAD引脚与PA1项链
     使用TIM5_CH2进行输入捕获

代码实现

tpad.h声明功能函数

#ifndef __TPAD_H
#define __TPAD_H
#include "sys.h"

// 未按下电容触摸按键时的充电时间
extern vu16 tpad_default_val;     

// 复位TPAD:
// 设置推挽输出放电到0;再设置浮空输入充电,计数器CNT=0
void  TPAD_Reset(void);

// 获取一次捕获事件得到充电时间:
// 复位TPAD,等待捕获上升沿,得到计数器值,计算充电时间
u16   TPAD_Get_Val(void);

// n次调用TPAD_Get_Val取最大值
u16   TPAD_Get_MaxVal(u8 n);

// 初始化TPAD:
// 系统启动后初始化输入捕获,10次调用TPAD_Get_Val()
// 取中间n次平均值,作为未按下时的充电时间tpad_default_val
u8     TPAD_Init(u8 psc);

// 扫描TPAD:
// 调用TPAD_Get_MaxVal获取多次充电中最大充电时间
// 与tpad_default_val对比,若超过tpad_default_val+TPAD_GATE_VAL则为触摸
u8     TPAD_Scan(u8 mode);

// 输入捕获通道初始化
void  TIM5_CH2_Cap_Init(u16 arr,u16 psc);

#endif

tpad.c-tpad.h声明功能函数的实现

#include "tpad.h"
#include "delay.h"
#include "usart.h"

#define TPAD_ARR_MAX_VAL 0XFFFF    // ARR最大值
vu16 tpad_default_val=0;           // 没有按下是的充电时间

// 初始化触摸按键
// 获取空载时触摸按键取值
// 返回值: 0:初始化成功 1:初始化失败
u8 TPAD_Init(u8 psc)
{
    u16 buf[10];
    u16 temp;
    u8 j,i;

    // 初始化定时器5通道2输入捕获
    TIM5_CH2_Cap_Init(TPAD_ARR_MAX_VAL, psc-1);//以1Mhz的频率计算

    // 连续读取10次TPAD_Get_Val(),间隔10ms
    for(i=0;i<10;i++) 
    {
        buf[i]=TPAD_Get_Val();// 10次定时器值装入数组
        delay_ms(10);
    }

    // 将10次计数器值按升序排序
    for(i=0;i<9;i++)
    {
        for(j=i+1;j<10;j++)
        {
            if(buf[i]>buf[j])// 升序
            {
                temp=buf[i];
                buf[i]=buf[j];
                buf[j]=temp;
            }
        }
    }

    // 取第2-8次充电时间取平均值,串口打印输出
    temp=0;
    for(i=2;i<8;i++)temp+=buf[i];
    tpad_default_val=temp/6;
    printf("tpad_default_val:%d\r\n",tpad_default_val);

    //  初始化遇到超过TPAD_ARR_MAX_VAL/2的数值,不正常
    if(tpad_default_val>TPAD_ARR_MAX_VAL/2) return 1;

    return 0;
}

// TPAD复位
void TPAD_Reset(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA时钟

    // 设置PA1为推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;             // PA1引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;      // 推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    GPIO_ResetBits(GPIOA, GPIO_Pin_1);                    // PA1输出0,放电

    delay_ms(5);     //延迟5ms 等待放电结束,电容电压此时为0V

    TIM_SetCounter(TIM5, 0);        //定时器5计数器设置为0
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中断标志

    //设置PA1为浮空输入-开始充电
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

}

// 获得定时器捕获值-上升沿捕获
u16 TPAD_Get_Val(void)
{
    // TPAD复位:PA1推挽输出放电到0,再设置PA1浮空输入充电,定时器=1,清除中断标志
    TPAD_Reset();

    // 等待捕获上升沿
    while(TIM_GetFlagStatus(TIM5, TIM_IT_CC2) == RESET)
    {
        // 超时,返回CNT值
        if(TIM_GetCounter(TIM5)>TPAD_ARR_MAX_VAL-500)
            return TIM_GetCounter(TIM5);
    };

    return TIM_GetCapture2(TIM5); // 返回通道2的捕获值
}

// 连续n次读取TPAD_Get_Val,返回最大值
u16 TPAD_Get_MaxVal(u8 n)
{
    u16 temp=0;    // 本次值
    u16 res=0;     // 最大值
    while(n--)
    {
        temp=TPAD_Get_Val();   // 获取一次充电计数
        if(temp>res)res=temp;  // 记录最大值
    };
    return res;
}

// 扫描触摸按键
// mode:0,不支持连续,1,支持联系
// 返回值:0,没有按下;1,有按下
#define TPAD_GATE_VAL 100    //门限值:大于tpad_default_val+TPAD_GATE_VAL视为按下
u8 TPAD_Scan(u8 mode)
{
    static u8 keyen=0;    // 是否可检测状态位 0:可以开始检验 1: 还不能检验
    u8 res=0;             // 返回是否按下 1:按下 0:未按下
    u8 sample=3;          // 默认采样3次
    u16 rval;             // 捕获到上升沿的最大值

    // 如果支持连续触发
    if(mode)
    {
        sample=6;         // 连续触发时,采样为6此
        keyen=0;          //支持连续
    }

    //取采样次数的最大值
    rval=TPAD_Get_MaxVal(sample);

    //对比是否按下了
    if(rval>(tpad_default_val+TPAD_GATE_VAL))
    {
        if(keyen==0)res=1;      // keyen==0,有效,返回1
        //printf("r:%d\r\n",rval);
        keyen=3;                // 至少再扫描3次后按键才能生效
    }
    if(keyen)keyen--;

    return res;
}

// 定时器5通道2输入捕获初始化
void TIM5_CH2_Cap_Init(u16 arr, u16 psc)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_ICInitTypeDef  TIM5_ICInitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);    // 使能定时器5时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   // 使能GPIOA时钟 

    // 初始化GPIOA-浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;               // PA1
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       // 50Mhz
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;     // 浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    //定时器5初始化
    TIM_TimeBaseStructure.TIM_Period = arr;                 // 重装载值
    TIM_TimeBaseStructure.TIM_Prescaler =psc;               // 预分频器
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; // 设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;// 向上计数
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);

    //初始化通道2
    TIM5_ICInitStructure.TIM_Channel = TIM_Channel_2; // CC1S=01 设置IC2映射到TI5
    TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿捕获
    TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 配置输入分频-不分频
    TIM5_ICInitStructure.TIM_ICFilter = 0x03;// IC2F=0011 配置输入滤波器-8个定时器时钟周期滤波
    TIM_ICInit(TIM5, &TIM5_ICInitStructure);// 初始化定时器5 IC2

    TIM_Cmd(TIM5, ENABLE ); // 使能定时器5
}

main.c 当捕获到电容触摸按键按下(捕获上升沿),LED1反转

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "tpad.h"

 int main(void)
 {
    u8 t=0;                      // 计数器
    delay_init();                // 延时函数初始化
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 中断优先级分组配置
    uart_init(115200);           // 串口初始化 115200
    LED_Init();                  // LED初始化
    TPAD_Init(6);                // TPAD初始化

    while(1)
    {
        if(TPAD_Scan(0))        // 捕获到上升沿(此函数执行至少15ms)
        {
            LED1=!LED1;          // LED1取反
        }

        t++;
        if(t==15)
        {
            t=0;
            LED0=!LED0;          // LED0闪烁,标志程序正在运行
        }

        delay_ms(10);            // 延时10ms
    }
 }

实验结果

LED0每间隔一段时间闪烁,标志程序正在运行中
当手指按下电容触摸按键时,LED1取反
作者:ABAP_Brave 发表于2016/10/26 18:38:42 原文链接
阅读:17 评论:0 查看评论

Android之ViewPager+GridView实现仿美团首页导航栏布局分页效果

$
0
0

用过美团app的小伙伴都应该非常熟悉,美团首页的分类导航栏是作为一个头布局展示在首页上的,并且分类过多的话则可以滑动查看。本篇博客正如题目所说,采用ViewPager+GridView的方式来实现美团app的这种效果。有人可能会说,我们可以采用ViewPager+Fragment的方式实现,至于Fragment中要显示的内容则可以用GridView或者是现在比较流行的RecyclerView实现,但是,我想说的是,如果ViewPager中的View个数是不确定的呢,如果后期需要再加入一页呢,是不是又需要创建一个Fragment对象,显然,稍微复杂了点。我是采用了童哥的思路,并在其基础上完善了一下,增加了滑动监听时指示器的状态变化,以及item的点击事件监听。可以在adapter中监听item的点击事件,也可以在activity中监听,但是需要注意传入的正确位置position。废话不多说,来一波效果图
这里写图片描述

首先分析一下,实现的主要思路就是将GridView作为一个View添加到ViewPager的adapter中,如上图展示的效果,数据源被分为三页加载,那么我们就需要inflate三个GridView出来,并add到ViewPager的集合中,并将这个集合作为ViewPager的数据源传给ViewPager的Adapter。

为了让大家能够更加直观的了解布局结构,我们接下来就先贴布局文件

activity_main.xml

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#CDCDCD"
    tools:context="com.example.viewpagerandgridview.MainActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:background="#FFFFFF"
        >
        <android.support.v4.view.ViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
        <LinearLayout
            android:id="@+id/points"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="10dp"
            android:gravity="center"
            android:orientation="horizontal"
            />
    </RelativeLayout>
</RelativeLayout>

接下来是作为ViewPager的item布局文件GridView,因为GridView是作为View加载到ViewPager中进行展示的,这里需要注意的是,此item布局文件必须以GridView作为根节点(如果最外层是RelativeLayout或者线性布局等等的话,在inflate时会报错,具体说明请自行调试)

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gridView"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:numColumns="4">

</GridView>

然后是GridView中的item布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:gravity="center"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/imgUrl"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/proName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="2dp"
        android:text="名称"/>
</LinearLayout>

分析完布局文件之后,我们就开始说我们的GridView的adapter,我们都知道,根据上面的动态效果图可以看出,我先假设有20条数据源,然后这20条数据源被加载到三个GridView上面,如何保证我们能获取到正确的item位置呢,你可能会说了不是直接用getView()方法中的position不就行了?当然不行了,根据上面的效果图简要说明一下原因,我们都知道GridView中第一条item数据的下标是0,所以直接用此position的话,那么第二页的GridView中第一个item下标仍然是0,但是,我们是直接传过来的20条数据源,然后将其分别加载到三个GridView上,那么以此类推,第二页的GridView中第一个item下标就应该是position=8而不是0,同时还要考虑每一页是否是铺满的情况。啰嗦了半天,还是直接看代码吧,注释的也很详细

/**
 * GridView加载数据adapter
 */
public class MyGridViewAdapter extends BaseAdapter {

    private List<ProductListBean> listData;
    private LayoutInflater inflater;
    private Context context;
    private int mIndex;//页数下标,表示第几页,从0开始
    private int mPagerSize;//每页显示的最大数量

    public MyGridViewAdapter(Context context,List<ProductListBean> listData,int mIndex,int mPagerSize) {
        this.context = context;
        this.listData = listData;
        this.mIndex = mIndex;
        this.mPagerSize = mPagerSize;
        inflater = LayoutInflater.from(context);
    }

    /**
     * 先判断数据集的大小是否足够显示满本页?listData.size() > (mIndex + 1)*mPagerSize
     * 如果满足,则此页就显示最大数量mPagerSize的个数
     * 如果不够显示每页的最大数量,那么剩下几个就显示几个 (listData.size() - mIndex*mPagerSize)
     */
    @Override
    public int getCount() {
        return listData.size() > (mIndex + 1)*mPagerSize ? mPagerSize : (listData.size() - mIndex*mPagerSize);
    }

    @Override
    public Object getItem(int position) {
        return listData.get(position + mIndex * mPagerSize);
    }

    @Override
    public long getItemId(int position) {
        return position + mIndex * mPagerSize;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if(convertView == null){
            convertView = inflater.inflate(R.layout.item_gridview,parent,false);
            holder = new ViewHolder();
            holder.proName = (TextView) convertView.findViewById(R.id.proName);
            holder.imgUrl = (ImageView) convertView.findViewById(R.id.imgUrl);

            convertView.setTag(holder);
        }else{
            holder = (ViewHolder) convertView.getTag();
        }
        //重新确定position(因为拿到的是总的数据源,数据源是分页加载到每页的GridView上的,为了确保能正确的点对不同页上的item)
        final int pos = position + mIndex*mPagerSize;//假设mPagerSize=8,假如点击的是第二页(即mIndex=1)上的第二个位置item(position=1),那么这个item的实际位置就是pos=9
        ProductListBean bean = listData.get(pos);
        holder.proName.setText(bean.getProName());
        holder.imgUrl.setImageResource(bean.getImgUrl());
        //添加item监听
        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context,"你点击了 "+listData.get(pos).getProName(),Toast.LENGTH_SHORT).show();
            }
        });
        return convertView;
    }

    class ViewHolder{
        private TextView proName;
        private ImageView imgUrl;
    }
}

ProductListBean 实体类

/**
 * 实体类
 */
public class ProductListBean implements Serializable {
    private String proName;
    private int imgUrl;

    public ProductListBean(String proName, int imgUrl) {
        this.proName = proName;
        this.imgUrl = imgUrl;
    }

    public String getProName() {
        return proName;
    }

    public void setProName(String proName) {
        this.proName = proName;
    }

    public int getImgUrl() {
        return imgUrl;
    }

    public void setImgUrl(int imgUrl) {
        this.imgUrl = imgUrl;
    }
}

接下来我们再看一下ViewPager的adapter实现。inflate出来的GridView作为View被add到ViewPager集合中,然后将数据传给adapter处理

/**
 *ViewPager的adapter
 */
public class MyViewPagerAdapter extends PagerAdapter {

    private List<View> viewLists;//View就是GridView

    public MyViewPagerAdapter(List<View> viewLists) {
        this.viewLists = viewLists;
    }

    /**
     *这个方法,是从ViewGroup中移出当前View
     */
    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        container.removeView(viewLists.get(position));
    }

    /**
     * 将当前View添加到ViewGroup容器中
     * 这个方法,return一个对象,这个对象表明了PagerAdapter适配器选择哪个对象放在当前的ViewPager中
     */
    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        container.addView(viewLists.get(position));
        return viewLists.get(position);
    }

    /**
     *这个方法,是获取当前窗体界面数
     */
    @Override
    public int getCount() {
        return viewLists != null ? viewLists.size() : 0;
    }

    /**
     *用于判断是否由对象生成界面
     */
    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;//官方文档要求这样写
    }
}

最后看一下我们的activity

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

    private ViewGroup points;//小圆点指示器
    private ImageView[] ivPoints;//小圆点图片集合
    private ViewPager viewPager;
    private int totalPage;//总的页数
    private int mPageSize = 8;//每页显示的最大数量
    private List<ProductListBean> listDatas;//总的数据源
    private List<View> viewPagerList;//GridView作为一个View对象添加到ViewPager集合中
    private int currentPage;//当前页

    private String[] proName = {"名称0","名称1","名称2","名称3","名称4","名称5",
            "名称6","名称7","名称8","名称9","名称10","名称11","名称12","名称13",
            "名称14","名称15","名称16","名称17","名称18","名称19"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        iniViews();
        //模拟数据源
        setDatas();
        LayoutInflater inflater = LayoutInflater.from(this);
        //总的页数,取整(这里有三种类型:Math.ceil(3.5)=4:向上取整,只要有小数都+1  Math.floor(3.5)=3:向下取整  Math.round(3.5)=4:四舍五入)
        totalPage = (int) Math.ceil(listDatas.size() * 1.0 / mPageSize);
        viewPagerList = new ArrayList<>();
        for(int i=0;i<totalPage;i++){
            //每个页面都是inflate出一个新实例
            GridView gridView = (GridView) inflater.inflate(R.layout.gridview_layout,viewPager,false);
            gridView.setAdapter(new MyGridViewAdapter(this,listDatas,i,mPageSize));
            //添加item点击监听
            /*gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    int pos = position + currentPage*mPageSize;
                    Log.i("TAG","position的值为:"+position + "-->pos的值为:"+pos);
                    Toast.makeText(MainActivity.this,"你点击了 "+listDatas.get(pos).getProName(),Toast.LENGTH_SHORT).show();
                }
            });*/
            //每一个GridView作为一个View对象添加到ViewPager集合中
            viewPagerList.add(gridView);
        }
        //设置ViewPager适配器
        viewPager.setAdapter(new MyViewPagerAdapter(viewPagerList));
        //小圆点指示器
        ivPoints = new ImageView[totalPage];
        for(int i=0;i<ivPoints.length;i++){
            ImageView imageView = new ImageView(this);
            //设置图片的宽高
            imageView.setLayoutParams(new ViewGroup.LayoutParams(10,10));
            if(i == 0){
                imageView.setBackgroundResource(R.drawable.page__selected_indicator);
            }else{
                imageView.setBackgroundResource(R.drawable.page__normal_indicator);
            }
            ivPoints[i] = imageView;
            LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            layoutParams.leftMargin = 20;//设置点点点view的左边距
            layoutParams.rightMargin = 20;//设置点点点view的右边距
            points.addView(imageView,layoutParams);
        }
        //设置ViewPager滑动监听
        viewPager.addOnPageChangeListener(this);
    }

    private void iniViews() {
        viewPager = (ViewPager) findViewById(R.id.viewPager);
        //初始化小圆点指示器
        points = (ViewGroup) findViewById(R.id.points);
    }

    private void setDatas() {
        listDatas = new ArrayList<>();
        for(int i=0;i<proName.length;i++){
            listDatas.add(new ProductListBean(proName[i], R.drawable.img));
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {
        //改变小圆圈指示器的切换效果
        setImageBackground(position);
        currentPage = position;
    }

    @Override
    public void onPageScrollStateChanged(int state) {

    }

    /**
     * 改变点点点的切换效果
     * @param selectItems
     */
    private void setImageBackground(int selectItems) {
        for (int i = 0; i < ivPoints.length; i++) {
            if (i == selectItems) {
                ivPoints[i].setBackgroundResource(R.drawable.page__selected_indicator);
            } else {
                ivPoints[i].setBackgroundResource(R.drawable.page__normal_indicator);
            }
        }
    }
}

此demo的应用场景还是实用性较强的,很多app中几乎都有此功能,相当实用,源代码随后上传,有问题欢迎提出

作者:xiaxiazaizai01 发表于2016/10/26 18:49:31 原文链接
阅读:19 评论:0 查看评论

Android布局之ViewStub

$
0
0

废话不多说,先来看看官方文档说明:

A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime. When a ViewStub is made visible, or when inflate() is invoked, the layout resource is inflated. The ViewStub then replaces itself in its parent with the inflated View or Views. Therefore, the ViewStub exists in the view hierarchy until setVisibility(int) or inflate() is invoked. The inflated View is added to the ViewStub's parent with the ViewStub's layout parameters. Similarly, you can define/override the inflate View's id by using the ViewStub's inflatedId property.

首先它是一个继承view的子类,ViewStub是一个不可见的零大小的视图,可用于在运行时延迟布局资源。 当ViewStub变为可见时,或者当调用inflate()时,布局资源会被填充。 ViewStub然后在其父级中用填充的视图替换它自己。 因此,ViewStub存在于视图层次结构中,直到调用setVisibility(int)或inflate()。 填充的视图被添加到ViewStub的父视图和ViewStub的布局参数。 类似地,您可以使用ViewStub的inflatedId属性定义/覆盖inflate View的id

总结起来比较简单,StubView就是一个延时加载的默认不显示的控件。

使用的一些注意事项:

1,判断是否已经加载过, 如果通过setVisibility来加载,那么通过判断可见性即可;如果通过inflate()来加载是不可以通过判断可见性来处理的,而需要使用方式2来进行判断。

2,findViewById的问题,注意ViewStub中是否设置了inflatedId,如果设置了则需要通过inflatedId来查找目标布局的根元素。


应用:例如我们通过一个ViewStub来惰性加载一个消息流的评论列表,因为一个帖子可能并没有评论,此时我可以不加载这个评论的ListView,只有当有评论时我才把它加载出来,这样就去除了加载ListView带来的资源消耗以及延时,示例如下 :

<span style="font-size:14px;"><ViewStub  
    android:id="@+id/stub_import"  
    android:inflatedId="@+id/stub_comm_lv"  
    android:layout="@layout/my_comment_layout"  
    android:layout_width="fill_parent"  
    android:layout_height="wrap_content"  
    android:layout_gravity="bottom" / 

my_comment_layout.xml如下:
</span><pre class="prettyprint" name="code"><span style="font-size:14px;"><code class="language-xml hljs  has-numbering"><span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>  
<span class="hljs-tag"><<span class="hljs-title">ListView</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>  
    <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>  
    <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/my_comm_lv"</span>  
    <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span> ></span>  

<span class="hljs-tag"></<span class="hljs-title">ListView</span>></span>  </code></span>


在运行时,我们只需要控制id为stub_import的ViewStub的可见性或者调用inflate()函数来控制是否加载这个评论列表即可。示例如下 :

public class MainActivity extends Activity {  

    public void onCreate(Bundle b){  
        // main.xml中包含上面的ViewStub  
        setContentView(R.layout.main);  

        // 方式1,获取ViewStub,  
        ViewStub listStub = (ViewStub) findViewById(R.id.stub_import);  
        // 加载评论列表布局  
        listStub.setVisibility(View.VISIBLE);  
        // 获取到评论ListView,注意这里是通过ViewStub的inflatedId来获取  
            ListView commLv = findViewById(R.id.stub_comm_lv);  
                if ( listStub.getVisibility() == View.VISIBLE ) {  
                       // 已经加载, 否则还没有加载  
                }  
            }  
       }  

通过setVisibility(View.VISIBILITY)来加载评论列表,此时你要获取到评论ListView对象的话,则需要通过findViewById来查找,而这个id并不是就是ViewStub的id。
这是为什么呢 ?

我们先看ViewStub的部分代码吧:

<pre class="prettyprint" name="code"><span style="font-size:14px;"><code class="language-java hljs  has-numbering"> <span class="hljs-annotation">@SuppressWarnings</span>({<span class="hljs-string">"UnusedDeclaration"</span>})  
    <span class="hljs-keyword">public</span> <span class="hljs-title">ViewStub</span>(Context context, AttributeSet attrs, <span class="hljs-keyword">int</span> defStyle) {  
        TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ViewStub,  
                defStyle, <span class="hljs-number">0</span>);  
        <span class="hljs-comment">// 获取inflatedId属性  </span>
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);  
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, <span class="hljs-number">0</span>);  

        a.recycle();  

        a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View, defStyle, <span class="hljs-number">0</span>);  
        mID = a.getResourceId(R.styleable.View_id, NO_ID);  
        a.recycle();  

        initialize(context);  
    }  

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">initialize</span>(Context context) {  
        mContext = context;  
        setVisibility(GONE);<span class="hljs-comment">// 设置不可教案  </span>
        setWillNotDraw(<span class="hljs-keyword">true</span>);<span class="hljs-comment">// 设置不绘制  </span>
    }  

    <span class="hljs-annotation">@Override</span>  
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onMeasure</span>(<span class="hljs-keyword">int</span> widthMeasureSpec, <span class="hljs-keyword">int</span> heightMeasureSpec) {  
        setMeasuredDimension(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>);<span class="hljs-comment">// 宽高都为0  </span>
    }  


    <span class="hljs-annotation">@Override</span>  
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setVisibility</span>(<span class="hljs-keyword">int</span> visibility) {  
        <span class="hljs-keyword">if</span> (mInflatedViewRef != <span class="hljs-keyword">null</span>) {<span class="hljs-comment">// 如果已经加载过则只设置Visibility属性  </span>
            View view = mInflatedViewRef.get();  
            <span class="hljs-keyword">if</span> (view != <span class="hljs-keyword">null</span>) {  
                view.setVisibility(visibility);  
            } <span class="hljs-keyword">else</span> {  
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"setVisibility called on un-referenced view"</span>);  
            }  
        } <span class="hljs-keyword">else</span> {<span class="hljs-comment">// 如果未加载,这加载目标布局  </span>
            <span class="hljs-keyword">super</span>.setVisibility(visibility);  
            <span class="hljs-keyword">if</span> (visibility == VISIBLE || visibility == INVISIBLE) {  
                inflate();<span class="hljs-comment">// 调用inflate来加载目标布局  </span>
            }  
        }  
    }  

    <span class="hljs-javadoc">/** 
     * Inflates the layout resource identified by {@link #getLayoutResource()} 
     * and replaces this StubbedView in its parent by the inflated layout resource. 
     * 
     *<span class="hljs-javadoctag"> @return</span> The inflated layout resource. 
     * 
     */</span>  
    <span class="hljs-keyword">public</span> View <span class="hljs-title">inflate</span>() {  
        <span class="hljs-keyword">final</span> ViewParent viewParent = getParent();  

        <span class="hljs-keyword">if</span> (viewParent != <span class="hljs-keyword">null</span> && viewParent <span class="hljs-keyword">instanceof</span> ViewGroup) {  
            <span class="hljs-keyword">if</span> (mLayoutResource != <span class="hljs-number">0</span>) {  
                <span class="hljs-keyword">final</span> ViewGroup parent = (ViewGroup) viewParent;<span class="hljs-comment">// 获取ViewStub的parent view,也是目标布局根元素的parent view  </span>
                <span class="hljs-keyword">final</span> LayoutInflater factory = LayoutInflater.from(mContext);  
                <span class="hljs-keyword">final</span> View view = factory.inflate(mLayoutResource, parent,  
                        <span class="hljs-keyword">false</span>);<span class="hljs-comment">// 1、加载目标布局  </span>
              <span class="hljs-comment">// 2、如果ViewStub的inflatedId不是NO_ID则把inflatedId设置为目标布局根元素的id,即评论ListView的id  </span>
                <span class="hljs-keyword">if</span> (mInflatedId != NO_ID) {  
                    view.setId(mInflatedId);  
                }  

                <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> index = parent.indexOfChild(<span class="hljs-keyword">this</span>);  
                parent.removeViewInLayout(<span class="hljs-keyword">this</span>);<span class="hljs-comment">// 3、将ViewStub自身从parent中移除  </span>

                <span class="hljs-keyword">final</span> ViewGroup.LayoutParams layoutParams = getLayoutParams();  
                <span class="hljs-keyword">if</span> (layoutParams != <span class="hljs-keyword">null</span>) {  
                    parent.addView(view, index, layoutParams);<span class="hljs-comment">// 4、将目标布局的根元素添加到parent中,有参数  </span>
                } <span class="hljs-keyword">else</span> {  
                    parent.addView(view, index);<span class="hljs-comment">// 4、将目标布局的根元素添加到parent中  </span>
                }  

                mInflatedViewRef = <span class="hljs-keyword">new</span> WeakReference<View>(view);  

                <span class="hljs-keyword">if</span> (mInflateListener != <span class="hljs-keyword">null</span>) {  
                    mInflateListener.onInflate(<span class="hljs-keyword">this</span>, view);  
                }  

                <span class="hljs-keyword">return</span> view;  
            } <span class="hljs-keyword">else</span> {  
                <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"ViewStub must have a valid layoutResource"</span>);  
            }  
        } <span class="hljs-keyword">else</span> {  
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(<span class="hljs-string">"ViewStub must have a non-null ViewGroup viewParent"</span>);  
        }  
    }</code></span>

置为根元素的id,这也是为什么我们在获取评论ListView时会使用findViewById(R.id.stub_comm_lv)来获取,其中的stub_comm_lv就是ViewStub的inflatedId。当然如果你没有设置inflatedId的话还是可以通过评论列表的id来获取的,例如findViewById(R.id.my_comm_lv)。然后就是ViewStub从parent中移除、把目标布局的根元素添加到parent中。最后会把目标布局的根元素返回,因此我们在调用inflate()函数时可以直接获得根元素,省掉了findViewById的过程。

参考:http://blog.csdn.net/bboyfeiyu/article/details/45869393


作者:MrNoHere 发表于2016/10/27 16:57:14 原文链接
阅读:32 评论:0 查看评论

java 内部类和匿名内部类

$
0
0
Java 内部类 
分四种:成员内部类、局部内部类、静态内部类和匿名内部类。 
1、成员内部类: 即作为外部类的一个成员存在,与外部类的属性、方法并列。
注意:成员内部类中不能定义静态变量,但可以访问外部类的所有成员。
public class Outer{
private static int i = 1;
private int j=10;
private int k=20;
public static void outer_f1(){
    //do more something
}
public void out_f2(){搜索
    //do more something
}
//成员内部类
class Inner{
//static int inner_i =100; //内部类中不允许定义静态变量
int j=100;//内部类中外部类的实例变量可以共存
int inner_i=1;
void inner_f1(){
    System.out.println(i);//外部类的变量如果和内部类的变量没有同名的,则可以直接用变量名访问外部类的变量
    System.out.println(j);//在内部类中访问内部类自己的变量直接用变量名
    System.out.println(this.j);//也可以在内部类中用"this.变量名"来访问内部类变量
    //访问外部类中与内部类同名的实例变量可用"外部类名.this.变量名"。
    System.out.println(k);//外部类的变量如果和内部类的变量没有同名的,则可以直接用变量名访问外部类的变量
    outer_f1();
    outer_f2();
}
}
//外部类的非静态方法访问成员内部类
public void outer_f3(){
    Inner inner = new Inner();
    inner.inner_f1();
}

//外部类的静态方法访问成员内部类,与在外部类外部访问成员内部类一样
public static void outer_f4(){
    //step1 建立外部类对象
    Outer out = new Outer();
    //***step2 根据外部类对象建立内部类对象***
    Inner inner=out.new Inner();
    //step3 访问内部类的方法
    inner.inner_f1();
}

public static void main(String[] args){
    outer_f4();
}
}
成员内部类的优点:
⑴ 内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使将外部类声明为PRIVATE,但是对于处于其内部的内部类还是可见的。)
⑵ 用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。
2、局部内部类: 即在方法中定义的内部类,与局部变量类似,在局部内部类前不加修饰符public或private,其范围为定义它的代码块。
注意:局部内部类中不可定义静态变量,可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的。
public class Outer {
 private int s = 100;
 private int out_i = 1;
 public void f(final int k){
  final int s = 200;
  int i = 1;
  final int j = 10;
  class Inner{ //定义在方法内部
   int s = 300;//可以定义与外部类同名的变量
   //static int m = 20;//不可以定义静态变量
   Inner(int k){
    inner_f(k);
   }
   int inner_i = 100;
   void inner_f(int k){
    System.out.println(out_i);//如果内部类没有与外部类同名的变量,在内部类中可以直接访问外部类的实例变量
    System.out.println(k);//*****可以访问外部类的局部变量(即方法内的变量),但是变量必须是final的*****
//    System.out.println(i);
    System.out.println(s);//如果内部类中有与外部类同名的变量,直接用变量名访问的是内部类的变量
    System.out.println(this.s);//用"this.变量名" 访问的也是内部类变量
    System.out.println(Outer.this.s);//用外部"外部类类名.this.变量名" 访问的是外部类变量
   }
  }
  new Inner(k);
 }

 public static void main(String[] args) {
 //访问局部内部类必须先有外部类对象
  Outer out = new Outer();
  out.f(3);
 }

}

注意:
在类外不可直接生成局部内部类(保证局部内部类对外是不可见的)。要想使用局部内部类时需要生成对象,对象调用方法,在方法中才能调用其局部内部类。通过内部类和接口达到一个强制的弱耦合,用局部内部类来实现接口,并在方法中返回接口类型,使局部内部类不可见,屏蔽实现类的可见性。
3、静态内部类: 静态内部类定义在类中,任何方法外,用static定义。
注意:静态内部类中可以定义静态或者非静态的成员
public class Outer {
 private static int i = 1;
 private int j = 10;
 public static void outer_f1(){
 
 }
 public void outer_f2(){
 
 }
// 静态内部类可以用public,protected,private修饰
// 静态内部类中可以定义静态或者非静态的成员
 static class Inner{
  static int inner_i = 100;
  int inner_j = 200;
  static void inner_f1(){
   System.out.println("Outer.i"+i);//静态内部类只能访问外部类的静态成员
   outer_f1();//包括静态变量和静态方法
  }
  void inner_f2(){
//   System.out.println("Outer.i"+j);//静态内部类不能访问外部类的非静态成员
//   outer_f2();//包括非静态变量和非静态方法
  } 
 
 }
 
 public void outer_f3(){
//  外部类访问内部类的静态成员:内部类.静态成员
  System.out.println(Inner.inner_i);
  Inner.inner_f1();
//  外部类访问内部类的非静态成员:实例化内部类即可
  Inner inner = new Inner();
  inner.inner_f2();
 
 }
 public static void main(String[] args) {
  new Outer().outer_f3();
 }

}

注意:*******生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:
Outer.Inner in=new Outer.Inner();
而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个顶级类。静态内部类不可用private来进行定义。*******

例子:
对于两个类,拥有相同的方法:
class People
{
  run();
}
class Machine{
   run();
}
此时有一个robot类:
class Robot extends People implement Machine.
此时run()不可直接实现。
注意:当类与接口(或者是接口与接口)发生方法命名冲突的时候,此时必须使用内部类来实现。用接口不能完全地实现多继承,用接口配合内部类才能实现真正的多继承。
4、匿名内部类 
匿名内部类是一种特殊的局部内部类,它是通过匿名类实现接口。
IA被定义为接口。
IA I=new IA(){};

匿名内部类的特点:

1,一个类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的事先或是覆盖。
2,只是为了获得一个对象实例,不需要知道其实际类型。
3,类名没有意义,也就是不需要使用到。

public class Outer {
 private static int i = 1;
 private int j = 10;
 public static void outer_f1(){
 
 }
 public void outer_f2(){
 
 }
// 静态内部类可以用public,protected,private修饰
// 静态内部类中可以定义静态或者非静态的成员
 static class Inner{
  static int inner_i = 100;
  int inner_j = 200;
  static void inner_f1(){
   System.out.println("Outer.i"+i);//静态内部类只能访问外部类的静态成员
   outer_f1();//包括静态变量和静态方法
  }
  void inner_f2(){
//   System.out.println("Outer.i"+j);//静态内部类不能访问外部类的非静态成员
//   outer_f2();//包括非静态变量和非静态方法
  }
 }
 
 public void outer_f3(){
//  外部类访问内部类的静态成员:内部类.静态成员
  System.out.println(Inner.inner_i);
  Inner.inner_f1();
//  外部类访问内部类的非静态成员:实例化内部类即可
  Inner inner = new Inner();
  inner.inner_f2();
 
 }
 public static void main(String[] args) {
  new Outer().outer_f3();
 }

}

注:一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类,没有类名,根据多态,我们使用其父类名。因他是局部内部类,那么局部内部类的所有限制都对其生效。匿名内部类是唯一一种无构造方法类。大部分匿名内部类是用于接口回调用的。匿名内部类在编译的时候由系统自动起名Out$1.class。如果一个对象编译时的类型是接口,那么其运行的类型为实现这个接口的类。因匿名内部类无构造方法,所以其使用范围非常的有限。当需要多个对象时使用局部内部类,因此局部内部类的应用相对比较多。匿名内部类中不能定义构造方法。如果一个对象编译时的类型是接口,那么其运行的类型为实现这个接口的类。

________________________________________________________________________________

内部类总结:
1.首先,把内部类作为外部类的一个特殊的成员来看待,因此它有类成员的封闭等级:private ,protected,默认(friendly),public
                                                      它有类成员的修饰符:   static,final,abstract
2.非静态内部类nested inner class,内部类隐含有一个外部类的指针this,因此,它可以访问外部类的一切资源(当然包括private)
  外部类访问内部类的成员,先要取得内部类的对象,并且取决于内部类成员的封装等级。
  非静态内部类不能包含任何static成员.
3.静态内部类:static inner class,不再包含外部类的this指针,并且在外部类装载时初始化.
  静态内部类能包含static或非static成员.
  静态内部类只能访问外部类static成员.
  外部类访问静态内部类的成员,循一般类法规。对于static成员,用类名.成员即可访问,对于非static成员,只能
    用对象.成员进行访问

4.对于方法中的内部类或块中内部类只能访问块中或方法中的final变量。

类成员有两种static , non-static,同样内部类也有这两种
non-static 内部类的实例,必须在外部类的方法中创建或通过外部类的实例来创建(OuterClassInstanceName.new innerClassName(ConstructorParameter)),并且可直接访问外部类的信息,外部类对象可通过OuterClassName.this来引用
static 内部类的实例, 直接创建即可,没有对外部类实例的引用。
内部类不管static还是non-static都有对外部类的引用
non-static 内部类不允许有static成员

方法中的内部类只允许访问方法中的final局部变量和方法的final参数列表,所以说方法中的内部类和内部类没什麽区别。但方法中的内部类不能在方法以外访问,方法中不可以有static内部类
匿名内部类如果继承自接口,必须实现指定接口的方法,且无参数 
匿名内部类如果继承自类,参数必须按父类的构造函数的参数传递


作者:zhzzhz123456 发表于2016/10/27 17:02:07 原文链接
阅读:37 评论:0 查看评论

Linux电源管理-Suspend/Resume流程

$
0
0

前言

根据上一节linux电源管理-概述可知,linux电源管理存在的几种方式,如何查看这几种方式,以及最后的如何睡眠唤醒等。
通过echo mem > /sys/power/state就可以达到睡眠,所以可以根据此节点的sys代码分析suspend的流程。

suspend代码分析

在手机端执行如下命令:
echo mem > /sys/power/state
根据sys节点的属性命令规则,可以此节点的实现代码为:  state_store

state_store函数分析

static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
			   const char *buf, size_t n)
{
	suspend_state_t state;
	int error;

	error = pm_autosleep_lock();
	if (error)
		return error;

	if (pm_autosleep_state() > PM_SUSPEND_ON) {
		error = -EBUSY;
		goto out;
	}

	state = decode_state(buf, n);
	if (state < PM_SUSPEND_MAX)
		error = pm_suspend(state);
	else if (state == PM_SUSPEND_MAX)
		error = hibernate();
	else
		error = -EINVAL;

 out:
	pm_autosleep_unlock();
	return error ? error : n;
}
1) pm_autosleep_lock
int pm_autosleep_lock(void)
{
	return mutex_lock_interruptible(&autosleep_lock);
}
获得autosleep锁,锁住autosleep功能,此功能在后面分析。
2. 判断当前autosleep的状态,如果当前状态大于PM_SUSPEND_ON则,返回退出。关于suspend的状态如下:
#define PM_SUSPEND_ON		((__force suspend_state_t) 0)
#define PM_SUSPEND_FREEZE	((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY	((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM		((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN		PM_SUSPEND_FREEZE
#define PM_SUSPEND_MAX		((__force suspend_state_t) 4)
3. 解析当前传入的state。如果state小于PM_SUSPEND_MAX就走suspend流程,等于PM_SUSPEND_MAX就走hibernate流程。加入我们传入的是mem, 则就会走suspend流程。

pm_suspend函数分析

int pm_suspend(suspend_state_t state)
{
	int error;

	if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
		return -EINVAL;

	pm_suspend_marker("entry");
	error = enter_state(state);
	if (error) {
		suspend_stats.fail++;
		dpm_save_failed_errno(error);
	} else {
		suspend_stats.success++;
	}
	pm_suspend_marker("exit");
	return error;
}
1. 依然会再次判断当前的state是否在PM_SUSPEND_ON和PM_SUSPEND_MAX之间
2. pm_suspend_marker("entry")
static void pm_suspend_marker(char *annotation)
{
	struct timespec ts;
	struct rtc_time tm;

	getnstimeofday(&ts);
	rtc_time_to_tm(ts.tv_sec, &tm);
	pr_info("PM: suspend %s %d-%02d-%02d %02d:%02d:%02d.%09lu UTC\n",
		annotation, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
		tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);
}
在suspend之间记录时间,用于统计或者调试suspend花费的时间
3. 调用enter_state进入suspend的下一步,如果执行suspend成功,增加suspend.success的引用计数,否则增加suspend.fail的引用计数。

enter_state函数分析

static int enter_state(suspend_state_t state)
{
	int error;

	trace_suspend_resume(TPS("suspend_enter"), state, true);
	if (state == PM_SUSPEND_FREEZE) {
#ifdef CONFIG_PM_DEBUG
		if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
			pr_warning("PM: Unsupported test mode for freeze state,"
				   "please choose none/freezer/devices/platform.\n");
			return -EAGAIN;
		}
#endif
	} else if (!valid_state(state)) {
		return -EINVAL;
	}
	if (!mutex_trylock(&pm_mutex))
		return -EBUSY;

	if (state == PM_SUSPEND_FREEZE)
		freeze_begin();

	trace_suspend_resume(TPS("sync_filesystems"), 0, true);
	printk(KERN_INFO "PM: Syncing filesystems ... ");
	sys_sync();
	printk("done.\n");
	trace_suspend_resume(TPS("sync_filesystems"), 0, false);

	pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]);
	error = suspend_prepare(state);
	if (error)
		goto Unlock;

	if (suspend_test(TEST_FREEZER))
		goto Finish;

	trace_suspend_resume(TPS("suspend_enter"), state, false);
	pr_debug("PM: Entering %s sleep\n", pm_states[state]);
	pm_restrict_gfp_mask();
	error = suspend_devices_and_enter(state);
	pm_restore_gfp_mask();

 Finish:
	pr_debug("PM: Finishing wakeup.\n");
	suspend_finish();
 Unlock:
	mutex_unlock(&pm_mutex);
	return error;
}
1. 通过vaild_state函数用来判断该平台是否支持该状态睡眠。
static bool valid_state(suspend_state_t state)
{
	/*
	 * PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states need low level
	 * support and need to be valid to the low level
	 * implementation, no valid callback implies that none are valid.
	 */
	return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}
根据注释可知,standby和mem状态是处于低功耗状态下的,需要平台代码来支持实现的。因此内核使用platform_suspend_ops来定义各个平台的pm实现,然后通过suspend_set_ops函数设置具体平台pm到suspend_ops中。最终还是通过vaild函数来判断该平台是否支持需要睡眠的状态。
内核也提供了只支持mem睡眠的函数
int suspend_valid_only_mem(suspend_state_t state)
{
	return state == PM_SUSPEND_MEM;
}
当然了如果state状态是freeze的话直接继续执行。
2. 调用mutex_trylock获得一个mutex锁,防止在suspend的时候再次suspend。
3. 如果当前state是PM_SUSPEND_FREEZE,则调用freeze_begin做开始准备工作。
4. 同步文件系统。
5. 调用suspend_prepare做进一步suspend前期准备工作,准备控制台,冻结内核线程等。
6. 调用suspend_devices_and_enter做设备以及系统相关的susupend操作。
7. 调用suspend_finish做最后的恢复工作。

suspend_prepare函数分析

/**
 * suspend_prepare - Prepare for entering system sleep state.
 *
 * Common code run for every system sleep state that can be entered (except for
 * hibernation).  Run suspend notifiers, allocate the "suspend" console and
 * freeze processes.
 */
static int suspend_prepare(suspend_state_t state)
{
	int error;

	if (!sleep_state_supported(state))
		return -EPERM;

	pm_prepare_console();

	error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);
	if (error)
		goto Finish;

	trace_suspend_resume(TPS("freeze_processes"), 0, true);
	error = suspend_freeze_processes();
	trace_suspend_resume(TPS("freeze_processes"), 0, false);
	if (!error)
		return 0;

	suspend_stats.failed_freeze++;
	dpm_save_failed_step(SUSPEND_FREEZE);
 Finish:
	pm_notifier_call_chain(PM_POST_SUSPEND);
	pm_restore_console();
	return error;
}
1. 检测该平台suspend_ops是否实现了enter函数
static bool sleep_state_supported(suspend_state_t state)
{
	return state == PM_SUSPEND_FREEZE || (suspend_ops && suspend_ops->enter);
}
2. 调用pm_prepare_console函数切换控制台,重新分配一个suspend模式下控制台,然后重定向kmsg。
3. 通过调用pm通知链,发送PM_SUSPEND_PREPARE消息。
int pm_notifier_call_chain(unsigned long val)
{
	int ret = blocking_notifier_call_chain(&pm_chain_head, val, NULL);

	return notifier_to_errno(ret);
}
那谁会收到这类消息呢? 只有通过register_pm_notifier的设备,子系统会在这个时候处理自己的事情。
int register_pm_notifier(struct notifier_block *nb)
{
	return blocking_notifier_chain_register(&pm_chain_head, nb);
}
4. 调用suspend_freeze_processes冻结userhelper进程,已经内核线程。如果冻结出现失败,记录失败的引用计数。
5. 接着会通过通知链恢复suspend,已经恢复控制台。

suspend_devices_and_enter函数分析

/**
 * suspend_devices_and_enter - Suspend devices and enter system sleep state.
 * @state: System sleep state to enter.
 */
int suspend_devices_and_enter(suspend_state_t state)
{
	int error;
	bool wakeup = false;

	if (!sleep_state_supported(state))
		return -ENOSYS;

	error = platform_suspend_begin(state);
	if (error)
		goto Close;

	suspend_console();
	suspend_test_start();
	error = dpm_suspend_start(PMSG_SUSPEND);
	if (error) {
		pr_err("PM: Some devices failed to suspend, or early wake event detected\n");
		log_suspend_abort_reason("Some devices failed to suspend, or early wake event detected");
		goto Recover_platform;
	}
	suspend_test_finish("suspend devices");
	if (suspend_test(TEST_DEVICES))
		goto Recover_platform;

	do {
		error = suspend_enter(state, &wakeup);
	} while (!error && !wakeup && platform_suspend_again(state));

 Resume_devices:
	suspend_test_start();
	dpm_resume_end(PMSG_RESUME);
	suspend_test_finish("resume devices");
	trace_suspend_resume(TPS("resume_console"), state, true);
	resume_console();
	trace_suspend_resume(TPS("resume_console"), state, false);

 Close:
	platform_resume_end(state);
	return error;

 Recover_platform:
	platform_recover(state);
	goto Resume_devices;
}
1. 调用sleep_state_supported函数判断当前平台是否实现了suspend_ops,已经suspend_ops->enter函数。
2. 如果当前状态是freeze,就调用freeze_ops的begin函数。否则就调用平台相关的begin函数。这里的begin主要是各个平台pm的一些设置,每个平台的操作都不一样,这里不详细说明。
static int platform_suspend_begin(suspend_state_t state)
{
	if (state == PM_SUSPEND_FREEZE && freeze_ops && freeze_ops->begin)
		return freeze_ops->begin();
	else if (suspend_ops->begin)
		return suspend_ops->begin(state);
	else
		return 0;
}
3. 调用suspend_console挂起控制台,防止其它代码访问该控制台。
4. 调用suspend_test_start记录当前suspend刚开始的时候的时间,使用jiffies表示。
void suspend_test_start(void)
{
	/* FIXME Use better timebase than "jiffies", ideally a clocksource.
	 * What we want is a hardware counter that will work correctly even
	 * during the irqs-are-off stages of the suspend/resume cycle...
	 */
	suspend_test_start_time = jiffies;
}
5. 调用dpm_suspend_start函数,该函数主要是调用所有设备的prepare和suspend回调函数。如果出现suspend失败,则会打印"fail suspend"的log,以及调用platform_recover函数执行平台相关的recover回调。
static void platform_recover(suspend_state_t state)
{
	if (state != PM_SUSPEND_FREEZE && suspend_ops->recover)
		suspend_ops->recover();
}
6. 调用suspend_enter使整个系统进入suspend状态。

dpm_suspend_start函数分析

int dpm_suspend_start(pm_message_t state)
{
	int error;

	error = dpm_prepare(state);
	if (error) {
		suspend_stats.failed_prepare++;
		dpm_save_failed_step(SUSPEND_PREPARE);
	} else
		error = dpm_suspend(state);
	return error;
}
1. 调用dpm_prepare函数,执行所有设备的prepare回调函数。执行顺序是pm_domain-type-class-bus-driver,如果失败设置failed_prepare的引用计数值。
2. 调用dpm_suspend函数,执行所有设备的suspend回调函数。

dpm_prepare函数分析

/**
 * dpm_prepare - Prepare all non-sysdev devices for a system PM transition.
 * @state: PM transition of the system being carried out.
 *
 * Execute the ->prepare() callback(s) for all devices.
 */
int dpm_prepare(pm_message_t state)
{
	int error = 0;

	trace_suspend_resume(TPS("dpm_prepare"), state.event, true);
	might_sleep();

	mutex_lock(&dpm_list_mtx);
	while (!list_empty(&dpm_list)) {
		struct device *dev = to_device(dpm_list.next);

		get_device(dev);
		mutex_unlock(&dpm_list_mtx);

		error = device_prepare(dev, state);

		mutex_lock(&dpm_list_mtx);
		if (error) {
			if (error == -EAGAIN) {
				put_device(dev);
				error = 0;
				continue;
			}
			printk(KERN_INFO "PM: Device %s not prepared "
				"for power transition: code %d\n",
				dev_name(dev), error);
			put_device(dev);
			break;
		}
		dev->power.is_prepared = true;
		if (!list_empty(&dev->power.entry))
			list_move_tail(&dev->power.entry, &dpm_prepared_list);
		put_device(dev);
	}
	mutex_unlock(&dpm_list_mtx);
	trace_suspend_resume(TPS("dpm_prepare"), state.event, false);
	return error;
}
1.  判断dpm_list是否为空。那这个dpm_list是在哪里设置的呢? dpm_list是在device_add的时候调用device_pm_add函数,将当前的设备添加到dpm_list中的。
void device_pm_add(struct device *dev)
{
	pr_debug("PM: Adding info for %s:%s\n",
		 dev->bus ? dev->bus->name : "No Bus", dev_name(dev));
	mutex_lock(&dpm_list_mtx);
	if (dev->parent && dev->parent->power.is_prepared)
		dev_warn(dev, "parent %s should not be sleeping\n",
			dev_name(dev->parent));
	list_add_tail(&dev->power.entry, &dpm_list);
	mutex_unlock(&dpm_list_mtx);
}
2. 调用get_device增加设备的引用计数,然后调用device_prepare函数调用设备的prepare回调。如果失败减少设备的引用计数。
3. 设置该设备的is_prepared标志位,然后将该设备添加到dom_prepared_list链表中。

device_prepare函数分析

static int device_prepare(struct device *dev, pm_message_t state)
{
	int (*callback)(struct device *) = NULL;
	char *info = NULL;
	int ret = 0;

	if (dev->power.syscore)
		return 0;

	/*
	 * If a device's parent goes into runtime suspend at the wrong time,
	 * it won't be possible to resume the device.  To prevent this we
	 * block runtime suspend here, during the prepare phase, and allow
	 * it again during the complete phase.
	 */
	pm_runtime_get_noresume(dev);

	device_lock(dev);

	dev->power.wakeup_path = device_may_wakeup(dev);

	if (dev->pm_domain) {
		info = "preparing power domain ";
		callback = dev->pm_domain->ops.prepare;
	} else if (dev->type && dev->type->pm) {
		info = "preparing type ";
		callback = dev->type->pm->prepare;
	} else if (dev->class && dev->class->pm) {
		info = "preparing class ";
		callback = dev->class->pm->prepare;
	} else if (dev->bus && dev->bus->pm) {
		info = "preparing bus ";
		callback = dev->bus->pm->prepare;
	}

	if (!callback && dev->driver && dev->driver->pm) {
		info = "preparing driver ";
		callback = dev->driver->pm->prepare;
	}

	if (callback) {
		trace_device_pm_callback_start(dev, info, state.event);
		ret = callback(dev);
		trace_device_pm_callback_end(dev, ret);
	}

	device_unlock(dev);

	if (ret < 0) {
		suspend_report_result(callback, ret);
		pm_runtime_put(dev);
		return ret;
	}
	/*
	 * A positive return value from ->prepare() means "this device appears
	 * to be runtime-suspended and its state is fine, so if it really is
	 * runtime-suspended, you can leave it in that state provided that you
	 * will do the same thing with all of its descendants".  This only
	 * applies to suspend transitions, however.
	 */
	spin_lock_irq(&dev->power.lock);
	dev->power.direct_complete = ret > 0 && state.event == PM_EVENT_SUSPEND;
	spin_unlock_irq(&dev->power.lock);
	return 0;
}
此函数就是从设备的pm_domain, type, class,bus,driver一直调用下来。通常情况下就会调用到driver中的prepare函数中。

dpm_suspend函数分析

当对系统中的所有设备调用prepare回调函数之后,就会调用所有设备的suspend回调函数。
int dpm_suspend(pm_message_t state)
{
	ktime_t starttime = ktime_get();
	int error = 0;

	trace_suspend_resume(TPS("dpm_suspend"), state.event, true);
	might_sleep();

	cpufreq_suspend();

	mutex_lock(&dpm_list_mtx);
	pm_transition = state;
	async_error = 0;
	while (!list_empty(&dpm_prepared_list)) {
		struct device *dev = to_device(dpm_prepared_list.prev);

		get_device(dev);
		mutex_unlock(&dpm_list_mtx);

		error = device_suspend(dev);

		mutex_lock(&dpm_list_mtx);
		if (error) {
			pm_dev_err(dev, state, "", error);
			dpm_save_failed_dev(dev_name(dev));
			put_device(dev);
			break;
		}
		if (!list_empty(&dev->power.entry))
			list_move(&dev->power.entry, &dpm_suspended_list);
		put_device(dev);
		if (async_error)
			break;
	}
	mutex_unlock(&dpm_list_mtx);
	async_synchronize_full();
	if (!error)
		error = async_error;
	if (error) {
		suspend_stats.failed_suspend++;
		dpm_save_failed_step(SUSPEND_SUSPEND);
	} else
		dpm_show_time(starttime, state, NULL);
	trace_suspend_resume(TPS("dpm_suspend"), state.event, false);
	return error;
}
 对之前加入dpm_prepared_list链表的设备,调用device_suspend函数。然后该此设备又加入到dpm_suspend_list链表中。如果出现suspend失败,就打印log,更新failed_suspend的值。在调用到device_suspend函数中,会判断是否支持异步suspend操作,这里不关心细节,主要分析主流程,最后调用到__device_suspend函数中。

__device_suspend函数分析

static int __device_suspend(struct device *dev, pm_message_t state, bool async)
{
	pm_callback_t callback = NULL;
	char *info = NULL;
	int error = 0;
	struct timer_list timer;
	struct dpm_drv_wd_data data;
	char suspend_abort[MAX_SUSPEND_ABORT_LEN];
	DECLARE_DPM_WATCHDOG_ON_STACK(wd);

	dpm_wait_for_children(dev, async);

	if (async_error)
		goto Complete;

	/*
	 * If a device configured to wake up the system from sleep states
	 * has been suspended at run time and there's a resume request pending
	 * for it, this is equivalent to the device signaling wakeup, so the
	 * system suspend operation should be aborted.
	 */
	if (pm_runtime_barrier(dev) && device_may_wakeup(dev))
		pm_wakeup_event(dev, 0);

	if (pm_wakeup_pending()) {
		pm_get_active_wakeup_sources(suspend_abort,
			MAX_SUSPEND_ABORT_LEN);
		log_suspend_abort_reason(suspend_abort);
		async_error = -EBUSY;
		goto Complete;
	}

	if (dev->power.syscore)
		goto Complete;
	
	data.dev = dev;
	data.tsk = get_current();
	init_timer_on_stack(&timer);
	timer.expires = jiffies + HZ * 12;
	timer.function = dpm_drv_timeout;
	timer.data = (unsigned long)&data;
	add_timer(&timer);

	if (dev->power.direct_complete) {
		if (pm_runtime_status_suspended(dev)) {
			pm_runtime_disable(dev);
			if (pm_runtime_suspended_if_enabled(dev))
				goto Complete;

			pm_runtime_enable(dev);
		}
		dev->power.direct_complete = false;
	}

	dpm_watchdog_set(&wd, dev);
	device_lock(dev);

	if (dev->pm_domain) {
		info = "power domain ";
		callback = pm_op(&dev->pm_domain->ops, state);
		goto Run;
	}

	if (dev->type && dev->type->pm) {
		info = "type ";
		callback = pm_op(dev->type->pm, state);
		goto Run;
	}

	if (dev->class) {
		if (dev->class->pm) {
			info = "class ";
			callback = pm_op(dev->class->pm, state);
			goto Run;
		} else if (dev->class->suspend) {
			pm_dev_dbg(dev, state, "legacy class ");
			error = legacy_suspend(dev, state, dev->class->suspend,
						"legacy class ");
			goto End;
		}
	}

	if (dev->bus) {
		if (dev->bus->pm) {
			info = "bus ";
			callback = pm_op(dev->bus->pm, state);
		} else if (dev->bus->suspend) {
			pm_dev_dbg(dev, state, "legacy bus ");
			error = legacy_suspend(dev, state, dev->bus->suspend,
						"legacy bus ");
			goto End;
		}
	}

 Run:
	if (!callback && dev->driver && dev->driver->pm) {
		info = "driver ";
		callback = pm_op(dev->driver->pm, state);
	}

	error = dpm_run_callback(callback, dev, state, info);

 End:
	if (!error) {
		struct device *parent = dev->parent;

		dev->power.is_suspended = true;
		if (parent) {
			spin_lock_irq(&parent->power.lock);

			dev->parent->power.direct_complete = false;
			if (dev->power.wakeup_path
			    && !dev->parent->power.ignore_children)
				dev->parent->power.wakeup_path = true;

			spin_unlock_irq(&parent->power.lock);
		}
	}

	device_unlock(dev);
	dpm_watchdog_clear(&wd);

	del_timer_sync(&timer);
	destroy_timer_on_stack(&timer);

 Complete:
	complete_all(&dev->power.completion);
	if (error)
		async_error = error;

	return error;
}
1.  调用dpm_wait_for_children使用异步等待该设备的所有孩子就绪。
2.  如果此时有wakup事件发生,应该停止系统suspend。
3.  如果没有wakup事件发生,创建一个12s的定时器,然后启动定时器。如果在12s之内suspend没有处理完成,就打印call stack,导致系统panic。
static void dpm_drv_timeout(unsigned long data)
{
	struct dpm_drv_wd_data *wd_data = (void *)data;
	struct device *dev = wd_data->dev;
	struct task_struct *tsk = wd_data->tsk;

	printk(KERN_EMERG "**** DPM device timeout: %s (%s)\n", dev_name(dev),
	       (dev->driver ? dev->driver->name : "no driver"));

	printk(KERN_EMERG "dpm suspend stack:\n");
	show_stack(tsk, NULL);

	BUG();
}
4. 判断该设备是否在suspend之前已经发生了runtime_suspend。如果该设备已经处于suspend则可以直接返回。
5. 依次调用subsystem-level(pm_domain, type, class, bus)级别的suspend回调函数,如果subsystem-level级别的suspend回调函数都没有实现,则调用driver的suspend回调。
6. 销毁之前创建的定时器。

suspend_enter函数分析

在之前对dpm_suspend_start函数进行了分析,该函数中主要是调用所有设备的prepare和suspend回调函数。而在suspend_enter主要是使系统进入到suspend中。
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	char suspend_abort[MAX_SUSPEND_ABORT_LEN];
	int error, last_dev;

	error = platform_suspend_prepare(state);
	if (error)
		goto Platform_finish;

	error = dpm_suspend_late(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		printk(KERN_ERR "PM: late suspend of devices failed\n");
		log_suspend_abort_reason("%s device failed to power down",
			suspend_stats.failed_devs[last_dev]);
		goto Platform_finish;
	}
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_early_resume;

	error = dpm_suspend_noirq(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		printk(KERN_ERR "PM: noirq suspend of devices failed\n");
		log_suspend_abort_reason("noirq suspend of %s device failed",
			suspend_stats.failed_devs[last_dev]);
		goto Platform_early_resume;
	}
	error = platform_suspend_prepare_noirq(state);
	if (error)
		goto Platform_wake;

	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	/*
	 * PM_SUSPEND_FREEZE equals
	 * frozen processes + suspended devices + idle processors.
	 * Thus we should invoke freeze_enter() soon after
	 * all the devices are suspended.
	 */
	if (state == PM_SUSPEND_FREEZE) {
		trace_suspend_resume(TPS("machine_suspend"), state, true);
		freeze_enter();
		trace_suspend_resume(TPS("machine_suspend"), state, false);
		goto Platform_wake;
	}

	error = disable_nonboot_cpus();
	if (error || suspend_test(TEST_CPUS)) {
		log_suspend_abort_reason("Disabling non-boot cpus failed");
		goto Enable_cpus;
	}

	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());

	error = syscore_suspend();
	if (!error) {
		*wakeup = pm_wakeup_pending();
		if (!(suspend_test(TEST_CORE) || *wakeup)) {
			trace_suspend_resume(TPS("machine_suspend"),
				state, true);
			error = suspend_ops->enter(state);
			trace_suspend_resume(TPS("machine_suspend"),
				state, false);
			events_check_enabled = false;
		} else if (*wakeup) {
			pm_get_active_wakeup_sources(suspend_abort,
				MAX_SUSPEND_ABORT_LEN);
			log_suspend_abort_reason(suspend_abort);
			error = -EBUSY;
		}
		syscore_resume();
	}

	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());

 Enable_cpus:
	enable_nonboot_cpus();

 Platform_wake:
	platform_resume_noirq(state);
	dpm_resume_noirq(PMSG_RESUME);

 Platform_early_resume:
	platform_resume_early(state);

 Devices_early_resume:
	dpm_resume_early(PMSG_RESUME);

 Platform_finish:
	platform_resume_finish(state);
	return error;
}
1.  调用平台相关prepare回调函数,如果平台prepare设置失败,在调用平台相关的finish回调函数。
2.  调用dpm_suspend_late函数。此函数主要调用dpm_suspend_list中的设备的suspend_late回调函数,然后又将这些设备加入到dpm_late_early_list链表中。如果出现失败,则跳到platform_finish做恢复工作。
3.  如果当前休眠状态是PM_SUSPEND_FREEZE的话,调用freeze_ops中的prepare回调。
4.  调用dpm_suspend_noirq函数,此函数主要是从dpm_late_early_list链表中取一个设备,然后调用该设备的suspend_noirq回调,同时将该设备加入到dpm_noirq_list链表中。
5.  回调平台相关的preplate_late函数,做suspend最后关头的事情。
6.  如果休眠状态是PM_SUSPEND_FREEZE,则frozen processes + suspended devices + idle processors
7.  disable所有非nonboot的CPU,失败之后启动CPU。
8.  关掉全局cpu中断,如果关掉中断,则报BUG
	arch_suspend_disable_irqs();
	BUG_ON(!irqs_disabled());
9.  执行所有system core的suspend回调函数。
10.  如果执行成功的话,这时候系统还会调用pm_wakeup_pending检查下,是否有唤醒事件发生,如果发生停止suspend,恢复系统。
11.  这时候系统已经睡眠,如果这时候有唤醒事件发生,比如按下手机的power按键,系统又会接着suspend的地方,再次往下执行。也就是suspend的一些列反操作。

suspend/resume过程总结

如下是suspend/resume过程的简图


以上就是整个系统的suspend/resume执行过程,但是对于一般的驱动开发工程师来说主要关心的是Device Suspend和Device Resume过程。
suspend:  prepare->suspend->suspend_late->suspend_noirq
resume: resume_noirq->resume_early->resume->complete



作者:longwang155069 发表于2016/10/27 17:10:48 原文链接
阅读:37 评论:0 查看评论

Android 自定义View练习:雷达图(比重)绘制

$
0
0

这里写图片描述

code:

package com.louisgeek.louiscustomviewstudy;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;

import static android.content.ContentValues.TAG;

/**
 * Created by louisgeek on 2016/10/27.
 */

public class RadarView08 extends View {
    private Paint mPaint;
    private int mViewWidth, mViewHeight,mScreenWidth,mScreenHeight;
    /**
     *文字与边缘点的距离
     */
    private final float mText_ContentDisance = 10;
    /**
     *线的粗细
    */
    private  int mLinesWidth = this.dp2px(1);

    //顺时针  对应的数据
    // private float[] mDataValue={1f,1f,1f,1f,0.8f,0.6f,0.4f,1f,0.4f,1f};//10
    private float[] mDataValue = {1f, 0.1f, 1f, 1f, 0.8f, 0.6f, 0.4f, 0.4f};//8
    //private float[] mDataValue={1f,0.8f,0.6f,0.4f,1f,0.4f};//6
    // private float[] mDataValue={1f,0.8f,0.6f,0.4f};//4
    // private float[] mDataValue={1f,1f,1f};//3
    private String[] mDataName = {"豌豆荚", "应用宝", "百度91安卓", "安智市场", "小米应用商店", "OPPO应用商店", "魅族应用商店", "360手机助手", "华为应用市场", "移动MM商店"};

    /**
     * 最大形状的半径
     */
   private int mRadius = this.dp2px(50);//max
    /**
     * 小圆点半径
     */
    private int mPointRadius = this.dp2px(2);
    private int mPointColor = Color.GRAY;

    private int mContentColor=Color.BLUE;
    private int mLinesColor=Color.LTGRAY;
    private int mTextColor=Color.RED;
    private int mTextSize=this.dp2px(14);
    /**
     * 角度
     */
    private int mDegreesAngle;
    private int mContentAlpha=150;//must in (0-225)
    //点的数量
    private int mPointsCount;

    private Paint mPaintText=new Paint();

    private  Context mContext;

    public RadarView08(Context context) {
        this(context, null);
    }

    public RadarView08(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RadarView08(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext=context;
        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.RadarView08);
       mLinesWidth = ta.getDimensionPixelOffset(R.styleable.RadarView08_linesWidth, mLinesWidth);
        mRadius = ta.getDimensionPixelOffset(R.styleable.RadarView08_maxCircleRadius, mRadius);
        mPointRadius = ta.getDimensionPixelOffset(R.styleable.RadarView08_pointRadius, mPointRadius);
        mContentColor = ta.getColor(R.styleable.RadarView08_contentColor, mContentColor);
        mPointColor = ta.getColor(R.styleable.RadarView08_pointColor, mPointColor);
        mLinesColor = ta.getColor(R.styleable.RadarView08_linesColor, mLinesColor);
        mContentAlpha = ta.getInteger(R.styleable.RadarView08_contentAlpha, mContentAlpha);
        mTextColor = ta.getColor(R.styleable.RadarView08_titleColor, mTextColor);
        mTextSize = ta.getDimensionPixelOffset(R.styleable.RadarView08_titleTextSize, mTextSize);
        //
        ta.recycle();
        init();
    }

    private void init() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.LTGRAY);
        mPaint.setStrokeWidth(mLinesWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        //
        mPaintText=new Paint();
        mPaintText.setColor(mTextColor);
        mPaintText.setTextSize(mTextSize);
        mPaintText.setAntiAlias(true);
        mPaintText.setStyle(Paint.Style.FILL);


        //must in (0-225)
        mContentAlpha=mContentAlpha<0?0:mContentAlpha;
        mContentAlpha=mContentAlpha>255?255:mContentAlpha;
        Log.d(TAG, "init: mContentAlpha:"+mContentAlpha);
        //
        mPointsCount = mDataValue.length;
        mDegreesAngle = 360 / mPointsCount;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         /*mWidth =getMeasuredWidth();
         mHeight = getMeasuredHeight();*/

//        mScreenWidth=getScreenWidth(mContext);
//        mScreenHeight=getScreenHeight(mContext);
       /* int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);*/



        int maxLen=0;
        String maxLenName=mDataName[0];
        for (int i = 0; i < mDataValue.length; i++) {
           int len= mDataName[i].length();
            if (len>maxLen){
                maxLen=len;
                maxLenName=mDataName[i];
            }

        }
        float fixWidth_Height=mPaintText.measureText(maxLenName);
        /**
         * 实际的内容宽和高
         */
        int contentWidthSize = (int) (fixWidth_Height*2+mRadius*2+Math.max(mPointRadius,mLinesWidth)*1.0f/2+this.getPaddingLeft()+this.getPaddingRight());
        int contentHeightSize = (int) (fixWidth_Height*2+mRadius*2+Math.max(mPointRadius,mLinesWidth)*1.0f/2+this.getPaddingTop()+this.getPaddingBottom());


        //getDefaultSize()
        int width = resolveSize(contentWidthSize, widthMeasureSpec);
        int height = resolveSize(contentHeightSize, heightMeasureSpec);

        /**
         * 修正xml设置太小的情况
         */
        width=width<contentWidthSize?contentWidthSize:width;
        height=height<contentHeightSize?contentHeightSize:height;

        /**
         *
         */
        setMeasuredDimension(width, height);

        mViewWidth = width;
        mViewHeight = height;


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //
        canvas.translate(mViewWidth/ 2, mViewHeight / 2);

        if (mPointsCount > 10 || mPointsCount < 3) {
            Log.e(TAG, "onDraw: 只能是3~10个数据");
            return;
        }
        drawFramesAndLines(canvas);
        //
        drawContent(canvas);

        drawTexts(canvas);
    }

    private void drawContent(Canvas canvas) {
        Paint paintContent = new Paint();
        //paintContent.setColor(mContentColor);
        paintContent.setAntiAlias(true);
        paintContent.setStyle(Paint.Style.FILL);
        paintContent.setStrokeCap(Paint.Cap.ROUND);
        Path path = new Path();

        for (int i = 0; i < mPointsCount; i++) {
            //!!!加上原有占width的笔触  mPointsCount-1条线 mLineWidth/2*(mPointsCount-1)
            float nowContentDisance = (mRadius - mLinesWidth / 2) * mDataValue[i];
            if (i == 0) {
                path.moveTo(nowContentDisance, 0);
                //绘制小圆点
                paintContent.setColor(mPointColor);
                canvas.drawCircle(nowContentDisance, 0, mPointRadius, paintContent);
            } else {
                float nowDegreesAngle = mDegreesAngle * i;
                double nowRadiansAngle = Math.toRadians(nowDegreesAngle);
                //sinA=对边/Radius
                //cosA=领边/Radius
                float tempX = (float) (nowContentDisance * Math.cos(nowRadiansAngle));
                float tempY = (float) (nowContentDisance * Math.sin(nowRadiansAngle));
                path.lineTo(tempX, tempY);
                //绘制小圆点
                paintContent.setColor(mPointColor);
                canvas.drawCircle(tempX, tempY, mPointRadius, paintContent);
            }
        }
        paintContent.setColor(mContentColor);
        paintContent.setAlpha(mContentAlpha);
        canvas.drawPath(path, paintContent);
    }

    /**
     * 绘制文字
     */
    private void drawTexts(Canvas canvas) {
       /* Paint.FontMetrics fontMetrics = mPaint.getFontMetrics();
        float fontHeight = fontMetrics.descent - fontMetrics.ascent;*/

        /**
         * 参考圆
         */
        //###canvas.drawCircle(0,0,mRadius+mText_ContentDisance,mPaint);

        for (int i = 0; i < mPointsCount; i++) {
            if (i == 0) {
                //获取文字度量信息
                Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
                float textHeight = fontMetrics.descent - fontMetrics.ascent;
                float baseLineYDis = textHeight / 2 - Math.abs(fontMetrics.descent);
                float tempXFix = mText_ContentDisance;
                float tempYFix = baseLineYDis;
                canvas.drawText(mDataName[i], mRadius + tempXFix, 0 + tempYFix, mPaintText);
            } else {
                float nowDegreesAngle = mDegreesAngle * i;
                double nowRadiansAngle = Math.toRadians(nowDegreesAngle);
                //sinA=对边/Radius
                //cosA=领边/Radius
                float tempX = (float) (mRadius * Math.cos(nowRadiansAngle));
                float tempY = (float) (mRadius * Math.sin(nowRadiansAngle));
                //
               /* Rect rect_bounds=new Rect();
                paintText.getTextBounds(mDataName[i],0,mDataName[i].length(),rect_bounds);
                int textWidth=rect_bounds.width();
                int textHeight=rect_bounds.height();*/
                //获取文字度量信息
                Paint.FontMetrics fontMetrics = mPaintText.getFontMetrics();
                float textHeight = fontMetrics.descent - fontMetrics.ascent;
                float textWidth = mPaint.measureText(mDataName[i]);

                float baseLineYDis = textHeight / 2 - Math.abs(fontMetrics.descent);
                float tempXFix;
                float tempYFix;
                if (nowDegreesAngle > 0 && nowDegreesAngle <= 90) {
                    tempXFix = mText_ContentDisance - textWidth / 2;
                    tempYFix = mText_ContentDisance + textHeight / 2 + baseLineYDis;
                    canvas.drawText(mDataName[i], tempX + tempXFix, tempY + tempYFix, mPaintText);
                } else if (nowDegreesAngle > 90 && nowDegreesAngle < 180) {
                    tempXFix = mText_ContentDisance + textWidth;
                    tempYFix = mText_ContentDisance + textHeight / 2 + baseLineYDis;
                    canvas.drawText(mDataName[i], tempX - tempXFix, tempY + tempYFix, mPaintText);
                } else if (nowDegreesAngle == 180) {
                    //###tempXFix=mText_ContentDisance+textWidth;//这里感觉计算不准 改变计算方式弥补下
                    /** 换个计算方法*/
                    Rect rectBounds = new Rect();
                    mPaintText.getTextBounds(mDataName[i], 0, mDataName[i].length(), rectBounds);
                    int textNewWidth = rectBounds.width();
                    tempXFix = mText_ContentDisance + textNewWidth;
                    tempYFix = baseLineYDis;
                    //mRadius
                    canvas.drawText(mDataName[i], -mRadius - tempXFix, 0 + tempYFix, mPaintText);
                } else if (nowDegreesAngle > 180 && nowDegreesAngle <= 270) {
                    tempXFix = mText_ContentDisance + textWidth;
                    tempYFix = mText_ContentDisance + textHeight / 2 - baseLineYDis;
                    canvas.drawText(mDataName[i], tempX - tempXFix, tempY - tempYFix, mPaintText);
                } else if (nowDegreesAngle > 270 && nowDegreesAngle <= 360) {
                    tempXFix = mText_ContentDisance - textWidth / 2;
                    tempYFix = mText_ContentDisance + textHeight / 2 - baseLineYDis;
                    canvas.drawText(mDataName[i], tempX + tempXFix, tempY - tempYFix, mPaintText);
                }
            }
        }


    }

    private void drawFramesAndLines(Canvas canvas) {
        Path path = new Path();
        int mDisance = mRadius / (mPointsCount - 1);
        /**
         * 循环出多个正六边形
         */
        for (int j = 0; j < mPointsCount - 1; j++) {
            float nowRadius = mDisance * ((mPointsCount - 1) - j);

            /**
             * 画每一个正六边形
             */
            for (int i = 0; i < mPointsCount; i++) {
                // path.reset();
                /**
                 * 顺时针  从最右边画
                 */
                if (i == 0) {
                    path.moveTo(nowRadius, 0);

                    /**
                     * 画从中点到最右边点的线
                     */
                    Path pathLines = new Path();
                    pathLines.lineTo(nowRadius, 0);
                    mPaint.setColor(mLinesColor);
                    canvas.drawPath(pathLines, mPaint);
                } else {
                    float nowDegreesAngle = mDegreesAngle * i;
                    double nowRadiansAngle = Math.toRadians(nowDegreesAngle);
                    //sinA=对边/Radius
                    //cosA=领边/Radius
                    float tempX = (float) (nowRadius * Math.cos(nowRadiansAngle));
                    float tempY = (float) (nowRadius * Math.sin(nowRadiansAngle));
                    path.lineTo(tempX, tempY);
                    /**
                     * 画从当前点到中点的线
                     */
                    Path pathLines = new Path();
                    pathLines.moveTo(tempX, tempY);
                    pathLines.lineTo(0, 0);
                    mPaint.setColor(mLinesColor);
                    canvas.drawPath(pathLines, mPaint);
                }

            }
            //
            path.close();
            //
            canvas.drawPath(path, mPaint);
        }
    }

    //
    public int dp2px(int dpValue) {
        int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
        return px;
    }
    //获取屏幕的宽度
    public static int getScreenWidth(Context context) {
        Resources resources = context.getResources();
        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
        float density = displayMetrics.density;
        int width = displayMetrics.widthPixels;
        int height = displayMetrics.heightPixels;
        return width;
    }

    //获取屏幕的高度
    public static int getScreenHeight(Context context) {
        Resources resources = context.getResources();
        DisplayMetrics displayMetrics = resources.getDisplayMetrics();
        float density = displayMetrics.density;
        int width = displayMetrics.widthPixels;
        int height = displayMetrics.heightPixels;
        return height;
    }
}
作者:RichieZhu 发表于2016/10/27 17:11:33 原文链接
阅读:46 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>