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

流式布局实现选择标签页小实例

$
0
0

参考Android-教你自作一个简单而又实用的流式Tag标签布局一文实现了流式布局的效果,支持单选,多选。这篇文章写的很好。

在这篇文章中作者对每个类的主要方法和设计思路进行了说明。实例代码地址

我通过对实例代码稍微修改实现我自己需要的选择标签页面,包含热门标签和其他标签,只能选择一个标签。

实现的效果图:

            图1                                                                                                      图2

                    

操作步骤:

1 将FlowTag-master项目中library下的FlowTagLayout.java ,OnInitSelectedPosition.java ,OnTagClickListener.java ,OnTagSelectListener.java复制到项目中。

2 复制FlowTag-master项目中的TagAdapter.java文件到项目中,getView方法:

@Override
    public View getView(int position, View convertView, ViewGroup parent) {

        View view = LayoutInflater.from(mContext).inflate(R.layout.tag_item, null);

        TextView textView = (TextView) view.findViewById(R.id.tv_tag);
        T t = mDataList.get(position);

        if (t instanceof String) {
            textView.setText((String) t);
        }
        return view;
    }

如果T类型是String,项目中的TagAdapter.java类中getView方法不需要修改,如果T是自定义类,需要在getView方法中增加判断语句,如下:

 if (t instanceof String) {
     textView.setText((String) t);
 }else if(t instanceof LabelInfo){  //其中LabelInfo为定义的实体类
     textView.setText(((LabelInfo)t).stagename);
 }

拷贝TagAdapter.java中引用的布局文件,同时将round_rectangle_bg.xml资源拷贝到res/drawable目录,和颜色资源normal_text_color.xml拷贝到res/color目录下。

3 定义实体类LabelInfo.java

public class LabelInfo implements Serializable {
    public String stagename;
    public String id;
    public int ishot;
}

4 我想实现的效果是展示热门标签FlowTagLayout和其他标签FlowTagLayout,其中一次只能单选一个标签。所以需要在选择热门标签的点击事件中重置其他标签所有项为不选中状态;在选择其他标签的点击事件中重置所有热门标签为不选中状态。在FlowTagLayout.java中增加以下方法:

    /**
     * 重置所有项为未选中状态
     */
    public void resetData(){
        for (int k = 0; k< mAdapter.getCount(); k++) {
           mCheckedTagArray.put(k, false);
           getChildAt(k).setSelected(false);
        }
    }

5 当下次打开选择标签页时,增加设置默认选中项的代码,如下:

    /**
     * 设置选中项
     */
    public void setDefaultSelection(int position){
        for (int k = 0; k< mAdapter.getCount(); k++) {
            if(k ==position){
               mCheckedTagArray.put(k, true);
               getChildAt(k).setSelected(true);
            }else{
               mCheckedTagArray.put(k, false);
               getChildAt(k).setSelected(false);
            }
        }
}

6 FlowTag项目中默认选中第1项,如果修改为默认不选择,对FlowTagLayout.java文件的reloadData方法中

mCheckedTagArray.put(i,true);

childView.setSelected(true);这段代码修改为:

if(mTagCheckMode == FLOW_TAG_CHECKED_SINGLE) {
      //单选只有第一次起作用
      if (isSelected &&!isSetted) {
           mCheckedTagArray.put(i,false);
           childView.setSelected(false);
           isSetted = true;
      }
}

7 增加选择标签页SelectLabelActivity.java

当选择热门标签,其他标签的FlowTagLayout对象调用resetData重置所有其他项为未选中状态;同样当选择其他标签,重置所有热门标签项为未选中状态。

SelectLabelActivity.java:

/**
 * @description 选择阶段页面
 */
public class SelectLabelActivity extends Activity {

    private RelativeLayout rl_top;
    private RelativeLayout rl_center;
    private LayoutInflater mInflater;
    private String nodelabelid;
    private String nodelabelname;
    private FlowTagLayout mFlowTagLayout_hot;  //热门
    private FlowTagLayout mFlowTagLayout_other;  //其他
    private TagAdapter<LabelInfo> mTagAdapterHot = new TagAdapter<LabelInfo>(this);  //热门adapter
    private TagAdapter<LabelInfo> mTagAdapterOther = new TagAdapter<LabelInfo>(this);  //其他adapter

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_selectlabel);
        nodelabelid = getIntent().getStringExtra("selectlabelid");
        initView();
        //初始化测试数据
        initTestData();
        showLabels();
        mInflater = LayoutInflater.from(this);
    }

    List<LabelInfo> mLabelInfos = new ArrayList<>();
    List<LabelInfo> mHotLabels = new ArrayList<LabelInfo>();
    List<LabelInfo> mOtherLabels = new ArrayList<LabelInfo>();

    private void initTestData(){
        for(int i = 0; i < 10; i++){
            LabelInfo info = new LabelInfo();
            info.id = i+"";
            info.stagename = "the number is"+i;
            if(i%2 == 0){
                info.ishot = 1;
            }else{
                info.ishot = 0;
            }
            mLabelInfos.add(info);
        }
    }

    /**
     * 显示标签数据
     */
    private void showLabels() {
        for(LabelInfo i : mLabelInfos){
            if (i.ishot == 1){
                mHotLabels.add(i);
            }else{
                mOtherLabels.add(i);
            }
        }
        if(mHotLabels.isEmpty()){
            rl_top.setVisibility(View.GONE);
        }else{
            rl_top.setVisibility(View.VISIBLE);
            mTagAdapterHot.onlyAddAll(mHotLabels);
        }
        if(mOtherLabels.isEmpty()){
            rl_center.setVisibility(View.GONE);
        }else{
            rl_center.setVisibility(View.VISIBLE);
            mTagAdapterOther.onlyAddAll(mOtherLabels);
        }
        for (int i = 0; i < mHotLabels.size(); i++){
            if(nodelabelid != null && mHotLabels.get(i).id.equals(nodelabelid)){
                mFlowTagLayout_hot.setDefaultSelection(i);
                Log.d("testtest", "ii --- "+i);
                break;
            }
        }
        for(int i = 0; i < mOtherLabels.size(); i++){
            if(nodelabelid != null && mOtherLabels.get(i).id.equals(nodelabelid)){
                mFlowTagLayout_other.setDefaultSelection(i);
                Log.d("testtest", "ii2 --- "+i);
                break;
            }
        }
    }
    private void initView() {
        rl_top = (RelativeLayout) findViewById(R.id.rl_top);
        rl_center = (RelativeLayout) findViewById(R.id.rl_center);


        mFlowTagLayout_hot = (FlowTagLayout)findViewById(R.id.fl_hot);
        mFlowTagLayout_hot.setTagCheckedMode(FlowTagLayout.FLOW_TAG_CHECKED_SINGLE);
        mFlowTagLayout_hot.setAdapter(mTagAdapterHot);
        mFlowTagLayout_hot.setOnTagSelectListener(new OnTagSelectListener() {
            @Override
            public void onItemSelect(FlowTagLayout parent, List<Integer> selectedList) {
                if (selectedList != null && selectedList.size() > 0) {
                    StringBuilder sb = new StringBuilder();
                    for (int i : selectedList) {
                        LabelInfo info = (LabelInfo)parent.getAdapter().getItem(i);
                        if(info != null){
                            nodelabelid = info.id;
                            nodelabelname = info.stagename;
                        }
                    }
                } else {
                }
                //其他标签清除选中
                mFlowTagLayout_other.resetData();
            }
        });

        mFlowTagLayout_other = (FlowTagLayout)findViewById(R.id.fl_other);
        mFlowTagLayout_other.setTagCheckedMode(FlowTagLayout.FLOW_TAG_CHECKED_SINGLE);
        mFlowTagLayout_other.setAdapter(mTagAdapterOther);
        mFlowTagLayout_other.setOnTagSelectListener(new OnTagSelectListener() {
            @Override
            public void onItemSelect(FlowTagLayout parent, List<Integer> selectedList) {
                if (selectedList != null && selectedList.size() > 0) {
                    StringBuilder sb = new StringBuilder();
                    for (int i : selectedList) {
                        LabelInfo label = (LabelInfo)parent.getAdapter().getItem(i);
                        if(label != null)  {
                            nodelabelid = label.id;
                            nodelabelname = label.stagename;
                        }
                    }
                } else {
                }
                //热门标签清除选中
                mFlowTagLayout_hot.resetData();
            }
        });
    }


布局文件activity_selectlabel.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    style="@style/match"
    android:background="#fff"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        style="@style/match"
        android:orientation="vertical">
        <RelativeLayout
            android:id="@+id/rl_top"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="25dp">
            <RelativeLayout
                android:id="@+id/rl_top_center"
                style="@style/wrap"
                android:layout_centerHorizontal="true">
                <ImageView
                    android:id="@+id/iv_labelicon"
                    android:layout_width="15dp"
                    android:layout_height="15dp"
                    android:src="@drawable/choose_lable_hot"
                    android:scaleType="fitXY"/>
                <TextView
                    style="@style/wrap"
                    android:text="水电施工阶段热门标签"
                    android:textSize="12sp"
                    android:layout_centerVertical="true"
                    android:layout_marginLeft="5dp"
                    android:layout_toRightOf="@id/iv_labelicon"
                    android:layout_toEndOf="@id/iv_labelicon"/>
            </RelativeLayout>
            <View android:id="@+id/view_left"
                  android:layout_width="match_parent"
                  android:layout_height="1dp"
                  android:background="#DBDBDB"
                  android:layout_centerVertical="true"
                  android:layout_marginRight="20dp"
                  android:layout_marginLeft="5dp"
                  android:layout_toLeftOf="@id/rl_top_center"/>
            <View
                android:id="@+id/view_right"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#DBDBDB"
                android:layout_centerVertical="true"
                android:layout_marginRight="5dp"
                android:layout_marginLeft="20dp"
                android:layout_toRightOf="@id/rl_top_center"/>
        </RelativeLayout>
        <com.androidpractice.myflowlayout_master.flowlayout.FlowTagLayout
            style="@style/wrap"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:id="@+id/fl_hot">
        </com.androidpractice.myflowlayout_master.flowlayout.FlowTagLayout>

        <RelativeLayout
            android:id="@+id/rl_center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp">
            <RelativeLayout
                android:id="@+id/rl_m_center"
                style="@style/wrap"
                android:layout_centerHorizontal="true">
                <ImageView
                    android:id="@+id/iv_othericon"
                    android:layout_width="15dp"
                    android:layout_height="15dp"
                    android:background="@drawable/choose_label_other"
                    android:scaleType="fitXY"
                    android:contentDescription="other description"/>
                <TextView
                    style="@style/wrap"
                    android:text="其他标签"
                    android:textSize="12sp"
                    android:layout_centerVertical="true"
                    android:layout_toRightOf="@id/iv_othericon"
                    android:layout_marginLeft="5dp"
                    android:layout_toEndOf="@id/iv_othericon"/>
            </RelativeLayout>
            <View android:id="@+id/view_m_left"
                  android:layout_width="match_parent"
                  android:layout_height="1dp"
                  android:background="#DBDBDB"
                  android:layout_centerVertical="true"
                  android:layout_marginRight="20dp"
                  android:layout_marginLeft="5dp"
                  android:layout_toLeftOf="@id/rl_m_center"/>
            <View
                android:id="@+id/view_m_right"
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="#DBDBDB"
                android:layout_centerVertical="true"
                android:layout_marginRight="5dp"
                android:layout_marginLeft="20dp"
                android:layout_toRightOf="@id/rl_m_center"/>
        </RelativeLayout>

        <com.androidpractice.myflowlayout_master.flowlayout.FlowTagLayout
            android:id="@+id/fl_other"
            android:layout_marginTop="10dp"
            style="@style/wrap"
            android:layout_marginBottom="10dp">
        </com.androidpractice.myflowlayout_master.flowlayout.FlowTagLayout>
    </LinearLayout>

</ScrollView>
res/values/styles.xml文件添加样式:
<style name="match">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">match_parent</item>
    </style>
    <style name="wrap">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
    </style>



作者:hxltech 发表于2016/10/18 10:31:40 原文链接
阅读:37 评论:0 查看评论

Mock+Proxy在SDK项目的自动化测试实战

$
0
0

项目背景


广告SDK项目是为应用程序APP开发人员提供移动广告平台接入的API程序集合,其形态就是一个植入宿主APP的jar包。提供的功能主要有以下几点:
- 为APP请求广告内容
- 用户行为打点
- 错误日志打点
- 反作弊

团队现状


在项目推进的过程中,逐渐暴露了一些问题:
1. 项目团队分为上海团队(服务端)和北京团队(客户端),由于信息同步,人力资源等其他原因,服务端与客户端的开发进度很难保持同步,经常出现客户端等着和服务端联调的情况
2. 接口文档不稳定,理解有偏差
3. 协议变化频繁,消息不同步
4. 缺少服务端测试环境,可模拟的真实广告内容太少
5. 协议字段太多,传统的测试用例设计方法容易出现遗漏,尤其是异常情况处理,测试完成以后测试人员对字段覆盖率没有信心

协议字段示例图

{
     "ads": [{
        "action": {  "path": "" },
        "adw":920, 
        "adh":900, 
        "template": "",
        "action_type": 2,
        "adid": "67346778",
        "adm": "",
        "adm_type": 0,
        "deep_link": "",
        "impid": "nXcM_kqBGqL=",
        "tk_act": [""],
        "tk_imp":[ ""],
        "tk_ad_close": [""],
        "tk_clk": [""],
        "tk_dl_begin": ["" ],
        "tk_dl_btn": [ ""],
        "tk_dl_done": [""],
        "tk_dp_suc": [],
        "tk_ins": [ ""],
         "tk_open": [""]
       }],
        "errno": "0"
}
  1. 测试用例设计极容易受需求影响,更新起来非常麻烦,成本很高
  2. 手工测试方法执行效率低,且容易漏测

手动测试过程示意图
这里写图片描述

分析&思路


上述几个问题,其中1、2、3都会对我们的测试工作产生影响,但是属于项目管理范畴,不在本文讨论范围内。那么针对4、5、6、7几个问题,应该如何解决呢?
首先分析一下问题:

Q: 缺少服务端测试环境,可模拟的真实广告内容太少
A: 由于服务端团队在人力上的不足,无法为我们提供测试环境,通过沟通协商,方法暂定为由服务端同事预先配置好线上广告物料,即固定的线上广告资源,能够覆盖提测的广告类型,在服务端完成功能逻辑之前,先利用mock方式测试客户端的功能逻辑以及展示,此时客户端和服务端后台无需交互。

Q: 协议字段太多,传统的测试用例设计方法容易出现遗漏,尤其是异常情况处理
A: 制定一个可靠的测试用例设计策略,以最少的case覆盖最多的情况。

Q:测试用例设计极容易受需求影响,变更起来非常麻烦,成本很高
A: 对测试用例进行拆分,分为正常返回情况和异常处理两部分。正常的处理包括系统环境、网络切换、下载、轮播、缓存、正常打点、安装卸载、UI检查等等需要人工检查的情况,因此这部分我们先梳理checklist,先组内review,再约产品和研发一起review,确保需求的完整性,另外开发过程中的需求变更是不可避免的,对于需求的变化要做到实时更新case,这部分case覆盖的点要足够全,而文字描述要尽量的精简,确保更新起来能快速响应节奏的变化。而异常的部分,我们的做法是批量自动化生成case,生成策略会在下面详细描述。

Q:手工测试方法效率低,且容易漏测
A: 正常的功能我们通过手工测试的方法覆盖,而对于客户端拿到的异常情况的error code要有全量的覆盖,比如我们的错误代码约定了21种,那么针对所有可能出现的错误代码都要想办法触发,这一部分工作希望从case生成到用例执行能100%的自动化实现。

调研过程


有了解决思路,那么需要想办法把想法落地。我们提炼出几个需要攻克的技术难点:

  • 难点一:mock框架选型
    做过单元测试的同学应该了解“桩(stub)测试”,即通过hard code方式验证函数的输入输出是否准确和健壮,而mock测试和桩测试类似,功能要更加丰富一些,可以模拟产品交互环节中的部分场景,换句话说,可以让测试工作提前介入研发流程中。多用于需要联调的环节,比如支付场景,购买流程,第三方插件调用等等业务。之前我们采用的Fiddler重定向请求结果到本地文件的方式模拟服务端的response来欺骗客户端,也可以理解为mock测试。
    最初我们计划自己写一个proxy server监听指定端口,截取所有的http/https请求,再替换response内容完成mock测试,后来一次偶然的机会接触了阿里开源出来的anyproxy(http://anyproxy.io/cn/),了解了一下该工具,发现这款工具刚好满足了我们的几个需求:

    • 代码开源
    • 规则可定制
    • 支持https可视化
    • 易部署、学习成本低
    • UI可视化,类似fiddler

    代理示意图

    实际使用截图(我们对response展示做了点优化):
    anyproxy

  • 难点二:可靠的测试用例设计策略
    在讨论接口测试用例设计之前,我们需要预先圈定一个思考范围,以免过度的思维发散。结合我们的业务特征,由于SDK的功能大部分是单接口,少部分是关联接口,因此我们的设计基于单接口而非单个业务场景
    接口的测试用例设计有别于其他测试用例,其业务逻辑主要体现在字段的取值上,每个取值体现了一种业务逻辑,我们做了一些调研,学习了其他业务团队的接口测试用例写法,发现测试人员喜欢这样设计case:
    这里写图片描述

这样的case无疑是工整、直观的,可读性比较强,很方便的复制粘贴,再通过修改其中的一个或者几个值,形成一个庞大的二维数组。
看到这个表格,一些熟练的测试工程师会立马联想到边界值、等价类设计、正交试验法等。然而要想保证每一个场景都被完整的覆盖,理论上我们需要测试所有字段的笛卡尔积,这种方式可以保证任何取值都会被覆盖到,但是当字段比较多的时候,测试用例的数量会呈爆炸式的增长,毫无疑问这种方式是不可行的。我们需要一个算法,能做到以下几点:
1、 以最少的组合覆盖尽可能多的场景
2 、覆盖所有字段的所有取值
3 、有统计学支撑,生成的数据有规律可循

有了需求,我们开始进行了可行性方案的研究,秉承不重复造轮子的理念,我们查阅了国内外很多的资料,逐渐的缩小了范围,在说出解决方案之前,先给大家简单介绍两个重要的算法:“ OATS(Orthogonal Array Testing Strategy)”和“Pairwise/All-Pairs Testing”,简称“正交表法”和“配对测试法”。


正交表法

正交表法有两个重要的特性,大家尝试着理解一下:
1.每列中不同数字出现的次数相等

备注:这一特点表明每个因素的每个水平与其它因素的每个水平参与试验的几率是完全相同的,从而保证了在各个水平中最大限度地排除了其它因素水平的干扰,能有效地比较试验结果并找出最优的试验条件。

2.在任意两列其横向组成的数字对中,每种数字对出现的次数相等

备注:这个特点保证了试验点均匀地分散在因素与水平的完全组合之中,因此具有很强的代表性。

举个例子:有三个字段,每个字段可以取三个值,设字段表现为A(A1,A2,A3)、B(B1,B2,B3)、C(C1,C2,C3),可以组成的集合恰好可以表现为一个三维空间图,如下图所示:
这里写图片描述

图中的正方体中每个字段的每个水平代表的是一个面,共九个面,任意两个字段的水平之间都存在交点,共27(3x3x3)个,这就是笛卡尔积。按照两大特性设计出的正交表如右图所示,试验点用⊙表示。我们看到,在9个平面中每个平面上都恰好有三个点而每个平面的每行每列都有一个点,而且只有一个点,总共九个点。这样的试验方案,试验点的分布很均匀,试验次数也不多。
国外有一个网站能查询正交表的结果案例:http://www.york.ac.uk/depts/maths/tables/orthogonal.htm
这里写图片描述


配对测试法
配对测试法(Pairwise)是L. L. Thurstone( 1887 – 1955)在1927年首先提出来的。他是美国的一位心理统计学家。Pairwise是基于数学统计和对传统的正交分析法进行优化后得到的产物。

定义:Most field faults were caused by either incorrect single values or by an interaction of pairs of values.” If that’s generally correct, we ought to focus our testing on the risk of single-mode and double-mode faults. We can get excellent coverage by choosing tests such that 1) each state of each variable is tested, and 2) each variable in each of its states is tested in a pair with every other variable in each of its states. This is called pairwise testing or all-pairs testing.
大概意思是:缺陷往往是由一个参数或两个参数的组合所导致的,那么我们选择比较好的测试组合的原则就是:
1)每个因子的水平值都能被测试到;
2)任意两个因子的各个水平值组合都能被测试到,这就叫配对测试法。
参看:http://www.developsense.com/pairwiseTesting.html

Pairwise基于如下2个假设:
1. 每一个维度都是正交的,即每一个维度互相都没有交集。
2. 根据数学统计分析,73%的缺陷(单因子是35%,双因子是38%)是由单因子或2个因子相互作用产生的。19%的缺陷是由3个因子相互作用产生的。

因此,pairwise基于覆盖所有2因子的交互作用产生的用例集合性价比最高而产生的。国外也有一份类似的数学统计:
这里写图片描述

我们通过一个订飞机票的实际例子来看一下,配对测试法是怎样从笛卡尔积中提炼出局部最优解的。
依然是三个字段的组合,分别是Destination(Canada, Mexico, USA),Class(Coach, Business Class, First Class), Seat Preference(Aisle, Window),所对应的笛卡尔积共有3x3x2=18中测试组合,如下表所示。
这里写图片描述

经过配对测试法筛选后,结果如下:
这里写图片描述

经过筛选以后,我们的测试用例变成了9条,case数量精简了50%。简单总结pairwise的筛选原理就是,发现两两配对在全集中有重复的就去掉其中之一,这样筛选也有副作用,每次筛选完了条数是固定的,但是结果却不尽相同。但是通过上面的介绍我们不难比较出两种算法的差异。

说了那么多,再回到我们之前提到的设计策略几个需求,可以认为pairwise算法的特征基本满足了我们的需求。

  • 难点三:测试用例自动化生成
    确定了用例设计的算法策略后,我们信心十足的准备开始设计我们的response返回值case了,我们套用文献中的排列分布方式应用到实际接口json中,悲伤的发现我们要组合的字段不是3个,而是20-35个左右,如果通过人工的方式来进行case设计的话,就算只考虑最多两个字段的值发生变化,数量也是非常惊人的,WWWWWWhat???
    这里写图片描述

本着“偷懒是人类进步的第一动力”的想法,我们自然不会前功尽弃,自动化测试是我们的必选之路,接下来要做的就是调研目前已经存在的基于pairwise算法的工具有哪些,下面是经过调研后得到的工具列表。
这里写图片描述

基于pairwise算法的工具如此之多,那么相同模型设定下产生的结果是否存在差异呢?我们看一下这张图:
@数据出自http:// www.pairwise.org/tools.asp

综合比较各工具产生的数据结果后,我们可以发现不同工具之间的结果差异并不大,基本上能够满足我们现有的需求。经过一番讨论后,我们决定采用微软的PICT(https://github.com/Microsoft/pict)作为case生成工具,原因有几点:
1. 代码开源可扩展
2. 源码依然在维护,贡献比较活跃
3. 产品成熟,语法丰富
4. 基于贪心算法,局部最优解


难点四:测试用例生成的设计
用例生成过程分为五个步骤:
这里写图片描述

1. 准备字段值
根据Wiki的接口文档,测试人员理清字段结构,字段类型,字段取值范围后,结合传统的case设计理念,构造出每个字段的赋值,存放到整理好的excel中,大概是这样的:
这里写图片描述

有的同学可能会问:你这样整理也挺麻烦,感觉人工也没省多少事儿。这样设计的好处是,当字段发生变化的时候,只需要从源头修改字段属性、值、层级、甚至删除,后面整个流程中的case都会统一生效,字段集中管理,牵一发而动全身。和UI自动化用到的page-object设计类似。

2. 构建模型
有了面粉了,还需要加工一下才能变成我们想要的面包,我们需要把准备的数据整理成可以批量生成的可识别文件,即模型文件。PICT的模型文件有自己的格式,类似这样:

参数定义
[子模型定义]
[约束定义]

举个例子,前面提到的订票系统的例子加工成模型文件是这样的,后面会给大家介绍语法含义:

Destination:  Canada, Mexico, USA
Class:  Coach, Business Class, First Class
Seat Preference:  Aisle, Window
{Destination, Class} @ 2

3. 生成Case
通过pairwise工具将模型文件组装成我们想要的case,那么上面的模型生成的case会是这样:

Destination Class Seat Preference
Canada First Class Window
USA First Class Aisle
Canada Coach Aisle
USA Business Class Window
Mexico Business Class Aisle
Canada Business Class Aisle
Mexico First Class Window
USA Coach Window
Mexico Coach Window

注:选择强度为2,因此上面的矩阵是两两变化的。如前面所说,这里生成的矩阵内容不是固定的!

4. 准备期望结果
输入数据已经准备好了,那么相对于case而言,是不是还缺一个期望结果呢?在这里我们碰到了一个难题,可能做过case自动生成的同学都会遇到的,就是生成排列组合是非常简单的,如何让这些组合变得有意义,体现在我们的期望结果上,那么一次性生成如此多的case,如何让输入值和期望结果对号入座呢?
我们的做法是:拆分了postive testing 和 negative testing(合法输入测试和非法输入测试或负面测试),通过整理接口case我们不难发现,合法输入的case其实占整个case的比重并不大,工作量比较大的是各种参数的异常数据输入,相应的会产生error code或二次请求。只需要我们在整理数据的时候给出对应的error code即可,如图所示:
这里写图片描述

有的同学会问:我们协议还不稳定,error code也不明确,有些输入也不知道对应什么error code,怎么破?别急,后面告诉大家。

5. 生成mock数据
完成了以上准备工作以后,剩下的就是生成我们mock需要的response json数据了。解析Wiki协议中的json模版,给对应的json字段赋上生成的值,这里需要写一段代码来完成,在此不做赘述。

番外篇:工具的二次开发
在使用过程中,我们发现工具PICT不能满足业务场景的复杂度要求,主要有两点:
- 异常输入测试的时候,不能同时输入多个异常值
在case设计中多个异常值输入是很常见的测试场景,虽然pict提供负面测试(negative testing)功能,即如果模型文件中,有值被标记为异常值(默认的异常值标识符为“~”),则case中会随机出现一个异常输入的值,但是PICT限制每个case只能有一个异常值存在,原因是多数异常值的组合虽然可能会引发问题,但是代码在catch了一个异常值造成的异常后,不会再去处理另一个异常值。
先通过一个示例来感受一下pict的负面测试。示例模型文件如下:

Destination:      Canada, Mexico, USA, ~Japan
Class:            Coach, Business Class, First Class
Seat Preference:  Aisle, Window, ~Door

产生的case如下:
这里写图片描述

通过上图可以看出,PICT同时保证了正常值的组合,也保证了异常值的组合,但是我们不难发现,每个case只会出现一个异常值,那么 ~Japan,First Class, ~Door的case就会遗漏,显然case覆盖率不够,不能满足我们的需求。
针对这个问题,在对PICT的源代码进行了详细的解读后,我们对代码进行了二次开发,扩展了负面测试的覆盖范围,彻底解决了这个问题,修改后的模型文件如下:

Destination:      Canada, Mexico, USA, ~Japan
Class:            Coach, Business Class, First Class
Seat Preference:  Aisle, Window, ~Door
~{Destination, Seat Preference}  
//增加一行公式,在模型文件中指定了Destination和Seat Preference两个字段可以进行异常值组合,数量不限。

扩展后的case生成是这样的:
这里写图片描述

  • 正则表达式过于简单,不支持复杂的语句
    PICT支持IF[ ] THEN[ ]格式的约束规则。但是约束规则中LIKE关键字的通配符操作只支持*和?(分别表示任意多个字符和任意一个字符)。显然简单的通配符操作限制了约束规则的表达能力。因此,我们在原有的基础上,引入C++的regex库支持正则表达式,修改后支持了更丰富的正则表达式。
    如下示例中,增加一条规则,如果Destination字段为数字类型(”\d”),那么Seat Preference字段也为数字类型。
Destination:      Canada, Mexico, USA, 3
Class:            Coach, Business Class, First Class
Seat Preference:  Aisle, Window, 4
If [Destination] like "\d" then [Seat Preference] like "\d";

生成的case如下图:
这里写图片描述

有了强大的正则表达式,再加上多异常组合输入的支持,目前已经完全能覆盖我们需要的任何场景,向开源致敬!

结构流程设计


在整个测试过程中,我们唯一需要人工介入的就是字段值的赋值以及跟error code的对应关系设计,协议字段的取值会受业务影响,暂时无法通过自动化的方式来进行。流程如图所示:
这里写图片描述

最初的版本比较简单,结构大概是这样的:
这里写图片描述

原本的设想是想绕过广告宿主直接调用SDK的API请求广告,从而节省一部分时间,且更容易自动化,但是由于广告SDK本身特殊的设计,这个想法无法实现,因此当时的设计是通过触发APP按钮点击发送request给SDK,再由SDK发送加工后的请求到proxy server,再经过mock server处理数据以后,返回给客户端来显示广告。在此过程中,彻底抛弃了Fiddler重定向的传统做法。

这里写图片描述

在阶段二我们解决了几个问题:
- 实现内部循环请求广告,解决手工触发请求的问题
- 监控APP自身出现的crash和ANR
- 解决case失败后可以rerun,
- 解决中途执行中断可以rerun
- 由于一些广告请求失败会触发二次打点请求,因此我们需要把对应的case和打点请求结果匹配上,我们通过在request中加入caseID来解决该问题
- 解决多种广告类型不能连续一次性运行完,需要切换场景的问题
- 当出现期望结果与实际结果不符时,自动重新运行该case 若干次(可配置),如果一直失败计为case失败

这里写图片描述

该阶段很明显,我们遇到了执行速度的问题,由于广告种类的增加,我们的case达到了3400余条,由于还需要兼顾广告渲染完成后的打点结果检查,执行全量case耗时达到了3个小时多,偏离了我们mock测试的初衷。因此如上图所示,我们用到了分布式结构。mock server可以通过客户端指纹信息来调度和发送任务给指定的手机,把case和设备紧密连接在一起,避免重复运行相同的case。
另外我们把config、case、期望结果、执行结果等诸多信息全部迁移到database中,一方面解决频繁的文件读取问题,另一方面解决了分布式调度跨server的问题。

截止目前,我们的测试数据是这样的:

Case总数 发现BUG 遗漏的异常处理
3498条 77个 331个

前面提到的问题,如果error code尚未明确,case应该如何匹配呢?我们的做法是设定一个基准error code,当运行结果出来后,会有实际结果与期望结果不符的case,拿去和开发对一下就可以,而调整error code期望结果以后,重新生成case也只不过分分钟的事。

我们的执行时间:
这里写图片描述

收益:协议变更时,只需要修改最开始的存放字段值的文件,后续的建模、case生成、期望结果填充、执行测试用例全部自动完成,测试人员查看运行结果即可

这里写图片描述

这里写图片描述

问题总结


由于我们也是第一次在mock测试中实践自动化构造测试数据,包括用到的pairwise模型的合理性和准确性,都属于初次尝试,目前在项目中取得了一定的效果,但是也遇到了很多的困难,个中酸楚不足以一一道来,同时架构和流程还有很多优化空间。

目前依然留存的问题包括:
- 自动生成case中,int、string、date等字段提取公共case,比如特殊字符、空、null、js等常规异常检查
- 更复杂的逻辑,比如关联字段依赖、加密字段、随机数、MD5、token等情况
- 非http(s)的自定义协议
- 分布式调度的更大规模的使用
- SDK的自动化测试对于APP的强依赖关系
- 正常的功能测试验证
- 业务逻辑产生的漏测率统计

诸如此类的问题还有很多很多,尤其是结合项目自身特点,就会更加复杂,希望能通过我们的探索之路给同行们更多的启发。

作者:wpyily 发表于2016/10/18 10:47:10 原文链接
阅读:126 评论:0 查看评论

王学岗滑动视图的设计(上)

$
0
0
   第一步:搭建列表界面       

第二步:分析组件原理
    准备工作
    说明:条目分为两个部分
    第一个部分:ContentView---代表内容视图
    第二个部分:FunctionView---代表功能视图

    1、滑动视图摆放(左边、右边)
        注意:
        第一种方案:ItemView可以是LinearLayout(左右摆放)---这种方式通过linearLayout自带的属性控制
        第二种方案   :FrameLayout实现(覆盖,ContentView在下面,FunctionView在上面)
        通过代码算法控制FunctionView到右边
    2、滑动视图状态(三种状态:CloseStatus、OpenStatus、SlidingStatus)
        CloseStatus:关闭功能视图
        OpenStatus:打开功能视图
        SlidingStatus:功能视图正在滑动

第三步:具体功能实现  
    分为两个部分实现

    第一个部分:实现布局摆放
        1、定义组件

        2、定义滑动视图摆放方向(采用枚举定义)

        3、定义滑动视图滑动状态(采用枚举)

        4、初始化当前SlidingItemLayout条目子视图
            注意:规范(只允许有两个儿子)

        5、滑动视图测量
            目的:计算滑动视图-滑动偏移量

        6、滑动视图摆放(onlayout方法)
            注意:滑动视图处于关闭状态
            6.1 摆放内容视图

            6.2 摆放功能视图

        完成,测试
package com.tz.dream.slidinglayout.libs;

import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

//1、定义组件
public class SlidingItemLayout extends FrameLayout {

    // 2、定义滑动视图摆放方向(采用枚举定义)
    public enum SlidingType {
        Left, Right;
    }

    // 3、定义滑动视图滑动状态(采用枚举)
    public enum SlidingStatus {
        Close, Open, Sliding;
    }

    // 4、初始化当前SlidingItemLayout条目子视图-内容视图
    private View contentView;
    // 4、初始化当前SlidingItemLayout条目子视图-功能视图
    private View functionView;
    private int horizontalDX;

    // 6.1.1 计算布局摆放的位置(矩形:left top right buttom)
    private SlidingType slidingType = SlidingType.Right;
    // 6.1.1 计算布局摆放的位置(矩形:left top right buttom)
    private SlidingStatus slidingStatus = SlidingStatus.Close;

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

    public SlidingItemLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    // 4、初始化当前SlidingItemLayout条目子视图
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        initView();
    }

    // 4、初始化当前SlidingItemLayout条目子视图
    private void initView() {
        if (getChildCount() != 2) {
            throw new IllegalArgumentException("你的子视图只允许有两个");
        }
        contentView = getChildAt(0);
        functionView = getChildAt(1);
    }

    // 5、滑动视图测量
    // 目的:计算滑动视图-滑动偏移量
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 普及:测量有很多策略......mode
        // 默认:我的FunctionView有多宽,那么我的偏移量就多大
        // 以下是我的规范
        horizontalDX = functionView.getMeasuredWidth();
    }

    // 6、滑动视图摆放(onlayout方法)
    // 注意:滑动视图处于关闭状态
    // 6.1 摆放内容视图
    // 6.2 摆放功能视图
    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        layoutView(true);
    }

    // 6、滑动视图摆放(onlayout方法)
    // 注意:滑动视图处于关闭状态
    // 6.1 摆放内容视图
    // 6.2 摆放功能视图
    // isOpen(true:代表默认打开 false:默认关闭)
    private void layoutView(boolean isOpen) {
        // 6.1 摆放内容视图
        // 6.1.1 计算布局摆放的位置
        Rect contentRect = layoutContentView(isOpen);
        // 6.1.2 摆放内容视图
        contentView.layout(contentRect.left, contentRect.top,
                contentRect.right, contentRect.bottom);

        // 6.2 摆放功能视图
        // 6.2.1 计算功能视图的位置
        Rect functionRect = layoutFunctionView(contentRect);
        // 6.2.2 摆放功能视图
        functionView.layout(functionRect.left, functionRect.top,
                functionRect.right, functionRect.bottom);
    }

    // 6.1.1 计算布局摆放的位置(矩形:left top right buttom)
    private Rect layoutContentView(boolean isOpen) {
        int left = 0;
        // 处理true状态
        if (isOpen) {
            if (slidingType == SlidingType.Left) {
                // 功能视图摆放方向---左边
                left = horizontalDX;
            } else if (slidingType == SlidingType.Right) {
                // 功能视图摆放右边
                left = -horizontalDX;
            }
        }
        // 首先摆放默认情况--false状态
        return new Rect(left, 0, left + getMeasuredWidth(), getMeasuredHeight());
    }

    // 6.2 摆放功能视图
    // 6.2.1 计算功能视图的位置
    private Rect layoutFunctionView(Rect rect) {
        int left = 0;
        // 根据类型摆放
        if (slidingType == SlidingType.Right) {
            // 功能视图摆放在右边
            left = rect.right;
        } else if (slidingType == SlidingType.Left) {
            // 功能视图摆放在左边
            left = -horizontalDX;
        }
        return new Rect(left, 0, left + horizontalDX,
                functionView.getMeasuredHeight());
    }

}
<?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:orientation="vertical" >

    <com.tz.dream.slidinglayout.libs.SlidingItemLayout
        android:layout_width="match_parent"
        android:layout_height="60dp" >

        <LinearLayout
            android:id="@+id/contentView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" 
            android:gravity="center_vertical"
            android:paddingLeft="10dp"
            android:background="@android:color/darker_gray">

            <ImageView 
                android:id="@+id/iv_header"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/header"/>

        </LinearLayout>

        <LinearLayout
            android:id="@+id/functionView"
            android:layout_width="wrap_content"
            android:layout_height="60dp"
            android:orientation="horizontal" >

            <Button
                android:id="@+id/bt_call"
                android:layout_width="60dp"
                android:layout_height="fill_parent"
                android:background="#888888"
                android:gravity="center"
                android:text="@string/call"
                android:textColor="@android:color/white" />

            <Button
                android:id="@+id/bt_delete"
                android:layout_width="60dp"
                android:layout_height="fill_parent"
                android:background="#ff0000"
                android:gravity="center"
                android:text="@string/delete"
                android:textColor="@android:color/white" />
        </LinearLayout>
    </com.tz.dream.slidinglayout.libs.SlidingItemLayout>

</LinearLayout>

先看下效果图!
这里写图片描述

注意下这个方法
onFinishInflate
* 我们一般使用View的流程是在onCreate中使用setContentView来设置要显示Layout文件或直接创建一个View,
* 在当设置了ContentView之后系统会对这个View进行解析,然后回调当前视图View中的onFinishInflate方法。
* 只有解析了这个View我们才能在这个View容器中获取到拥有Id的组件, 同样因为系统解析完View之后才会调用
* onFinishInflate方法,所以我们自定义组件时可以onFinishInflate方法中获取指定子View的引用。
我这里有三张图,大家可以参照看下
功能图
左边
右边
我会在下一篇文章中详细的介绍具体功能的实现;会包含跟多的算法

作者:qczg_wxg 发表于2016/10/18 10:47:13 原文链接
阅读:37 评论:0 查看评论

Serializable和Parcelable的再次回忆

$
0
0

自己开发Android也有些时间了,Serializable和Parcelable遇到过不止一次了。但是每次别人问起具体的内容自己偏偏记得不是很清晰。因为某些原因再次梳理一下,以文章的形式给自己存储下来。温故而知新~!~!

序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中;另一方面,它们会以其他更容易理解的概念出现,例如加密、持久化。然而,序列化和反序列化的选型却是系统设计或重构一个重要的环节,在分布式、大数据量系统设计里面更为显著。恰当的序列化协议不仅可以提高系统的通用性、强健性、安全性、优化系统性能,而且会让系统更加易于调试、便于扩展。总而言之搞懂序列化是很重要滴。

序列化

将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。

反序列化

就是读取序列化后保存在存储区的序列化信息或反序列化对象的状态,重新创建该对象。

序列化和反序列化通常是并存的,他们的关系就像进行加密和解密操作一样。前者需要相同的序列化方式,后者需要知道秘钥。

Android中将对象序列化的方式有两种Serializable和Parcelable这两个接口都可以完成。Serializable是Java自带的序列化方法,而Android原生的序列化为Parcelable。这并不意味着在Android中可以抛弃Serialable,只能说在Android中Parcelable方法实现序列化更有优势。下边我们可以具体来看看这两个接口实现。(PS:对象参加序列化的时候其类的结构是不能发生变化的,以下Demo都是在此基础上演示)

Serializable

我们先看一个实现Serializable接口的类:

public class User implements Serializable {
    private static final long serialVersionUID = -4454266436543306544L;
    public int userId;
    public String userName;
    public User(int userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }
}

Serializable是java提供的一个序列化接口,它是一个空接口,专门为对象提供标准的序列化和反序列化操作,使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标示即可自动实现默认的序列化过程(serialVersionUID可自动生成也可自己写,如0L,1L,2L…)。

private static final long serialVersionUID = -4454266436543306544L;

通过Serializable方式来实现对象的序列化,实现起来非常简单,几乎素有工作都被系统自动完成。如果进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现。下面举个简单的列子:

//序列化过程
User user = new User(1, "Tom");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newuser = (User) in.readObject();
in.close();

上述代码采用Serializable方式序列化对象的典型过程,只需要把实现Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和User的内容完全一致,但是两个并不是同一个对象。

serialVersionUID

上面提到,想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实际上,甚至这个serialVersionUID也不是必需的,我们不声明这个serialVersionUID同样可以实现序列化,但是这将会对反序列化过程产生影响,具体会产生什么影响呢?

解决这个问题前我们先提一个问题,为什么需要serialVersionUID呢?

因为静态成员变量属于类不属于对象,不会参与序列化过程,使用transient关键字标记的成员变量也不参与序列化过程。 (PS:关键字transient,这里简单说明一下,Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的)

这个时候又有一个疑问serialVersionUID是静态成员变量不参与序列化过程,那么它的存在与否有什么影响呢?

具体过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。

InvalidClassException

接下来我们回答声明serialVersionUID对进行序列化有啥影响?

如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并且把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败。所以我们手动指定serialVersionUID的值能很大程度上避免了反序列化失败。

以上就是自己对Serializable的认识,下边来看看Parcelable相关的知识~!~!

Parcelable

我们先看一个使用Parcelable进行序列化的例子:

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book() {
    }

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    //从序列化后的对象中创建原始对象
    protected Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        //从序列化后的对象中创建原始对象
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }
        //指定长度的原始对象数组
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
    //返回当前对象的内容描述。如果含有文件描述符,返回1,否则返回0,几乎所有情况都返回0
    @Override
    public int describeContents() {
        return 0;
    }
    //将当前对象写入序列化结构中,其flags标识有两种(1|0)。
    //为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况下都为0.
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(bookId);
        dest.writeString(bookName);
    }

    @Override
    public String toString() {
        return "[bookId=" + bookId + ",bookName='" + bookName + "']";
    }
}

虽然Serializable可以将数据持久化在磁盘,但其在内存序列化上开销比较大(PS:Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC),而内存资源属于android系统中的稀有资源(android系统分配给每个应用的内存开销都是有限的),为此android中提供了Parcelable接口来实现序列化操作,在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。

Parcelable内部包装了可序列化的数据,可以在Biander中自由的传输,从代码中可以看出,在序列化过程中需要实现的功能有序列化,反序列化和内容描述。序列化功能是由writetoParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的。反序列化功能是由CREATOR方法来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程(PS:write和read的顺序必须一致~!);内容描述功能是有describeContents方法来完成,几乎所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1.

系统已经给我们提供了许多实现了Parcelable接口类,他们都是可以直接序列化的,比如Intent,Bundle,Bitmap等,同事List和Map也支持序列化,提前是他们里面的每个元素都是可以序列化的。

总结

既然Parcelable和Serializable都能实现序列化并且都可以用于Intent间传递数据,那么二者改如果选择呢?Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量的I/O操作。而Parcelable是Android中序列化方法,因为更适合于在Android平台上,它的缺点就是使用起来比较麻烦,但是它的效率很高,这是Android推荐的序列化方法,因为我们要首选Parcelable。Parcelable主要用于内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程稍显复杂,因此在这两种情况下建议使用Serializable。

文章部分摘自任玉刚的《Android开发艺术探索》

作者:stven_king 发表于2016/10/18 10:55:02 原文链接
阅读:178 评论:0 查看评论

cocoapods:常见错误总结

$
0
0

1.无论是执行pod install还是pod update都卡在了Analyzing dependencies 或者 Updating local specs repositories不动 
解决: 原因在于当执行以上两个命令的时候会升级CocoaPods的spec仓库,加一个参数可以省略这一步,然后速度就会提升不少。加参数的命令如下:

<code class="hljs brainfuck has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">pod</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">install</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">verbose</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">no</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">repo</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">update</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">pod</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">update</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">verbose</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">no</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">repo</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">update</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

2.pod 命令运行报下面错误

Your Podfile has had smart quotes sanitised. To avoid issues in the future, you should not use TextEdit for editing it. If you are not using TextEdit, you should turn off smart quotes in your editor of choice.

解决: 不要使用文本编辑去编辑Podfile,使用Xcode编辑,或者使用终端敲命令去编辑。或者输入格式错误,没输入运行版本:

<code class="hljs livecodeserver has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">platform</span>:iOS, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'7.0'</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

3.使用cocoapods导入第三方类库后头文件没有代码提示 
解决: 选择Target -> Build Settings 菜单,找到\”User Header Search Paths\”设置项,新增一个值"${SRCROOT}",并且选择\”Recursive\”

4.The dependency “ is not used in any concrete target. 
解决: 这个错误是说明你没有使用下面的格式, 将 Podfile编辑成下面的格式

<code class="hljs r has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"> platform :ios,<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'7.0'</span> target <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'你的app的target名字'</span> do pod <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'AFNetworking'</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'2.0'</span> pod <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'SDWebImage'</span>, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'3.7'</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span> end </code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

5.安装错误

Setting up CocoaPods master repo 
[!] /usr/bin/Git clone https://github.com/CocoaPods/Specs.git master –depth=1 
Cloning into ‘master’… 
error: RPC failed; result=18, HTTP code = 200 
fatal: The remote end hung up unexpectedly 
fatal: early EOF 
fatal: index-pack failed

解决: 

<code class="hljs brainfuck has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">$</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">git</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">config</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">global</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">http</span><span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">.</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">postBuffer</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">24288000</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">$</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">git</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">config</span> <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">-</span><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">list</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

若输出 http.postbuffer=24288000, 就 OK了

6.在 pod install 时, 可以生成要导入的第三库, 但是其头文件找不到, 文件有缺失, target 设置没有完成.

<code class="hljs asciidoc has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">[!] The <span class="hljs-smartquote" style="box-sizing: border-box;">`Paopao [Debug]` target overrides the `PODS_ROOT` build setting defined in `Pods/Target Support Files/Pods/Pods.debug.xcconfig'</span>. This can lead to problems with the CocoaPods installation <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Use the <span class="hljs-code" style="box-sizing: border-box;">`$(inherited)`</span> flag, or <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Remove the build settings from the target. [!] The <span class="hljs-smartquote" style="box-sizing: border-box;">`Paopao [Debug]` target overrides the `OTHER_LDFLAGS` build setting defined in `Pods/Target Support Files/Pods/Pods.debug.xcconfig'</span>. This can lead to problems with the CocoaPods installation <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Use the <span class="hljs-code" style="box-sizing: border-box;">`$(inherited)`</span> flag, or <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Remove the build settings from the target. [!] The <span class="hljs-smartquote" style="box-sizing: border-box;">`Paopao [Release]` target overrides the `PODS_ROOT` build setting defined in `Pods/Target Support Files/Pods/Pods.release.xcconfig'</span>. This can lead to problems with the CocoaPods installation <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Use the <span class="hljs-code" style="box-sizing: border-box;">`$(inherited)`</span> flag, or <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Remove the build settings from the target. [!] The <span class="hljs-smartquote" style="box-sizing: border-box;">`Paopao [Release]` target overrides the `OTHER_LDFLAGS` build setting defined in `Pods/Target Support Files/Pods/Pods.release.xcconfig'</span>. This can lead to problems with the CocoaPods installation <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Use the <span class="hljs-code" style="box-sizing: border-box;">`$(inherited)`</span> flag, or <span class="hljs-bullet" style="box-sizing: border-box;">- </span>Remove the build settings from the target.</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li></ul>

解决: 产生此警告的原因是项目 Target 中的一些设置,CocoaPods 也做了默认的设置,如果两个设置结果不一致,就会造成问题。 
我想要使用 CocoaPods 中的设置,分别在我的项目中定义PODS_ROOT 和 Other Linker Flags的地方,把他们的值用$(inherited)替换掉,进入终端,执行 pod update , 然后错误没了.

还有一种简单粗暴的方法: 
点击项目文件 project.xcodeproj,右键显示包内容,用文本编辑器打开project.pbxproj,删除OTHER_LDFLAGS的地方,保存,执行pod update,错误没了

7.引用要导入的三方库缺少 .o 文件的错误 
这里写图片描述

解决: 在Build Setting 中的Other Linker Flags选项中加入$(OTHER_LDFLAGS)

8.还有一个复杂些的错误, 在为新项目配置 cocoapods 时遇到的

<code class="hljs lasso has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">LoadError <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">-</span> dlopen(/Users/MyMac<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">/</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>rvm/rubies/ruby<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">-</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2.0</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span><span class="hljs-attribute" style="box-sizing: border-box;">-p643</span>/lib/ruby/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2.0</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span>/x86_64<span class="hljs-attribute" style="box-sizing: border-box;">-darwin14</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.1</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span>/psych<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>bundle, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">9</span>): Library <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">not</span> loaded: /usr/<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">local</span>/lib/libyaml<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">-</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.2</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>dylib Referenced from: /Users/MyMac<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">/</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>rvm/rubies/ruby<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">-</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2.0</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span><span class="hljs-attribute" style="box-sizing: border-box;">-p643</span>/lib/ruby/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2.0</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span>/x86_64<span class="hljs-attribute" style="box-sizing: border-box;">-darwin14</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.1</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span>/psych<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>bundle Reason: image <span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">not</span> found <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">-</span> /Users/MyMac<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">/</span><span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>rvm/rubies/ruby<span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">-</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2.0</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span><span class="hljs-attribute" style="box-sizing: border-box;">-p643</span>/lib/ruby/<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2.0</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span>/x86_64<span class="hljs-attribute" style="box-sizing: border-box;">-darwin14</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.1</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.0</span>/psych<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>bundle</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

9.Mac OS X 跟新到10.11后cocoapods安装出现的问题

$ sudo gem install cocoapods 
ERROR: Could not find a valid gem ‘cocoapods’ (>= 0), here is why: 
Unable to download data from http://ruby.taobao.org/ - bad response Not Found 404 (http://ruby.taobao.org/latest_specs.4.8.gz)

更新ruby后也没有解决, 最后发现是由于淘宝镜像失效了,http://ruby.taobao.org/失效了 
解决方案有两个: 
1>将淘宝镜像的http改为https

<code class="hljs ruby has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">$ </span>gem sources --remove <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">http:</span>/<span class="hljs-regexp" style="color: rgb(0, 136, 0); box-sizing: border-box;">/ruby.taobao.org/</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">$ </span>gem sources -a <span class="hljs-symbol" style="color: rgb(0, 102, 102); box-sizing: border-box;">https:</span>/<span class="hljs-regexp" style="color: rgb(0, 136, 0); box-sizing: border-box;">/ruby.taobao.org/</span> <span class="hljs-variable" style="color: rgb(102, 0, 102); box-sizing: border-box;">$ </span>gem sources -l</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

再安装就可以了

<code class="hljs bash has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sudo</span> gem install cocoapods</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

2>后来在stackoverflow上看到一个回答 是关于gem install fails with openssl failure,将淘宝镜像替换为http://rubygems.org/.

<code class="hljs bash has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ gem <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">source</span> <span class="hljs-operator" style="box-sizing: border-box;">-a</span> http://rubygems.org/</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

再安装就可以了

<code class="hljs bash has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sudo</span> gem install cocoapods</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

10.ruby环境需要升级更新 
ERROR: Error installing cocoapods: activesupport requires Ruby version >= 2.2.2.

原因:在安装cocoapods过程中提示需要Ruby的版本在2.2.2以上,而目前使用的Ruby版本是Mac系统自带的1.8.7。所以需要对Ruby进行升级。以下是安装Ruby的三种方法: 
1.下载ruby源代码,编译,安装 
2.使用发行版自带的安装包,安装 
3.使用 rvm安装 
在这里还是推荐大家通过rvm对Ruby进行升级,具体的步骤这里就不赘述了, 需要的请查看我的博客之 cocoapods:安装/更新Ruby环境教程, 里面有通过ram安装ruby的具体操作.

11.pod setup/ pod install 错误总结

问题1:

$ pod install 
Analyzing dependencies 
[!] Unable to satisfy the following requirements: 
-AFNetworking (~> 3.0) required by Podfile 
None of your spec sources contain a spec satisfying the dependency: AFNetworking (~> 3.0)
You have either: 
* out-of-date source repos which you can update with pod repo update
* mistyped the name or version. 
* not added the source repo that hosts the Podspec to your Podfile. 
Note: as of CocoaPods 1.0, pod repo update does not happen on pod install by default.

解决:

<code class="hljs smalltalk has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-char" style="box-sizing: border-box;">$ </span>pod repo remove master <span class="hljs-char" style="box-sizing: border-box;">$ </span>pod repo add master <span class="hljs-method" style="box-sizing: border-box;">https:</span>//gitcafe.com/akuandev/<span class="hljs-class" style="box-sizing: border-box; color: rgb(102, 0, 102);">Specs</span>.git <span class="hljs-char" style="box-sizing: border-box;">$ </span>pod repo update</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>

问题2:

$ pod setup 
Setting up CocoaPods master repo 
[!] /usr/bin/git clone https://github.com/CocoaPods/Specs.git master 
Cloning into ‘master’… 
error: RPC failed; curl 56 SSLRead() return error -36 
fatal: The remote end hung up unexpectedly 
fatal: early EOF 
fatal: index-pack failed 
解决:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ sudo xcode-<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">select</span> -<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">switch</span> /Applications/Xcode.app/Contents/Developer</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

并一定是这行代码,要根据自己的情况,来修改这个命令行, 也许你是多个xcode, 那么你的命令行就要修改一下:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ sudo xcode-<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">select</span> -<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">switch</span> /Applications/Xcode <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">7.3</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">.1</span>.app/Contents/Developer</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

问题3:

$ pod setup 
Setting up CocoaPods master repo 
[!] /usr/bin/git clone https://github.com/CocoaPods/Specs.git master 
Cloning into ‘master’… 
error: RPC failed; curl 56 SSLRead() return error -9806 
fatal: The remote end hung up unexpectedly 
fatal: early EOF 
fatal: index-pack failed

问题4:

$ pod setup 
Setting up CocoaPods master repo 
[!] /usr/bin/git clone https://github.com/CocoaPods/Specs.git master 
Cloning into ‘master’… 
error: RPC failed; curl 18 transfer closed with outstanding read data remaining 
fatal: The remote end hung up unexpectedly 
fatal: early EOF 
fatal: index-pack failed

这些怎么解决呢? 其实mac OS 10.11之后, cocoapods的一些命令需要进行变化,今天来总结一下. 如果你遇到了pod setup或者pod update 的问题了, 而且很难解决, 我建议不如卸载重装. 10.11 之后, 安装cocoapods有一些不同的命令,下面总结一下: 
1.检查ruby环境,若需要请按如下更新

<code class="hljs bash has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sudo</span> gem update --system</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

2.卸载cocoapods

<code class="hljs bash has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sudo</span> gem uninstall cocoapods</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

3.重装cocoapods (安装命令有变化) 
10.11之前

<code class="hljs bash has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">sudo</span> gem install cocoapods</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

10.11之后

<code class="hljs lasso has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ sudo gem install <span class="hljs-attribute" style="box-sizing: border-box;">-n</span> /usr/<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">local</span>/bin cocoa pods</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

4.

<code class="hljs lasso has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">$ sudo chmod <span class="hljs-subst" style="color: rgb(0, 0, 0); box-sizing: border-box;">+</span><span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">rx</span> /usr/<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">local</span>/bin $ sudo xcode<span class="hljs-attribute" style="box-sizing: border-box;">-select</span> <span class="hljs-attribute" style="box-sizing: border-box;">-switch</span> /Applications/Xcode<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">.</span>app/Contents/Developer</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>

//将 CocoaPods Specs repository复制到你电脑上~/.cocoapods目录下

<code class="hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">pod setup</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>

作者:st646889325 发表于2016/10/18 11:22:08 原文链接
阅读:29 评论:0 查看评论

Android----Rxjava与Retrofit初体验

$
0
0

RxJava(响应式编程)

RxJava 在 GitHub 主页上的自我介绍是 “a library for composing asynchronous and event-based programs using observable sequences for the Java VM”(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。

Retrofit

Retrofit与okhttp共同出自于Square公司,retrofit就是对okhttp做了一层封装。把网络请求都交给给了Okhttp,我们只需要通过简单的配置就能使用retrofit来进行网络请求了,其主要作者是Android大神JakeWharton

下面是相关学习文档

RxJava:http://gank.io/post/560e15be2dca930e00da1083
Retrofit:https://square.github.io/retrofit/

1.在buil.gradle添加依赖

    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'io.reactivex:rxjava:1.2.1'
    compile 'com.squareup.retrofit2:retrofit:2.1.0'
    compile 'com.squareup.retrofit2:converter-scalars:2.1.0'

2.需要添加网络权限

<uses-permission android:name="android.permission.INTERNET" />

 这里请求用到的网络:http://www.omghz.cn/FirstService/getString

3.先来说说RxJava(观察者模式):第一种形式

// 创建字符串
    private String sayRxAndroid() {
        return "你好啊!RxAndroid";
    }
    //第一种
    private void numberOne() {
        //这里定义的Observable(被观察者,事件源)  对象仅仅发出一个Hello World字符串,然后就结束了。
        Observable<String> observable = Observable.create(
                new Observable.OnSubscribe<String>() {
                    @Override
                    public void call(Subscriber<? super String> sub) {
                        sub.onNext(sayRxAndroid());
                        sub.onCompleted();
                    }
                }
        );
        //接着我们创建一个Subscriber(观察者)来处理Observable对象发出的字符串。
        //只要订阅了observable,那么当他发生改变时这里就可以接收到消息
        Subscriber<String> subscriber = new Subscriber<String>() {
            @Override
            public void onNext(String s) {
                textView1.setText(s);
            }

            @Override
            public void onCompleted() {
            }

            @Override
            public void onError(Throwable e) {
                //出错回调
            }
        };
        //通过subscribe函数就可以将我们定义的observable对象和subscriber对象关联起来,
        // 这样就完成了subscriber对observable的订阅。
        observable.subscribe(subscriber);//订阅
    }

4.第二种形式,也就是对第一种形式的简化

//第二种,简单形式。Observable.just()就是用来创建只发出一个事件就结束的Observable对象
    private void numberTwo() {
        Observable<String> observable = Observable.just(sayRxAndroid() + "简单形式");
        //我们其实并不关心OnComplete和OnError,
        //我们只需要在onNext的时候做一些处理,这时候就可以使用Action1类。
        Action1<String> action1 = new Action1<String>() {
            @Override
            public void call(String s) {
                textView2.setText(s);
            }
        };
        //订阅
        observable.subscribe(action1);
    }

5.第三种形式,对第二种形式的简化

//方式二的代码最终可以简化成这样
    private void numberThree() {
        Observable.just(sayRxAndroid() + "简单形式二")
                .subscribe(new Action1<String>() {
                    @Override
                    public void call(String s) {
                        textView3.setText(s);
                    }
                });
    }

6.这里我没有引入Lambda表达式,Lambda表达式可以让上面的代码更加的简化、直观有兴趣的小伙伴可以尝试一下,下面我们使用RxJava来加载网络数据。

Schedulers(调度器)->线程控制
subscribeOn(Schedulers.io())—>指定 observable 发生在 IO 线程
observeOn(AndroidSchedulers.mainThread())—>指定 subscribe 的回调发生在主线程

    //请求网络数据,获取数据
    private void numberFour() {
        final Observable<String> observable = Observable.create(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                subscriber.onNext(getInternet());
                subscriber.onCompleted();
            }
        })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                observable.subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(String s) {
                        textView4.setText(s);
                    }
                });
            }
        });
    }

getInternet()从网络获取数据

 private String getInternet() {
        StringBuilder sb = new StringBuilder();
        String line;
        BufferedReader reader;
        try {
            URL url = new URL("http://www.omghz.cn/FirstService/getString?username=a_zhon&password=9584");
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.setConnectTimeout(3000);
            urlConnection.setReadTimeout(3000);
            if (urlConnection.getResponseCode() == 200) {
                reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8"));
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                return sb.toString();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

7.我们还可以来获取网络图片

 //从网络上获取一张图片
    private void numberFive() {
        Observable.just("http://sc.jb51.net/uploads/allimg/141119/10-1411192130010-L.jpg") // 输入类型 String
                .map(new Func1<String, Bitmap>() {
                    @Override
                    public Bitmap call(String url) { // 参数类型 String
                        return getBitmap(url); // 返回类型 Bitmap
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<Bitmap>() {
                    @Override
                    public void call(Bitmap bitmap) {
                        showBitmap(bitmap);
                    }

                    //显示获取的图片
                    private void showBitmap(Bitmap bitmap) {
                        ImageView iv = (ImageView) findViewById(R.id.iv);
                        iv.setImageBitmap(bitmap);
                    }
                });
    }

从网络获取一张图片

//从网络获取图片
    private Bitmap getBitmap(String path) {
        try {
            URL url = new URL(path);
            HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.setConnectTimeout(3000);
            urlConnection.setReadTimeout(3000);
            if (urlConnection.getResponseCode() == 200) {
                InputStream inputStream = urlConnection.getInputStream();
                return BitmapFactory.decodeStream(inputStream);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

8.说了那么多RxJava,现在来看下RxJava与Retrofit结合请求网络,先来说说Retrofit的使用

Retrofit需要定义一个接口,用来返回我们的Call对象。
这里请求所使用的接口:http://www.omghz.cn/FirstService/getString

RequestServes.class

interface RequestServes {
    /**
     * 使用retrofit与Get方式获取网络数据
     * @GET("getString")--->定义请求方式和请求接口==== Call<String>--->定义返回结果类型
     */
    @GET("getString")
    Call<String> getJson(@Query("username") String username, @Query("password") String password);
    //@Query("username") String username, @Query("password") String password
    //请求参数,括号内为键值名,参数为传递的值


    //使用retrofit与Post方式获取网络数据
    @POST("getString")
    Call<String> getStringRetrofit(@Query("username") String username, @Query("password") String password);


    //使用retrofit与RxJava与Post方式获取网络数据
    @POST("getString")
    Observable<String> getStringRxJava(@Query("username") String username, @Query("password") String password);
}

9.接口写完了,我们来创建一个Retrofit对象使用Get请求网络

    /**
     * 使用Retrofit与Get请求
     */
    private void getJson() {
        Retrofit retrofit = new Retrofit.Builder()
          //请求地址,会与@GET("getString")拼接成一个完整的地址
          //http://www.omghz.cn/FirstService/getString
           .baseUrl("http://www.omghz.cn/FirstService/")
          //增加返回值为String的支持  
                       .addConverterFactory(ScalarsConverterFactory.create())
                .build();
        RequestServes serves = retrofit.create(RequestServes.class);//这里采用的是Java的动态代理模式
        Call<String> call = serves.getJson("名字", "密码");//传入我们请求的键值对的值
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                textView4.setText("Retrofit:--Get" + response.body());
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {

            }
        });
    }

10.使用Retrofit与Post请求网络

    /**
     * 使用Retrofit与Post请求网络
     */
    private void getStringRetrofit() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://www.omghz.cn/FirstService/")
                //增加返回值为String的支持
                .addConverterFactory(ScalarsConverterFactory.create())
                .build();
        RequestServes serves = retrofit.create(RequestServes.class);//这里采用的是Java的动态代理模式
        Call<String> call = serves.getStringRetrofit("阿钟", "1011");//传入我们请求的键值对的值
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                textView4.setText("Retrofit:--Post" + response.body());
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {

            }
        });

11.使用Retrofit+RxJava与Post请求网络

    /**
     * 使用Retrofit+RxJava与Post请求网络
     */
    private void getStringRetrofitRxJava() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://www.omghz.cn/FirstService/")
                .addConverterFactory(ScalarsConverterFactory.create())
                .build();
        retrofit.create(RequestServes.class)//这里采用的是Java的动态代理模式
                .getStringRxJava("阿钟", "1011")//传入我们请求的键值对的值
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<String>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(String s) {
                        textView4.setText("Retrofit+RxJava:--Post" + s);
                    }
                });
    }
}

12.当你安装到手机上你会看到使用Retrofit+RxJava与Post请求网络 直接报如下错误

这里写图片描述

解决方法:在build.gradle中添加 compile ‘com.squareup.retrofit2:adapter-rxjava:2.1.0’在创建Retrofit 时添加 .addCallAdapterFactory()函数

 Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://www.omghz.cn/FirstService/")
                .addConverterFactory(ScalarsConverterFactory.create())

                //解决 Unable to create call adapter for rx.Observable<java.lang.String>
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

效果图

这里写图片描述

到这里我们的第一次体验就结束了,对于Retrofit请求网络感觉真是太好用了省去了很多繁琐的步骤。这里好多用法都在代码注释中,所以小伙伴们阅读要仔细耐心哦!

项目地址:https://github.com/azhong1011/Rxjava-Retrofit

作者:a_zhon 发表于2016/10/18 11:23:22 原文链接
阅读:42 评论:0 查看评论

AngularJS $watch用法

$
0
0

$watch()执行时会先调用一次传入的回调,可以认为是初始化。

用法:

$scope.name = 'zhangsan'; 
<pre name="code" class="javascript">$scope.count=0;

$scope.$watch('name', function(newValue, oldValue) {  
    // console.log(newValue+ '===' +oldValue);  
    ++$scope.count;  
  
    if ($scope.count > 3) {  
        $scope.name = '已将大于3次了';  
    }  
}); 

错误用法 <pre name="code" class="javascript">$scope.$watch($scope.name, function(newValue, oldValue) {  

}
如果这么写$watch不会被执行。


可以参照下面这篇文章

理解$watch ,$apply 和 $digest --- 理解数据绑定过程  http://www.angularjs.cn/A0a6

原文地址:http://angular-tips.com/blog/2013/08/watch-how-the-apply-runs-a-digest/

Angular用户都想知道数据绑定是怎么实现的。你可能会看到各种各样的词汇:$watch,$apply,$digest,dirty-checking... 它们是什么?它们是如何工作的呢?这里我想回答这些问题,其实它们在官方的文档里都已经回答了,但是我还是想把它们结合在一起来讲,但是我只是用一种简单的方法来讲解,如果要想了解技术细节,查看源代码。

让我们从头开始吧。

浏览器事件循环和Angular.js扩展

我们的浏览器一直在等待事件,比如用户交互。假如你点击一个按钮或者在输入框里输入东西,事件的回调函数就会在javascript解释器里执行,然后你就可以做任何DOM操作,等回调函数执行完毕时,浏览器就会相应地对DOM做出变化。Angular拓展了这个事件循环,生成一个有时成为angular context的执行环境(记住,这是个重要的概念),为了解释什么是context以及它如何工作,我们还需要解释更多的概念。

$watch 队列($watch list)

每次你绑定一些东西到你的UI上时你就会往$watch队列里插入一条$watch。想象一下$watch就是那个可以检测它监视的model里时候有变化的东西。例如你有如下的代码

index.html

User: <input type="text" ng-model="user" />
Password: <input type="password" ng-model="pass" />

在这里我们有个$scope.user,他被绑定在了第一个输入框上,还有个$scope.pass,它被绑定在了第二个输入框上,然后我们在$watch list里面加入两个$watch:

controllers.js

app.controller('MainCtrl', function($scope) {
  $scope.foo = "Foo";
  $scope.world = "World";
});

index.html

Hello, {{ World }}

这里,即便我们在$scope上添加了两个东西,但是只有一个绑定在了UI上,因此在这里只生成了一个$watch.再看下面的例子:controllers.js

app.controller('MainCtrl', function($scope) {
  $scope.people = [...];
});

index.html

<ul>
  <li ng-repeat="person in people">
      {{person.name}} - {{person.age}}
  </li>
</ul>

这里又生成了多少个$watch呢?每个person有两个(一个name,一个age),然后ng-repeat又有一个,因此10个person一共是(2 * 10) +1,也就是说有21个$watch。因此,每一个绑定到了UI上的数据都会生成一个$watch。对,那这写$watch是什么时候生成的呢?当我们的模版加载完毕时,也就是在linking阶段(Angular分为compile阶段和linking阶段---译者注),Angular解释器会寻找每个directive,然后生成每个需要的$watch。听起来不错哈,但是,然后呢?

$digest循环(这个digest不知道怎么翻译)

还记得我前面提到的扩展的事件循环吗?当浏览器接收到可以被angular context处理的事件时,$digest循环就会触发。这个循环是由两个更小的循环组合起来的。一个处理evalAsync队列,另一个处理$watch队列,这个也是本篇博文的主题。这个是处理什么的呢?$digest将会遍历我们的$watch,然后询问:

  • 嘿,$watch,你的值是什么?
    • 是9。
  • 好的,它改变过吗?
    • 没有,先生。
  • (这个变量没变过,那下一个)
  • 你呢,你的值是多少?
    • 报告,是Foo
  • 刚才改变过没?
    • 改变过,刚才是Bar
  • (很好,我们有DOM需要更新了)
  • 继续询问知道$watch队列都检查过。

这就是所谓的dirty-checking。既然所有的$watch都检查完了,那就要问了:有没有$watch更新过?如果有至少一个更新过,这个循环就会再次触发,直到所有的$watch都没有变化。这样就能够保证每个model都已经不会再变化。记住如果循环超过10次的话,它将会抛出一个异常,防止无限循环。当$digest循环结束时,DOM相应地变化。

例如:controllers.js

app.controller('MainCtrl', function() {
  $scope.name = "Foo";

  $scope.changeFoo = function() {
      $scope.name = "Bar";
  }
});

index.html

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

这里我们有一个$watch因为ng-click不生成$watch(函数是不会变的)。

  • 我们按下按钮
  • 浏览器接收到一个事件,进入angular context(后面会解释为什么)。
  • $digest循环开始执行,查询每个$watch是否变化。
  • 由于监视$scope.name$watch报告了变化,它会强制再执行一次$digest循环。
  • 新的$digest循环没有检测到变化。
  • 浏览器拿回控制权,更新与$scope.name新值相应部分的DOM。

这里很重要的(也是许多人的很蛋疼的地方)是每一个进入angular context的事件都会执行一个$digest循环,也就是说每次我们输入一个字母循环都会检查整个页面的所有$watch

通过$apply来进入angular context

谁决定什么事件进入angular context,而哪些又不进入呢?$apply

如果当事件触发时,你调用$apply,它会进入angular context,如果没有调用就不会进入。现在你可能会问:刚才的例子里我也没有调用$apply啊,为什么?Angular为了做了!因此你点击带有ng-click的元素时,时间就会被封装到一个$apply调用。如果你有一个ng-model="foo"的输入框,然后你敲一个f,事件就会这样调用$apply("foo = 'f';")

Angular什么时候不会自动为我们$apply呢?

这是Angular新手共同的痛处。为什么我的jQuery不会更新我绑定的东西呢?因为jQuery没有调用$apply,事件没有进入angular context$digest循环永远没有执行。

我们来看一个有趣的例子:

假设我们有下面这个directive和controller

app.js

app.directive('clickable', function() {

return {
  restrict: "E",
  scope: {
    foo: '=',
    bar: '='
  },
  template: '<ul style="background-color: lightblue"><li>{{foo}}</li><li>{{bar}}</li></ul>',
  link: function(scope, element, attrs) {
    element.bind('click', function() {
      scope.foo++;
      scope.bar++;
    });
  }
}

});

app.controller('MainCtrl', function($scope) {
  $scope.foo = 0;
  $scope.bar = 0;
});

它将foobar从controller里绑定到一个list里面,每次点击这个元素的时候,foobar都会自增1。

那我们点击元素的时候会发生什么呢?我们能看到更新吗?答案是否定的。因为点击事件是一个没有封装到$apply里面的常见的事件,这意味着我们会失去我们的计数吗?不会

真正的结果是:$scope确实改变了,但是没有强制$digest循环,监视foobar$watch没有执行。也就是说如果我们自己执行一次$apply那么这些$watch就会看见这些变化,然后根据需要更新DOM。

试试看吧:http://jsbin.com/opimat/2/

如果我们点击这个directive(蓝色区域),我们看不到任何变化,但是我们点击按钮时,点击数就更新了。如刚才说的,在这个directive上点击时我们不会触发$digest循环,但是当按钮被点击时,ng-click会调用$apply,然后就会执行$digest循环,于是所有的$watch都会被检查,当然就包括我们的foobar$watch了。

现在你在想那并不是你想要的,你想要的是点击蓝色区域的时候就更新点击数。很简单,执行一下$apply就可以了:

element.bind('click', function() {
  scope.foo++;
  scope.bar++;

  scope.$apply();
});

$apply是我们的$scope(或者是direcvie里的link函数中的scope)的一个函数,调用它会强制一次$digest循环(除非当前正在执行循环,这种情况下会抛出一个异常,这是我们不需要在那里执行$apply的标志)。

试试看:http://jsbin.com/opimat/3/edit

有用啦!但是有一种更好的使用$apply的方法:

element.bind('click', function() {
  scope.$apply(function() {
      scope.foo++;
      scope.bar++;
  });
})

有什么不一样的?差别就是在第一个版本中,我们是在angular context的外面更新的数据,如果有发生错误,Angular永远不知道。很明显在这个像个小玩具的例子里面不会出什么大错,但是想象一下我们如果有个alert框显示错误给用户,然后我们有个第三方的库进行一个网络调用然后失败了,如果我们不把它封装进$apply里面,Angular永远不会知道失败了,alert框就永远不会弹出来了。

因此,如果你想使用一个jQuery插件,并且要执行$digest循环来更新你的DOM的话,要确保你调用了$apply

有时候我想多说一句的是有些人在不得不调用$apply时会“感觉不妙”,因为他们会觉得他们做错了什么。其实不是这样的,Angular不是什么魔术师,他也不知道第三方库想要更新绑定的数据。

使用$watch来监视你自己的东西

你已经知道了我们设置的任何绑定都有一个它自己的$watch,当需要时更新DOM,但是我们如果要自定义自己的watches呢?简单

来看个例子:

app.js

app.controller('MainCtrl', function($scope) {
  $scope.name = "Angular";

  $scope.updated = -1;

  $scope.$watch('name', function() {
    $scope.updated++;
  });
});

index.html

<body ng-controller="MainCtrl">
  <input ng-model="name" />
  Name updated: {{updated}} times.
</body>

这就是我们创造一个新的$watch的方法。第一个参数是一个字符串或者函数,在这里是只是一个字符串,就是我们要监视的变量的名字,在这里,$scope.name(注意我们只需要用name)。第二个参数是当$watch说我监视的表达式发生变化后要执行的。我们要知道的第一件事就是当controller执行到这个$watch时,它会立即执行一次,因此我们设置updated为-1。

试试看:http://jsbin.com/ucaxan/1/edit

例子2:

app.js

app.controller('MainCtrl', function($scope) {
  $scope.name = "Angular";

  $scope.updated = 0;

  $scope.$watch('name', function(newValue, oldValue) {
    if (newValue === oldValue) { return; } // AKA first run
    $scope.updated++;
  });
});

index.html

<body ng-controller="MainCtrl">
  <input ng-model="name" />
  Name updated: {{updated}} times.
</body>

watch的第二个参数接受两个参数,新值和旧值。我们可以用他们来略过第一次的执行。通常你不需要略过第一次执行,但在这个例子里面你是需要的。灵活点嘛少年。

例子3:

app.js

app.controller('MainCtrl', function($scope) {
  $scope.user = { name: "Fox" };

  $scope.updated = 0;

  $scope.$watch('user', function(newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $scope.updated++;
  });
});

index.html

<body ng-controller="MainCtrl">
  <input ng-model="user.name" />
  Name updated: {{updated}} times.
</body>

我们想要监视$scope.user对象里的任何变化,和以前一样这里只是用一个对象来代替前面的字符串。

试试看:http://jsbin.com/ucaxan/3/edit

呃?没用,为啥?因为$watch默认是比较两个对象所引用的是否相同,在例子1和2里面,每次更改$scope.name都会创建一个新的基本变量,因此$watch会执行,因为对这个变量的引用已经改变了。在上面的例子里,我们在监视$scope.user,当我们改变$scope.user.name时,对$scope.user的引用是不会改变的,我们只是每次创建了一个新的$scope.user.name,但是$scope.user永远是一样的。

例子4:

app.js

app.controller('MainCtrl', function($scope) {
  $scope.user = { name: "Fox" };

  $scope.updated = 0;

  $scope.$watch('user', function(newValue, oldValue) {
    if (newValue === oldValue) { return; }
    $scope.updated++;
  }, true);
});

index.html

<body ng-controller="MainCtrl">
  <input ng-model="user.name" />
  Name updated: {{updated}} times.
</body>

试试看:http://jsbin.com/ucaxan/4/edit

现在有用了吧!因为我们对$watch加入了第三个参数,它是一个bool类型的参数,表示的是我们比较的是对象的值而不是引用。由于当我们更新$scope.user.name$scope.user也会改变,所以能够正确触发。

关于$watch还有很多tips&tricks,但是这些都是基础。

作者:tiramisu_ljh 发表于2016/10/18 11:24:41 原文链接
阅读:40 评论:0 查看评论

CocoaPods - 源码篇

$
0
0

前言


由于项目里业务线很多,集成了很多第三方pod库 和 私有pod库,整个pod project体积非常大。默认的Xcode 编译行为寻找依赖的project进行编译,并且是并行的。



我们做了如下改动,加快主工程编译速度:

.去除了主工程对 Pods target的依赖编译
.取消上面两个勾选
.在 Manage Scheme 里勾选了Pods project,以便于可以手动选择 Pods project进行编译

如果对Pods库更改了,我们可以手动选择 Pods Scheme 进行编译,然后再编译主工程,这样避免Pods不必要编译。


问题

每次pod install之后,pods scheme 自动消失了,我们找到 xcschememanagement.plist文件

cd demo/Pods/Pods.xcodeproj/xcuserdata/Green.xcuserdatad/xcschemes
cat xcschememanagement.plist

# 输出
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
    <key>Pods.xcscheme</key>
    <dict>
        <key>isShown</key>
        <false/>
    </dict>
...

可以看到:Pods.xcscheme isShownfalse,这样导致scheme 中没有Pods

Pod Install 剖析

我们来看看 CocoaPods 源码

cd /Library/Ruby/Gems/2.0.0/gems/
#这里有很多版本,我们只看0.38.2

当我们执行 pod install,其实调用到Installer对象,

#文件位置:`cocoapods-0.38.2/lib/cocoapods/command/project.rb`

#初始化 Installer 对象       
def run_install_with_update(update)
    installer = Installer.new(config.sandbox, config.podfile, config.lockfile)
    installer.update = update

    #install 方法
    installer.install!
  end
end
install!
#文件位置:`cocoapods-0.38.2/lib/cocoapods/installer.rb`文件,

  #install 方法
def install!
  prepare
  resolve_dependencies

  #下载依赖
  download_dependencies
  determine_dependency_product_types
  verify_no_duplicate_framework_names
  verify_no_static_framework_transitive_dependencies
  verify_framework_usage

  #合成 pods project
  generate_pods_project

  integrate_user_project if config.integrate_targets?
  perform_post_install_actions
end
download_dependencies
#下载pods 资源
def download_dependencies
  UI.section 'Downloading dependencies' do
    create_file_accessors
    install_pod_sources
    run_podfile_pre_install_hooks
    clean_pod_sources
  end
end
generate_pods_project
#合成 pods project
def generate_pods_project
  UI.section 'Generating Pods project' do
    prepare_pods_project
    install_file_references
    install_libraries
    set_target_dependencies

    #执行Podfile 的post_install 代码块
    run_podfile_post_install_hooks

    #重新写入pod project,就是在这里修改了所有`pod.xcscheme` 的`isShown`为false
    write_pod_project

    share_development_pod_schemes
    write_lockfiles
  end
end
run post_install
#执行Podfile 的post_install 代码块
def run_podfile_post_install_hooks
  UI.message '- Running post install hooks' do
    executed = run_podfile_post_install_hook
    UI.message '- Podfile' if executed
  end
end


def run_podfile_post_install_hook
    #执行 post_install,这个代码块(block)可以在Podfile里指定
  podfile.post_install!(self)
rescue => e
  raise Informative, 'An error occurred while processing the post-install ' \
    'hook of the Podfile.' \
    "\n\n#{e.message}\n\n#{e.backtrace * "\n"}"
end
post_install

我们经常在 Podfile 里设置 @post_install 代码块:

#文件位置:`cocoapods-core-0.38.2/lib/cocoapods-core/podfile/dsl.rb` 文件

  def post_install(&block)
    @post_install_callback = block
  end    
write_pod_project
# 重新写入pod project,就是在这里修改了所有`pod.xcscheme` 的`isShown`为false
def write_pod_project
  UI.message "- Writing Xcode project file to #{UI.path sandbox.project_path}" do
    pods_project.pods.remove_from_project if pods_project.pods.empty?
    pods_project.development_pods.remove_from_project if pods_project.development_pods.empty?
    pods_project.sort(:groups_position => :below)

    ##重新创建schemes 文件,这里更改了isShown
    pods_project.recreate_user_schemes(false)
    pods_project.predictabilize_uuids if config.deterministic_uuids?
    pods_project.save
  end
end  
recreate_user_schemes
  #重新创建schemes 文件
def recreate_user_schemes(visible = true)
  schemes_dir = XCScheme.user_data_dir(path)
  FileUtils.rm_rf(schemes_dir)
  FileUtils.mkdir_p(schemes_dir)
  xcschememanagement = {}
  xcschememanagement['SchemeUserState'] = {}
  xcschememanagement['SuppressBuildableAutocreation'] = {}

  targets.each do |target|
    scheme = XCScheme.new
    scheme.add_build_target(target)
    scheme.save_as(path, target.name, false)
    xcschememanagement['SchemeUserState']["#{target.name}.xcscheme"] = {}

    #就是在这里修改的。
    xcschememanagement['SchemeUserState']["#{target.name}.xcscheme"]['isShown'] = visible
  end

  xcschememanagement_path = schemes_dir + 'xcschememanagement.plist'
  Xcodeproj.write_plist(xcschememanagement, xcschememanagement_path)
end

这样我们找到了根本问题,其实还是底层做了限制,每次 pod install 会重新生成 scheme 文件,并且每个 pod target 的 isShown 都是 false 。

Podfile 剖析

Podfile 其实是个 Ruby 类,对应 Cocoapods 的 Podfile class,我们可以看看Podfile class源码:

# 文件位置:/Library/Ruby/Gems/2.0.0/gems/cocoapods-core-0.38.2/lib/cocoapods-core/podfile/dsl.rb

module Pod 
 class Podfile
 module DSL
 ...
platform

指定 Pods targetplatform

# @!group Target configuration
#   These settings are used to control the  CocoaPods generated project.
#
#   This starts out simply with stating what `platform` you are working  on. `xcodeproj` allows you to state specifically which project to link with.

# Specifies the platform for which a static library should be built.
#
# CocoaPods provides a default deployment target if one is not specified.
# The current default values are `4.3` for iOS, `10.6` for OS X and `2.0` for watchOS.
#
# If the deployment target requires it (iOS < `4.3`), `armv6` architecture will be added to `ARCHS`.
#
# @param    [Symbol] name
#           the name of platform, can be either `:osx` for OS X, `:ios`
#           for iOS or `:watchos` for watchOS.
#
# @param    [String, Version] target
#           The optional deployment.  If not provided a default value
#           according to the platform name will be assigned.
#
# @example  Specifying the platform
#
#           platform :ios, "4.0"
#           platform :ios
#
# @return   [void]

def platform(name, target = nil)
  # Support for deprecated options parameter
  target = target[:deployment_target] if target.is_a?(Hash)
  current_target_definition.set_platform(name, target)
end
xcodeproj

指定 Pods libraries 可以被哪个 project 链接。

# @Specifies the Xcode project that contains the target that the Pods library should be linked with.
# 
# @param    [String] path
#           the path of the project to link with
#
# @param    [Hash{String => symbol}] build_configurations
#           a hash where the keys are the name of the build
#           configurations in your Xcode project and the values are
#           Symbols that specify if the configuration should be based on
#           the `:debug` or `:release` configuration. If no explicit
#           mapping is specified for a configuration in your project, it
#           will default to `:release`.
#
# @example  Specifying the user project
#
#           # Look for target to link with in an Xcode project called
#           # `MyProject.xcodeproj`.
#           xcodeproj 'MyProject'
#
#           target :test do
#             # This Pods library links with a target in another project.
#             xcodeproj 'TestProject'
#           end
#
# @example  Using custom build configurations
#
#           xcodeproj 'TestProject', 'Mac App Store' => :release, 'Test' => :debug
#
#
# @return   [void]


def xcodeproj(path, build_configurations = {})
  current_target_definition.user_project_path = path
  current_target_definition.build_configurations = build_configurations
end
inhibit_all_warnings

指定 是否需要忽略警告

# @Inhibits **all** the warnings from the CocoaPods libraries.
#
#
# This attribute is inherited by child target definitions.
#
# If you would like to inhibit warnings per Pod you can use the following syntax:
#
#     pod 'SSZipArchive', :inhibit_warnings => true

def inhibit_all_warnings!
  current_target_definition.inhibit_all_warnings = true
end
use_frameworks

指定是否使用 framework

# @Use frameworks instead of static libraries for Pods.
#
# ------
#
# This attribute is inherited by child target definitions.
#    

def use_frameworks!(flag = true)
  current_target_definition.use_frameworks!(flag)
end
workspace

指定 合成的 workspace 路径

# @!group Workspace
#
#   This group list the options to configure workspace and to set global settings.
# Specifies the Xcode workspace that should contain all the projects.
#
# -----
#
# If no explicit Xcode workspace is specified and only **one** project
# exists in the same directory as the Podfile, then the name of that project is used as the workspace’s name.
#
# @param    [String] path
#           path of the workspace.
#
# @example  Specifying a workspace
#
#           workspace 'MyWorkspace'
#
# @return   [void]


def workspace(path)
  set_hash_value('workspace', path.to_s)
end
source

指定 specs 仓库源

# @!group Sources
#
#   The Podfile retrieves specs from a given list of sources (repositories).
#
#   Sources are __global__ and they are not stored per target definition.
# Specifies the location of specs
#
# -----
#
# Use this method to specify sources. The order of the sources is
# relevant. CocoaPods will use the highest version of a Pod of the first
# source which includes the Pod (regardless whether other sources have a
# higher version).
#
# @param    [String] source
#           The URL of a specs repository.
#
# @example  Specifying to first use the Artsy repository and then the
#           CocoaPods Master Repository
#
#           source 'https://github.com/artsy/Specs.git'
#           source 'https://github.com/CocoaPods/Specs.git'
#
# @return   [void]

def source(source)
  hash_sources = get_hash_value('sources') || []
  hash_sources << source
  set_hash_value('sources', hash_sources.uniq)
end
post_install

设置 installer 之后的执行的代码块

# This hook allows you to make any last changes to the generated Xcode
# project before it is written to disk, or any other tasks you might want to perform.
#
# It receives the [`Pod::Installer`](http://rubydoc.info/gems/cocoapods/Pod/Installer/) as its only argument.
#
# @example  Customising the build settings of all targets
#
#   post_install do |installer|
#     installer.pods_project.targets.each do |target|
#       target.build_configurations.each do |config|
#         config.build_settings['GCC_ENABLE_OBJC_GC'] = 'supported'
#       end
#     end
#   end
#
# @return   [void]
#

def post_install(&block)
  @post_install_callback = block
end

Podfile 自定义

我们在Podfile中增加如下代码:

#设置 Podfile 对象 @post_install_callback 成员
self.post_install do |installer|
$KDPod_Project
begin
    $KDPod_Project=installer.project
    rescue
    puts "installer.project is delete"
    $KDPod_Project=installer.pods_project
end
installer.use_default_plugins = false
$KDPod_Project.targets.each do |target|
    #设置ORGANIZATIONNAME 、 CLASSPREFIX
    #target.project.root_object.attributes['ORGANIZATIONNAME']='xxxxx.xxx'
    #target.project.root_object.attributes['CLASSPREFIX']='xxxx'
    target.build_configurations.each do |config|

        #设置target的编译后生成目录
        config.build_settings['CONFIGURATION_TEMP_DIR'] = './build'
        config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO'
    end
end

# 延迟执行
def kdperformSelector(time)
    before=Time.now
    fork do
        sleep(1) until Time.now-before >= time
        yield
    end
end


#延迟3秒 更改pods scheme visabled
kdperformSelector(3){
    ##update pod visabled
    $schemes_dir = Xcodeproj::XCScheme.user_data_dir($KDPod_Project.path)
    $xcschememanagement_path = $schemes_dir + 'xcschememanagement.plist'
    $xcschememanagement_content = Xcodeproj.read_plist($xcschememanagement_path)

    #设置 isShown 属性
    $xcschememanagement_content['SchemeUserState']["Pods.xcscheme"]['isShown'] = true
    FileUtils.rm_rf($xcschememanagement_path)
    Xcodeproj.write_plist($xcschememanagement_content,$xcschememanagement_path)
    #    puts $xcschememanagement_content
    #    $KDPod_Project.recreate_user_schemes(true)
    $KDPod_Project.save
    puts "KDPod_Project.save"
}
end

我们新for一个进程,在 download_dependencies 之后 延迟了3秒执行更新 pods.xschemeisShown 属性。
这样就可以显示Pods Scheme了。

这里只是做个例子,重要的是,了解了源码,我们就可以对project进行其他配置。

作者:weixin_36397543 发表于2016/10/18 11:33:38 原文链接
阅读:41 评论:0 查看评论

理解Android应用内存限制与高效加载大图片

$
0
0

谷歌对android系统的每个app做了内存限制,不同版本的android系统,不同的设备对每个app的内存限制可能有所不同,从早期的16M ,32M到现在的256M,384M...虽然内存增大了,但是不代表就不会出现OOM(OutOfMemory)异常,这个异常大家都懂,比如加载一些分辨率很大的图像就可能超出内存限制,所以我们在加载大图片时,还是要小心处理。

下面通过以下代码获得在Nexus_5X 5.0设备上,一个app的可用内存大小

ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
        int memoryClass = activityManager.getMemoryClass();
        Log.d("memoryinfo","memoryClass="+memoryClass);
log:
D/memoryinfo: memoryClass=384

 在Android3.0(Honeycomb) 有了 “largeHeap” 选项后,可以在app内存本身限定的大小内,调整到一个最大值

可以这么理解吧,在没有“largeHeap”最大内存之前,app的内存最大只能384M,超过这个值,就会出现OOM(OutOfMemory)异常,现在有“largeHeap” 这个概念,就多了一个最大值的概念,比如这个最大值512M,现在如果你在工程的AndroidManifest.xml中添加了android:largeHeap="true",表示该应用最大内存可以调整512M了,超过了512M才会出现OOM(OutOfMemory)异常。

通过以下代码获取在Nexus_5X 5.0设备上,一个app的最大可用内存大小

int largeMemoryClass = activityManager.getLargeMemoryClass();
        Log.d("memoryinfo","largeMemoryClass="+largeMemoryClass);

log:

D/memoryinfo: largeMemoryClass=384

发现该设备两个最大值相等.不是所有设备都一样的

获取是否设置了largeHeap,用以下代码:

AndroidManifest.xml中添加

<application
        android:largeHeap="true"

Log.d("memoryinfo","isLargeHeap="+isLargeHeap(this));

private  boolean isLargeHeap(Context context) {
        return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_LARGE_HEAP) != 0;
    }

log:

D/memoryinfo: isLargeHeap=true

既然现在知道在这个设备上一个app的内存最大为384M,那么就来测试一把。

现在有一张片大小为35M左右的图片


看如下代码:

public void click(View view){
        Log.d("BitmapFactory","click");
        BitmapFactory.Options options = new BitmapFactory.Options();
        for(int i=0;i<5;i++){
            Log.d("BitmapFactory","i="+i);

            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);
            int bytes = bitmap.getAllocationByteCount();//Returns the size of the allocated memory used to store this bitmap's pixels.
            Log.d("BitmapFactory","bytes="+bytes);
            list.add(bitmap);
        }

现在点击button,就添加该图片添加到集合中,先设置了添加5次,结果程序崩溃了,看log:

D/BitmapFactory: click
D/BitmapFactory: i=0
I/art: Alloc partial concurrent mark sweep GC freed 405(25KB) AllocSpace objects, 1(255MB) LOS objects, 40% free, 1755KB/2MB, paused 101us total 10.729ms
D/BitmapFactory: bytes=267845760
D/BitmapFactory: i=1
I/art: Forcing collection of SoftReferences for 255MB allocation
E/art: Throwing OutOfMemoryError "Failed to allocate a 267845772 byte allocation with 4194304 free bytes and 127MB until OOM"
D/skia: --- allocation failed for scaled bitmap
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: cj.com.bitmapfactory, PID: 4180
                  java.lang.IllegalStateException: Could not execute method for android:onClick
                      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
                      at android.view.View.performClick(View.java:4756)
                      at android.view.View$PerformClick.run(View.java:19749)
                      at android.os.Handler.handleCallback(Handler.java:739)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:135)
                      at android.app.ActivityThread.main(ActivityThread.java:5221)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at java.lang.reflect.Method.invoke(Method.java:372)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
                   Caused by: java.lang.reflect.InvocationTargetException
                      at java.lang.reflect.Method.invoke(Native Method)
                      at java.lang.reflect.Method.invoke(Method.java:372)
                      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
                      at android.view.View.performClick(View.java:4756) 
                      at android.view.View$PerformClick.run(View.java:19749) 
                      at android.os.Handler.handleCallback(Handler.java:739) 
                      at android.os.Handler.dispatchMessage(Handler.java:95) 
                      at android.os.Looper.loop(Looper.java:135) 
                      at android.app.ActivityThread.main(ActivityThread.java:5221) 
                      at java.lang.reflect.Method.invoke(Native Method) 
                      at java.lang.reflect.Method.invoke(Method.java:372) 
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899) 
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694) 
                   Caused by: java.lang.OutOfMemoryError: Failed to allocate a 267845772 byte allocation with 4194304 free bytes and 127MB until OOM
                      at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
                      at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
                      at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
                      at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
                      at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:467)
                      at cj.com.bitmapfactory.MainActivity$override.click(MainActivity.java:31)
                      at cj.com.bitmapfactory.MainActivity$override.access$dispatch(MainActivity.java)
                      at cj.com.bitmapfactory.MainActivity.click(MainActivity.java:0)
                      at java.lang.reflect.Method.invoke(Native Method) 
                      at java.lang.reflect.Method.invoke(Method.java:372) 
                      at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
                      at android.view.View.performClick(View.java:4756) 
                      at android.view.View$PerformClick.run(View.java:19749) 
                      at android.os.Handler.handleCallback(Handler.java:739) 
                      at android.os.Handler.dispatchMessage(Handler.java:95) 
                      at android.os.Looper.loop(Looper.java:135) 
                      at android.app.ActivityThread.main(ActivityThread.java:5221) 
                      at java.lang.reflect.Method.invoke(Native Method) 
                      at java.lang.reflect.Method.invoke(Method.java:372) 
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899) 
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694) 
I/Process: Sending signal. PID: 4180 SIG: 9

没错出现OOM异常,通过log发现应该是在第二次添加图片的时候发生了内存溢出。

int bytes = bitmap.getAllocationByteCount();
这个方法是获取存储该张图片开辟的内存大小

一共267845760字节,也就是255.43762207M左右,这就是为什么添加第二张的时候就出现内存溢出了,两张加起来就大于384M了。

但是是不是很奇怪,这张图片本身就35M左右啊,怎么应用给开辟了255M左右的内存呢??

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image, options);
答案在这个方法里。

解码资源文件获取的位图经过了缩放。缩放的依据是根据设备屏幕的密度来的,当前该设备的密度是:420DPI


放大的倍数就是420/160,160就默认的标准密度,这样以来图片的宽高都放大了420/160倍,所以最终图片的大小差不多就是34.9×(420/160)×(420/160)结果大小就差不多250M了

可见虽然内存大小有348M,但是在加载大图片时,也很容易出现OOM异常,所以需要我们在解码图片资源的时候要对大的图片进行缩小。

下面就接着讲一下高效加载大图片的API

官方文档:

https://developer.android.com/training/displaying-bitmaps/index.html

https://developer.android.com/training/displaying-bitmaps/load-bitmap.html

这里就来缩小上边那张35M的大图片:

代码如下:

 public void click(View view){
        Log.d("BitmapFactory","click");
        Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.image, 100, 100);
        int byteCount = bitmap.getAllocationByteCount();
        Log.d("BitmapFactory","byteCount="+byteCount);
    }


还是去解码那张大图片,只不过现在我有要求了,要求经过处理的图片的宽高都是100,然后再打印一下程序为该图片分配的内存大小

原图的宽高:


很大吧


private Bitmap decodeSampledBitmapFromResource(Resources res , int resId, int targetWidth, int tartgetHegiht){
// First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        /**
         * If set to true, the decoder will return null (no bitmap), but
         * the out... fields will still be set, allowing the caller to query
         * the bitmap without having to allocate the memory for its pixels.
         */
        options.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeResource(res, resId, options);
        Log.d("BitmapFactory",bitmap+"");

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, targetWidth, tartgetHegiht);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        Bitmap bitmap2 = BitmapFactory.decodeResource(res, resId, options);
        Log.d("BitmapFactory",bitmap2+"");
        Log.d("BitmapFactory","bitmap2 height ="+bitmap2.getHeight()+"  width=="+bitmap2.getWidth());
        return  bitmap2;
    }

解码图片资源还是用BitmapFactory这个工具

该工具介绍

https://developer.android.com/reference/android/graphics/BitmapFactory.html

BitmapFactory结合这个BitmapFactory.Options来处理图片,首先是获取原始图片的大小,只要设置

options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);暂时不会分配内存,只是查看图片信息,所以返回的位图为null。
然后通图片原始大小和期待的大小,算出一下缩小的比例:
private int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        String imageType = options.outMimeType;

        Log.d("BitmapFactory","Raw height ="+height+"  width=="+width);
        Log.d("BitmapFactory","options.outMimeType ="+imageType);
        /**
         * If set to a value > 1, requests the decoder to subsample the original
         * image, returning a smaller image to save memory. The sample size is
         * the number of pixels in either dimension that correspond to a single
         * pixel in the decoded bitmap. For example, inSampleSize == 4 returns
         * an image that is 1/4 the width/height of the original, and 1/16 the
         * number of pixels. Any value <= 1 is treated the same as 1. Note: the
         * decoder will try to fulfill this request, but the resulting bitmap
         * may have different dimensions that precisely what has been requested.
         * Also, powers of 2 are often faster/easier for the decoder to honor.
         */
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {
            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }
        Log.d("BitmapFactory","inSampleSize ="+inSampleSize);
        return inSampleSize;
    }

缩小的倍数就是2的多少次方,比如1,2,4,8...,
比如期待100*100,原始是480*800,那就是以小的值480为标准,缩小到接近100,但大于100,算出缩小倍数是4,缩小后的大小就是120*200了。
将缩小的比例值的赋值给
options.inSampleSize
然后再设置:
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
重新解码图片资源,最好获取的位图就是缩小的了
看一下log:
D/BitmapFactory: click
D/BitmapFactory: null
D/BitmapFactory: Raw height =4160  width==2336
D/BitmapFactory: options.outMimeType =image/jpeg
D/BitmapFactory: inSampleSize =16
D/BitmapFactory: android.graphics.Bitmap@2c6cbd5d
D/BitmapFactory: bitmap2 height =683  width==383
D/BitmapFactory: byteCount=1046356

图片的宽高都缩放了16倍,咦,不对呀 4160/16 不等于683呀,这还是上面提到的,处理的图片还要根据屏幕密度(dpi)来适配设备,所以又放大了420/160倍,可以算一下就知道了。最终获取的图片的大小是1046356字节,大概1M左右。

因此为了防止OOM异常,有时候对图片的的缩小还是有必要的,图片的显示还要结合UI控件来。


作者:hehe26 发表于2016/10/18 11:48:36 原文链接
阅读:40 评论:0 查看评论

Android 2D绘图(Canvas+paint)详解

$
0
0
目录:
1.重要类概述
2.重要类的常用方法
2.简单View绘制(圆、圆弧、矩形、弧形、圆角矩形、椭圆、文字等)
3.setXfermode(Xfermode xfermode)的运用

1.重要类概述
在2D绘制中我们常用的类,也是两个最重要的类就是Canvas(画布)和Paint(画笔),通过Canvas我们可以设置
绘制的形状和路径,当然仅仅形状和路径是不行的,我们还需要颜色啊,阴影啊,透明度等等的设置,这时候就是Paint的
事情了,Paint的作用主要就是设置绘图的风格,下面我们就总结一下这两个类常用的方法。

2.重要类的常用方法
(1)Canvas:

构造类方法:
Canvas()        //构造方法
Canvas(Bitmap bitmap) //带参构造方法,创建一个以bitmap位图为背景的Canvas

裁切类方法:
		clipPath(Path path, Region.Op op) //根据特殊path组合裁切图像,Region.Op定义了Region支持的区域间运算种类。
		
		clipRect(Rect rect, Region.Op op) //根据矩形组合裁切图像
		
		clipRegion(Region region, Region.Op op)
		
		concat(Matrix matrix) //通过matrix的设置可以对绘制的图形进行绘制伸缩和位移

图形绘制类方法:
		drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) //绘制弧形
		
		drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) //绘制bitmap位图
		
		drawPicture(Picture picture, RectF dst) //绘制图片
		
		drawCircle(float cx, float cy, float radius, Paint paint) //绘制圆
		
		drawLine(float startX, float startY, float stopX, float stopY, Paint paint) //绘制线
		
		drawLines(float[] pts, int offset, int count, Paint paint) //可以选择性的去掉一些数据绘制多条线
		
		drawOval(RectF oval, Paint paint) //绘制椭圆
		
		drawPath(Path path, Paint paint) //绘制路径
		
		drawPoint(float x, float y, Paint paint) //绘制点
		
		drawPoints(float[] pts, int offset, int count, Paint paint) //绘制多个点
		
		drawPosText(String text, float[] pos, Paint paint) //绘制文本,float[] pos指定每个文本位置
		
		drawRect(float left, float top, float right, float bottom, Paint paint) //绘制矩形
		
		drawRoundRect(RectF rect, float rx, float ry, Paint paint) //绘制圆角矩形
		
		drawText(String text, float x, float y, Paint paint) //绘制string文本
		
		drawTextOnPath(char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint) //路径上绘制文本

填充类方法:
		drawRGB(int r, int g, int b) //使用RGB指定颜色填充canvas的bitmap画布
		
		drawARGB(int a, int r, int g, int b) //使用ARGB指定颜色填充canvas的bitmap画布

其他操作类方法:
		save() //保存Canvas状态,save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作
		
		restore() //恢复Canvas之前保存的状态,防止save后对Canvas执行的操作对后续的绘制有影响
		
		rotate(float degrees, float px, float py) //旋转
		
		scale(float sx, float sy) //缩放
		
		skew(float sx, float sy) //扭曲
		
		translate(float dx, float dy) //平移

(2)Paint:
文本设置相关方法:
			isUnderlineText() //判断是否有下划线
			
			setUnderlineText(boolean underlineText) //设置下划线
			
			getLetterSpacing() //获取字符间的间距
			
			setLetterSpacing(float letterSpacing) //设置字符间距
			
			getFontSpacing() //获取行间距
			
			isStrikeThruText() //判断文本是否有删除线
			
			setStrikeThruText(boolean strikeThruText) //设置文本删除线
			
			getTextSize() //获取字体大小
			
			setTextSize(float textSize) //设置字体大小
			
			getTypeface() //获取文字字体类型
			
			setTypeface(Typeface typeface) //设置文字字体类型
			
			getTextSkewX() //获取斜体文字的值
			
			setTextSkewX(float skewX) //设置斜体文字的值,负数为右倾斜,正数为左倾斜 官方推荐-0.25
			
			getTextScaleX() //获取文字水平缩放值
			
			setTextScaleX(float scaleX) //设置文本水平缩放值
			
			getTextAlign() //获取文本对其方式
			
			setTextAlign(Paint.Align align) //设置文本对其方式
			
			ascent() //baseline之上至字符最高处的距离
			
			descent() //baseline下面到字符最低处的距离
			
			measureText(CharSequence text, int start, int end) //测绘文本的宽度
			
		        getTextBounds(char[] text, int index, int count, Rect bounds) //获取文本宽高


			getTextWidths(String text, int start, int end, float[] widths) //精确获取文本宽度
			
			getTextLocale() //获取文本语言地理位置
			
			setTextLocale(Locale locale) //设置文本地理位置,也就是设置对应的语言

绘图设置相关方法:
			//设置画笔颜色
			setARGB(int a, int r, int g, int b) 
			setAlpha(int a) 
			setColor(int color)
			
			//获取画笔颜色
			getAlpha() 
			getColor() 
			
			isAntiAlias() //判断是否抗锯齿
			
			setAntiAlias(boolean aa)  //设置抗锯齿,虽然耗资源耗时间,但是一般会开启
			
			getStyle() //获取画笔样式
			
			setStyle(Paint.Style style) //设置画笔样式,FILL:实心; FILL_OR_STROKE:同时实心和空心; STROKE:空心
			
			setStrokeCap(Cap cap) //设置画笔样式, 圆形(Cap.Round),方形(Cap.SQUARE)
			
			getStrokeWidth() //获取画笔的粗细大小
			
			setStrokeWidth(float width) //设置画笔的粗细大小
			
			clearShadowLayer() //清除阴影层
			
			setShadowLayer(float radius, float dx, float dy, int shadowColor) //设置阴影
			
			getXfermode() //获取图形绘制的像素融合模式
			
			setXfermode(Xfermode xfermode) //设置图形绘制的像素融合模式和叠加模式,就是新绘制的像素与Canvas上对应位置已有的像素按照混合规则进行颜色混合
			
			getShader() //获取图形的渲染方式
			
			setShader(Shader shader) //设置图形的渲染方式,分别有线性渲染(LinearGradient) 环形渲染(RadialGradient) 组合渲染(ComposeShader) 扫描渐变渲染/梯度渲染(SweepGradient)

其他方法:
			reset() //清除画笔复位

2.简单View绘制(圆、圆弧、矩形、弧形、圆角矩形、椭圆、文字等)
我们通过自定义View的形式来展示我们绘画的图形,所以在此之前我们需要搭建一个自定义View的模型
1.添加自定义类继承View类(待会我们将在onDraw()方法中绘制我们的图形)
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-11.
 */
public class DrawCircleView extends View {
    Paint paint;
    public DrawCircleView(Context context) {
        super(context);
    }


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


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }
}

2.布局xml文件中应用(通过 包名.类名 的形式 (就像下面com.example.drawview.DrawTextView一样)指定我们定义的类)
<?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"
    tools:context="com.example.drawview.MainActivity">


    <com.example.drawview.DrawTextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />
</RelativeLayout>

接下来我们就可以在onDraw()方法中绘制我们的图形了

(1)圆的绘制
1.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-11.
 */
public class DrawCircleView extends View {
    Paint paint;
    public DrawCircleView(Context context) {
        super(context);
    }


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


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //初始化画笔
        paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //设置画笔要是,圆形/方形/其他
        paint.setStrokeCap(Paint.Cap.BUTT);
        //设置实心圆
        paint.setStyle(Paint.Style.FILL);
        //设置画笔大小
        paint.setStrokeWidth(2);
        //设置阴影
        paint.setShadowLayer(5,5,5, Color.BLUE);
        //绘制实心圆
        canvas.drawCircle(300,200,100,paint);
        //设置为空心
        paint.setStyle(Paint.Style.STROKE);
        //设置画笔大小
        paint.setStrokeWidth(3);
        //绘制
        canvas.drawCircle(500,200,100,paint);


    }


}

1.2 效果图


(2)矩形绘制
2.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-15.
 */
public class DrawRectView extends View {
    public DrawRectView(Context context) {
        super(context);
    }


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


    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔的样式
        paint.setStrokeCap(Paint.Cap.ROUND);
        //设置画笔粗细大小
        paint.setStrokeWidth(3);
        //设置画笔的所画图形的样式
        paint.setStyle(Paint.Style.FILL);
        //设置画笔的颜色
        paint.setColor(Color.GREEN);
        //绘制矩形 drawRect(left顶点X坐标, left顶点Y坐标, 右底部X坐标, 右底Y坐标, @NonNull Paint paint)
        canvas.drawRect(200,200,400,400,paint);
        //从新设置画笔所画图形样式
        paint.setStyle(Paint.Style.STROKE);
        //重新设置画笔大小
        paint.setStrokeWidth(5);
        //绘制
        RectF rect = new RectF(200,420,400,620);
        canvas.drawRect(rect,paint);




    }
}

2.2 效果图

(3)圆角矩形的绘制
3.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-14.
 */
public class DrawRoundRectView extends View {


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


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


    @Override
    protected void onDraw(Canvas canvas){
      super.onDraw(canvas);
        //声明并初始化
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画出的图形是实心还是空心
        paint.setStyle(Paint.Style.FILL);
        //设置画笔粗细
        paint.setStrokeWidth(2);
        //设置画笔的样式
        paint.setStrokeCap(Paint.Cap.ROUND);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorPrimaryDark));
/*        if (Build.VERSION.SDK_INT>=21){
            //需要在API 21版本及其以后才能使用,和下面效果相同
            canvas.drawRoundRect(100,100,200,200,20,20,paint);
        }*/
        RectF rect = new RectF(200,200,400,400);
        canvas.drawRoundRect(rect,50,50,paint);
        //从新设置画笔大小
        paint.setStrokeWidth(5);
        //设置画笔画出的图形未空心
        paint.setStyle(Paint.Style.STROKE);
        //绘制矩形
        // RectF(left顶点x坐标, left顶点Y坐标, right边底部x坐标, right边底部Y坐标)
        RectF rect2 = new RectF(200,420,400,620);
        //绘制圆角矩形
        //drawRoundRect(@NonNull RectF rect, x方向上的圆角半径, Y方向上的圆角半径, @NonNull Paint paint)
        canvas.drawRoundRect(rect2,50,50,paint);


    }
}

3.2 效果图


(4)椭圆的绘制
4.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-16.
 */
public class DrawOvalView extends View {
    public DrawOvalView(Context context) {
        super(context);
    }


    public DrawOvalView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔笔尖样式
        paint.setStrokeCap(Paint.Cap.ROUND);
        //设置所画图形的填充样式
        paint.setStyle(Paint.Style.FILL);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //实例化一个包裹椭圆的矩形,如果为正方形,则椭圆为圆
        RectF rectF = new RectF(200,200,500,400);
        //设置画布颜色,方便与paint.setColor()对比,看看他们的区别
        canvas.drawColor(Color.BLACK);
        //绘制椭圆
        canvas.drawOval(rectF,paint);
        //设置画笔的大小
        paint.setStrokeWidth(5);
        //设置绘画图形为空心
        paint.setStyle(Paint.Style.STROKE);
        //绘制
        if (Build.VERSION.SDK_INT>=21){
            //API 21以上才能用,与上面的绘画方式效果一致
            canvas.drawOval(200,450,500,650,paint);
        }
    }
}

4.2 效果图

(5)点的绘制
5.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-16.
 */
public class DrawPointView extends View {
    public DrawPointView(Context context) {
        super(context);
    }


    public DrawPointView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        //设置画笔大小,我这边设置的大点,方便观察
        paint.setStrokeWidth(50);
        //设置画笔笔尖样式,默认为Paint.Cap.SQUARE,待会切换一下,你就会知道它的作用了
        // 网上有说在设置paint.setStrokeCap(Paint.Cap.ROUND|SQUARE);之前需要同时设置
        //paint.setStrokeJoin(Paint.Join.ROUND|MITER);我测试了好像不需要也能达到切换的目的
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setColor(getResources().getColor(R.color.colorAccent));
        canvas.drawPoint(200,200,paint);


        paint.setStrokeCap(Paint.Cap.BUTT);
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawPoint(300,200,paint);


        paint.setStrokeCap(Paint.Cap.SQUARE);
        paint.setColor(getResources().getColor(R.color.colorAccent));
        canvas.drawPoint(400,200,paint);


        paint.setStrokeCap(Paint.Cap.BUTT);
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawPoint(500,200,paint);


        //设置画笔样式
        paint.setStrokeCap(Paint.Cap.ROUND);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //绘制多个点
        canvas.drawPoints(new float[]{150,300,250,300,350,300,450,300,550,300},paint);


    }
}

5.2 效果图

(6)线段的绘制
6.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-16.
 */
public class DrawLineView extends View {
    public DrawLineView(Context context) {
        super(context);
    }


    public DrawLineView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔样式
        paint.setStrokeCap(Paint.Cap.ROUND);
        //设置画笔颜色
        paint.setColor(Color.RED);
        //设置画笔大小
        paint.setStrokeWidth(20);
        //画线段,参数分别是线段起始点的坐标和终点的坐标,和预设的画笔
        canvas.drawLine(100,200,600,200,paint);
        //LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
        //TileMode tile),(x0,y0) (x1,y1)源码上说是梯度的坐标,不是很懂,我个人理解为后面设置的每一种颜色渲染的长度,坐标间隔越小,渲染的颜色长度越短
        //int colors[]:指示需要渲染的颜色数组  float positions[]:可为null,为null时颜色均匀分布
        //TileMode tile:指示平铺模式,分别有REPEAT(重复) CLAMP(像素扩散) MIRROR(镜面投影)
        Shader mShader=new LinearGradient(0,0,100,100,
                new int[]{Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW},
                null,Shader.TileMode.REPEAT);
        //设置画笔渲染渐变
        paint.setShader(mShader);
        //绘制渐变线段
        canvas.drawLine(100,300,600,300,paint);


    }
}

6.2 效果图

(7)路径的绘制
7.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-16.
 */
public class DrawPathView extends View {
    public DrawPathView(Context context) {
        super(context);
    }


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


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔笔尖样式
        paint.setStrokeCap(Paint.Cap.ROUND);
        //设置填充模式,默认为FILL
        paint.setStyle(Paint.Style.FILL);
        //设置画笔大小
        paint.setStrokeWidth(3);
        //设置颜色
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        /*Path类的几个...To()方法:
        *   path.moveTo(x,y)移动到某个点
        *   path.lineTo(x,y)从起始点,默认(0,0)点绘制直线到(x,y)点
        *   path.arcTo()用来绘制弧形
        *   path.cubicTo()用来绘制贝塞尔曲线
        *   path.quadTo()也是绘制贝塞尔曲线的
        * */
        //实例化路径类
        Path path = new Path();
        //设置移动,不绘制
        path.moveTo(200, 200);
        //从(200,200)画到(200,400)
        path.lineTo(200, 400);
        //从(200,400)画到(400,400)
        path.lineTo(400, 400);
        //封闭曲线
        path.close();
        //绘制路径
        canvas.drawPath(path, paint);
        //设置颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //设置移动,不绘制
        Path path1 = new Path();
        path1.moveTo(200, 200);
        //从(200,200)画到(200,400)
        path1.lineTo(400, 200);
        //从(200,400)画到(400,400)
        path1.lineTo(400, 400);
        //封闭曲线
        path1.close();
        //绘制
        canvas.drawPath(path1, paint);
        //初始化路径
        Path path2 = new Path();
        //设置矩形
        RectF rectF = new RectF(420, 200, 620, 500);
        //取矩形包裹椭圆0°到270°的弧
        path2.arcTo(rectF, 0, 270);
        //封闭弧形
        path2.close();
        //设置填充实心
        paint.setStyle(Paint.Style.FILL);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //绘制图形
        canvas.drawPath(path2, paint);
        /*
        *quadTo()绘制贝塞尔曲线
         */
        Path path3 = new Path();
        //移动到(200,620)点
        path3.moveTo(200, 620);
        //绘制贝塞尔曲线
        path3.quadTo(300, 420, 400, 620);
        //设置空心
        paint.setStyle(Paint.Style.STROKE);
        //绘制曲线
        canvas.drawPath(path3, paint);
        /*
        * cubicTo()绘制贝塞尔曲线
        * */
        Path path4 = new Path();
        //移动到(300,100)点
        path4.moveTo(300, 100);
        //绘制曲线
        path4.cubicTo(300, 100, 5, 300, 300, 500);
        //设置不填充
        paint.setStyle(Paint.Style.STROKE);
        //绘制
        canvas.drawPath(path4, paint);
    }
}

7.2 效果图

(8)路径文字+简单文字的绘制
8.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Typeface;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-16.
 */
public class DrawTextView extends View {
    public DrawTextView(Context context) {
        super(context);
    }


    public DrawTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        //设置画笔大小
        paint.setStrokeWidth(3);
        //设置字体大小
        paint.setTextSize(30);
        //设置文字样式
        paint.setTypeface(Typeface.DEFAULT_BOLD);
        String str = "我知道,你要说我很帅,哈啊哈哈哈哈,字不够长!";
        //全部显示,起点在(200,100)点
        canvas.drawText(str,50,100,paint);
        //截取4~9的字符串显示
        canvas.drawText(str,4,10,200,200,paint);
        //实例化路径
        Path path = new Path();
        //设置圆形状路径,Direction指示文字显示是逆时针向外还是顺时针向内 Path.Direction.CW|Path.Direction.CCW
        path.addCircle(400,400,110, Path.Direction.CW);
        //更具路径绘制文本
        canvas.drawTextOnPath(str,path,0,0,paint);


        Path path1 = new Path();
        if (Build.VERSION.SDK_INT>=21){
            //API 21以上
            path1.addOval(300,600,500,900, Path.Direction.CCW);
        }
        canvas.drawTextOnPath(str,path1,0,0,paint);
    }
}

8.2 效果图

(9)点文字的绘制
9.1 代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-16.
 */
public class DrawPosTextView extends View {
    public DrawPosTextView(Context context) {
        super(context);
    }


    public DrawPosTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    @Override
    protected void onDraw(Canvas canvas){
        super.onDraw(canvas);
        Paint paint = new Paint();
        //设置抗锯齿
        paint.setAntiAlias(true);
        //设置画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        //设置画笔到校
        paint.setStrokeWidth(5);
        //设置字体颜色
        paint.setTextSize(30);
        canvas.drawColor(Color.BLACK);
        canvas.drawPosText("你看我帅嘛?",new float[]{100,200,200,100,300,200,300,400,200,500,100,400},paint);
    }
}	

9.2 效果图


3.setXfermode(Xfermode xfermode)的运用

setXfermode()方法主要是设置图形绘制的像素融合模式和叠加模式,就是新绘制的像素与Canvas上对应位置已有的像素按照混合规则进行颜色混合
以此实现多样的自定义View。


由于它的混合模式多达18种,下面我只是选取了其中几种,然后分别对不在Canvas上创建新Layer且不限制使用软件渲染与在Canvas上建立新
Layer并且个别模式采用软件渲染模式进行对比,看看他们的区别。如果又需要了解的原理的可以看看下面这篇博客:
http://blog.csdn.net/iispring/article/details/50472485


1)不在Canvas上创建新Layer且不限制使用软件渲染
(1)代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-17.
 */
public class XfermodeView extends View {
    public XfermodeView(Context context) {
        super(context);
    }


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


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);






        /*
        * 默认的Xfermode
        * */


        Paint paint = getPaint();
        //设置绘制圆的画笔颜色
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(200, 200, 100, paint);
        //设置绘制圆角矩形的画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上,当然最好是些兼容性好的,我这里只是为了方便
            canvas.drawRoundRect(200, 200, 300, 350, 10, 10, paint);


        /*
        * PorterDuff.Mode.ADD模式
        * */


        Paint paint1 = getPaint();
        paint1.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(500, 200, 100, paint1);
        //设置像素融合模式
        paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
        paint1.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(500, 200, 600, 350, 10, 10, paint1);
        //取消像素融合模式的设置
        paint1.setXfermode(null);




        /*
        * PorterDuff.Mode.CLEAR模式
        * */
        Paint paint2 = getPaint();
        paint2.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(200, 500, 100, paint2);
        paint2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        paint2.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(200, 500, 300, 650, 10, 10, paint2);
        paint2.setXfermode(null);


         /*
        * PorterDuff.Mode.DARKEN模式
        * */
        Paint paint3 = getPaint();
        paint3.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(500, 500, 100, paint3);
        paint3.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
        paint3.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(500, 500, 600, 650, 10, 10, paint3);
        paint3.setXfermode(null);
		
        /*
        * PorterDuff.Mode.LIGHTEN模式
        * */
        Paint paint4 = getPaint();
        paint4.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(200, 800, 100, paint4);
        paint4.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
        paint4.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(200, 800, 300, 950, 10, 10, paint4);


         /*
        * PorterDuff.Mode.MULTIPLY模式
        * */
        Paint paint5 = getPaint();
        paint5.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(500, 800, 100, paint5);
        paint5.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
        paint5.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(500, 800, 600, 950, 10, 10, paint5);
        paint5.setXfermode(null);


    }


    public Paint getPaint() {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(5);
        return paint;
    }
}

(2)效果截图


2)在Canvas上建立新Layer并且个别模式采用软件渲染模式
(1)代码
package com.example.drawview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;


/**
 * Created by elimy on 2016-10-17.
 */
public class XfermodeView extends View {
    public XfermodeView(Context context) {
        super(context);
    }


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


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);






        /*
        * 默认的Xfermode
        * */


        Paint paint = getPaint();
        //设置绘制圆的画笔颜色
        paint.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(200, 200, 100, paint);
        //设置绘制圆角矩形的画笔颜色
        paint.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上,当然最好是些兼容性好的,我这里只是为了方便
            canvas.drawRoundRect(200, 200, 300, 350, 10, 10, paint);


        /*
        * PorterDuff.Mode.ADD模式
        * */


        //canvas.saveLayer()在canvas原画布上见一个透明的layer
        int layerId = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
        Paint paint1 = getPaint();
        paint1.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(500, 200, 100, paint1);
        //设置像素融合模式
        paint1.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
        paint1.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(500, 200, 600, 350, 10, 10, paint1);
        //取消像素融合模式的设置
        paint1.setXfermode(null);
        //将新图层画到canvas上
        canvas.restoreToCount(layerId);




        /*
        * PorterDuff.Mode.CLEAR模式
        * */
        int layerId2 = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
        Paint paint2 = getPaint();
        paint2.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(200, 500, 100, paint2);
        paint2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        paint2.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(200, 500, 300, 650, 10, 10, paint2);
        paint2.setXfermode(null);
        canvas.restoreToCount(layerId2);
         /*
        * PorterDuff.Mode.DARKEN模式
        * */
        int layerId3 = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
        Paint paint3 = getPaint();
        paint3.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(500, 500, 100, paint3);
        //禁用掉CPU硬件加速,采用软件渲染模式,因为DARKEN、LIGHTEN、OVERLAY等几种混合规则在GPU硬件加速效果展示不出来
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        paint3.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DARKEN));
        paint3.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(500, 500, 600, 650, 10, 10, paint3);
        paint3.setXfermode(null);
        canvas.restoreToCount(layerId3);
        /*
        * PorterDuff.Mode.LIGHTEN模式
        * */
        int layerId4 = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
        Paint paint4 = getPaint();
        paint4.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(200, 800, 100, paint4);
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        paint4.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.LIGHTEN));
        paint4.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(200, 800, 300, 950, 10, 10, paint4);
        paint4.setXfermode(null);
        canvas.restoreToCount(layerId4);
         /*
        * PorterDuff.Mode.MULTIPLY模式
        * */
        int layerId5 = canvas.saveLayer(0, 0, canvas.getWidth(), canvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
        Paint paint5 = getPaint();
        paint5.setColor(getResources().getColor(R.color.colorPrimary));
        canvas.drawCircle(500, 800, 100, paint5);
        paint5.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
        paint5.setColor(getResources().getColor(R.color.colorAccent));
        if (Build.VERSION.SDK_INT >= 21)
            //API 21以上
            canvas.drawRoundRect(500, 800, 600, 950, 10, 10, paint5);
        paint5.setXfermode(null);
        canvas.restoreToCount(layerId5);
    }


    public Paint getPaint() {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setStrokeWidth(5);
        return paint;
    }
}

(2)效果截图

3)PorterDuff.Mode的模式分类
		
        /** [0, 0] */
        CLEAR       (0),
        /** [Sa, Sc] */
        SRC         (1),
        /** [Da, Dc] */
        DST         (2),
        /** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
        SRC_OVER    (3),
        /** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
        DST_OVER    (4),
        /** [Sa * Da, Sc * Da] */
        SRC_IN      (5),
        /** [Sa * Da, Sa * Dc] */
        DST_IN      (6),
        /** [Sa * (1 - Da), Sc * (1 - Da)] */
        SRC_OUT     (7),
        /** [Da * (1 - Sa), Dc * (1 - Sa)] */
        DST_OUT     (8),
        /** [Da, Sc * Da + (1 - Sa) * Dc] */
        SRC_ATOP    (9),
        /** [Sa, Sa * Dc + Sc * (1 - Da)] */
        DST_ATOP    (10),
        /** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
        XOR         (11),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
        DARKEN      (12),
        /** [Sa + Da - Sa*Da,
             Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
        LIGHTEN     (13),
        /** [Sa * Da, Sc * Dc] */
        MULTIPLY    (14),
        /** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
        SCREEN      (15),
        /** Saturate(S + D) */
        ADD         (16),
        OVERLAY     (17);

上面这段代码来之源码,看了半天也没看出啥来,话说那些注释是啥意思呢,啥Sa又Da,宝宝头都大了?
网上看了一些文字解释,原来这样:
Sa->Source alpha->源图的Alpha通道
Da->Destination alpha->目标图的Alpha通道
Sc->Source color->源图的颜色
Dc->Destination color ->目标图的颜色
然后呢这样混合过后就组成了最后的ARGB值,形成最终的效果
作者:qq_28057577 发表于2016/10/18 11:50:01 原文链接
阅读:42 评论:0 查看评论

打造超越 EventBus 的事件管理框架

$
0
0

EventPoster

一.目的
二.概述以及优势
1.模块化,易扩展
2.缓存管理
3.预加载
4.对于注册的实例的管理,防止 Leak
5.各模块 Handler 的管理
三.用法
1.与 MVP 结合使用
2.接口
3.扩展模块

一.目的
纯粹的想做一个轮子,已经有 EventBus 了,为什么还要去做?应为个人觉得·EvntBus还缺点东西。事件本身多种多样,绝不仅仅只是自定义的一些事件。在Android平台上,各种各样的事件驱动着代码的逻辑。View 事件,ActivityLife 事件,Receiver 事件,以及类似 EventBus 的自定义事件。简单来说 EvntPoster 就是一个综合的事件中心,它模块化,可扩展,接口统一,性能优秀。
二.概述以及优势
1.模块化,易扩展
EventPoster 是模块化的,简单的说由核心框架,以及各个模块组成,目前完成的有四个模块:View 事件,ActivityLife 事件,Receiver 事件,以及类似 EventBus 的自定义事件。如果需要扩展模块,仅仅需要实现事件相应的 Handler,Annotation ,以及缓存实体模型。核心框架会帮各个模块管理缓存,以及预加载等。
2.缓存管理
为了减少开销,框架自然会避免重复的反射操作。核心框架会管理以 Annotation 对象为 Key 的缓存实体,实体内会存储反射结果。
3.预加载
为了避免在·regist 的时候给予主线程过多的压力,核心框架提供了异步预加载的支持,实现 Handler 接口的 parse 方法返回包含反射结果的实体对象。这是线程安全的,在Application 内可以启用多个线程对需要注册的类进行预加载。这样通过缓存以及预加载,在事件 regist 以及发生的时候,反射操作有且只有 invoke,性能接近接口通讯。
4.对于注册的实例的管理,防止 Leak
默认仅有核心框架持有注册的 Object 对象,各个模块可以使用 api getInsts(Class clazz) 从核心框架获取相应类的实例。原则上不建议各模块私自持有 Object 实例。
5.各模块 Handler 的管理
用户通过框架主类 EventPoster 可以通过工厂方法获得各模块的 Handler 实例。原则上各个模块的 Handler 都是单例的。

三.用法
1.与 MVP 结合使用
框架内提供了一个简单的 MVP Presenter 的微框架。为了让 Activity/Fragment 纯粹成为一个View 的容器,只保留 set/get 方法,同时 Presenter 又需要获得绝对的控制力。所以 Presenter 需要监听到各个事件。View 的点击触摸,Receiver 的发生,Activity 生命周期,以及一些自定义事件等。
2.接口
- regist 各种事件统一调用 EventPoster.Regist(object) 即可,如果想解析父类中的事件即调用 EventPoster.RegistDeep. UnRegist 同理。
- 获取某个模块的 Handler EventPost.With(Class handlerType);然后调用各自模块的接口
- 预加载,Application 中调用 EventPoster.PreLoad(Class[] classes); 递归加载包括父类的 EventPoster.PreLoadDeep(Class[] classes);

3.扩展模块
- 自定义 Annotation 加上@AnnotationBase(你的 Handler.class)
- 自定义 Handler 实现 init destory parse load unload 以及特殊需求时穿透的 regist remove方法。
- 实现 EventEntity 缓存实体。

框架地址 :GitHub

作者:ganyao939543405 发表于2016/10/18 11:53:40 原文链接
阅读:64 评论:0 查看评论

异步任务

$
0
0

1.  一个线程里面只有一个Looper。

2. 子线程也可创建handler。

前后需分别加上Looper.prepare();和Looper.loop();

标准写法:

Looper.prepare();

Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

if (msg.what == 10000) {

Log.i(TAG, "在子线程中定义Handler,并接收到消息。。。");

}

}};

Looper.loop();

prepare 中创建looper。

3. 主线程对应的方法在ActivityThread的main方法中。

4. Looper的构造方法中初始化了一个MessageQueue对象

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

总: Looper.prepare()方法初始话了一个Looper对象并关联在一个MessageQueue对象,并且一个线程中只有一个Looper对象,只有一个MessageQueue对象。而Handler的构造方法则在Handler内部维护了当前线程的Looper对象

5. handler.sendMessage(msg) 会调用queue.enqueueMessage(msg, uptimeMillis);,会发现 MessageQueue并没有使用列表将所有的Message保存起来,而是使用Message.next保存下一个Message,从而按照时间将所有的Message排序;

6. Looper.loop()

可以看到Looper.loop()方法里起了一个死循环,不断的判断MessageQueue中的消息是否为空,如果为空则直接return掉,然后执行queue.next()方法

可以看到其大概的实现逻辑就是Message的出栈操作,里面可能对线程,并发控制做了一些限制等。获取到栈顶的Message对象之后开始执行:

msg.target.dispatchMessage(msg);

Handle的dispatchMessage()

/**  

* Handle system messages here. */

public void dispatchMessage(Message msg) {

if (msg.callback != null) {

handleCallback(msg);

} else {

if (mCallback != null) {

if (mCallback.handleMessage(msg)) {

return;

}

 }

handleMessage(msg);

}

}

可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法:

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">handleCallback</span>(Message message) {
        message.callback.run();
    }</code>
而如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。由于Handler对象是在主线程中创建的,所以handler的handlerMessage方法的执行也会在主线程中。

原作者的总结:

1)主线程中定义Handler,直接执行:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">Handler mHandler = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Handler() {
        <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">handleMessage</span>(Message msg) {
               <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.handleMessage(msg);
        }
};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

而如果想要在子线程中定义Handler,则标准的写法为:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 初始化该线程Looper,MessageQueue,执行且只能执行一次</span>
                Looper.prepare();
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 初始化Handler对象,内部关联Looper对象</span>
                Handler mHandler = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Handler() {
                    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">handleMessage</span>(Message msg) {
                        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.handleMessage(msg);
                    }
                };
                <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// 启动消息队列出栈死循环</span>
                Looper.loop();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li></ul>

2)一个线程中只存在一个Looper对象,只存在一个MessageQueue对象,可以存在N个Handler对象,Handler对象内部关联了本线程中唯一的Looper对象,Looper对象内部关联着唯一的一个MessageQueue对象。

3)MessageQueue消息队列不是通过列表保存消息(Message)列表的,而是通过Message对象的next属性关联下一个Message从而实现列表的功能,同时所有的消息都是按时间排序的。

4)android中两个子线程相互交互同样可以通过Handler的异步消息机制实现,可以在线程a中定义Handler对象,而在线程b中获取handler的引用并调用sendMessage方法。

5)activity内部默认存在一个handler的成员变量,android中一些其他的异步消息机制的实现方法: 
Handler的post方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">mHandler.post(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> Runnable() {
                    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
                    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">run</span>() {

                    }
                });</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

查看其内部实现:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">post</span>(Runnable r)
    {
       <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span>  sendMessageDelayed(getPostMessage(r), <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>);
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>

可以发现其内部调用就是sendMessage系列方法。。。

view的post方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">boolean</span> <span class="hljs-title" style="box-sizing: border-box;">post</span>(Runnable action) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> AttachInfo attachInfo = mAttachInfo;
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (attachInfo != <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">null</span>) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> attachInfo.mHandler.post(action);
        }
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// Assume that post will succeed later</span>
        ViewRootImpl.getRunQueue().post(action);
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">true</span>;
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>

可以发现其调用的就是activity中默认保存的handler对象的post方法。

activity的runOnUiThread方法:

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">runOnUiThread</span>(Runnable action) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
            action.run();
        }
    }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li></ul>

判断当前线程是否是UI线程,如果不是,则调用handler的post方法,否则直接执行run方法。

http://blog.csdn.net/qq_23547831/article/details/50751687


作者:cch___ 发表于2016/10/18 12:16:46 原文链接
阅读:25 评论:0 查看评论

Android Studio 基本设置

$
0
0




一般来说,Android开发者最初开发都是使用的Eclipse,到后面会用Android Studio工具来开发,使用前一般要做一些简单设置。

以下是Studio中常见的设置内容:
界面、字体、代码格式、默认文件编码、快捷键
其他:编辑区竖线、显示行、显示空格、Git版本控制、插件、检查更新、自动导入、导包、SDK导入、禁止SDK自动更新等等。

有时候Studio的版本不一样,它设置的位置也会有变化,这时使用搜索功能就特别重要了。
当然,关键字还是要记得的,比如:设置字体用font

如图所示:

关键字font


一.进入设置页面

进入设置页面
我这里是Studio的最新版本,其他版本有可能会不一样。
使用快捷键Ctrl+Alt+S也可以直接进入设置页面。
设置页面如下:


下面是具体内容的设置

set

二.界面设置

默认的 Android Studio 为灰色界面,可以选择使用炫酷的黑色界面。Settings –> Appearance –> Theme ,选择 Darcula 主题即可。
theme

三.字体设置

(一)系统字体设置

如果你的Android Studio界面中,中文显示有问题,或者选择中文目录显示有问题,或者想修改菜单栏的字体,可以这么设置。Settings –> Appearance ,勾选 Override default fonts by (not recommended) ,选择一款支持中文的字体即可。我使用的是 微软雅黑 ,效果不错。
SystemFont

(二)编程字体设置

此部分会修改编辑器的字体,包含所有的文件显示的字体。Settings –> Editor –> Colors & Fonts –> Font 。默认系统显示的 Scheme 为 Defualt ,你是不能编辑的,你需要点击右侧的 Save As… ,保存一份自己的设置,并在当中设置。之后,在 Editor Font 中即可设置字体。Show only monospaced fonts 表示只显示等宽字体,一般来说,编程等宽字体使用较多,且效果较好。
Font

(三)Settings –> Editor –> Colors & Fonts 中可以还可以设置字体的颜色,你可以根据你要设置的对象进行选择设置,同时你也可以从网络上下载字体颜色设置包导入。

FontColor

四.代码格式设置

如果你想设置你的代码格式化时显示的样式,你可以这么设置。Settings –> Code Style 。同样的, Scheme 中默认的配置,你无法修改,你需要创建一份自己的配置。
CodeStyle

五.默认文件编码

无论是你个人开发,还是在项目组中团队开发,都需要统一你的文件编码。出于字符兼容的问题,建议使用 utf-8 。中国的 Windows 电脑,默认的字符编码为 GBK 。Settings –> File Encodings 。建议将 IDE Encoding 、 Project Encoding 、 Properties Fiels 都设置成统一的编码。

Encoding

六.快捷键

Android Studio的快捷键和Eclipse的不相同,但是你可以在Android Studio中使用Eclipse的快捷键。Settings –> Keymap 。
keymap
本人原本使用的是Eclipse,所以使用Eclipse的风格比较符合个人开发习惯。

当你想设置在某一个快捷键配置上进行更改,你需要点击 copy 创建一个自己的快捷键,并在上面进行设置。Android Studio默认的快捷键中,代码提示为 Ctrl+Space ,会与系统输入法快捷键冲突,需要特殊设置。Main menu –> Code –> Completion –> Basic ,更改为你想替换的快捷键组合。

七.其他设置(有些就不附图片了)

(一)Android Studio编辑区域,在中部会有一条竖线。这条线是用以提醒程序员,一行的代码长度最好不要超过这条线。如果你不想显示这条线,可以这么设置。Settings –> Editor –> Appearance ,取消勾选 Show right margin (configured in Code Style options) 。

图片和下面的一样。

(二)显示行号Settings –> Editor –> Appearance ,勾选 Show line numbers 。

lines

(三)显示空格。

我习惯显示空格,这样就能看出缩进是 tab 缩进还是空格缩进。建议使用空格缩进。Settings –> Editor –> Appearance ,勾选 Show whitespaces 。
图片和上面的一样。

(四)如果你使用 Git 进行版本控制,你需要设置 Git 的安装文件目录。Settings –> Version Control –> Git ,在右侧中选择你的 Git 的安装目录。

(五)插件。

Android Studio和Eclipse一样,都是支持插件的。Android Studio默认自带了一些插件,如果你不使用某些插件,你可以禁用它。Settings –> Plugins ,右侧会显示出已经安装的插件列表。取消勾选即可禁用插件。
plugins
插件不要下很多,要先了解它的功能、用法之后觉得很有用才下载。

(六)检查更新。

Android Studio支持自动检查更新。之前尚未发布正式版时,一周有时会有几次更新。你可以设置检查的类型,用以控制更新类型。Settings –> Updates 。勾选 Check for updates in channel ,即开通了自动检查更新。你可以禁用自动检查更新。右侧的列表,是更新通道。

Stable Channel : 正式版本通道,只会获取最新的正式版本。

Beta Channel : 测试版本通道,只会获取最新的测试版本。

Dev Channel : 开发发布通道,只会获取最新的开发版本。

Canary Channel : 预览发布通道,只会获取最新的预览版本。

以上4个通道中, Stable Channel 最稳定,问题相对较少, Canary Channel 能获得最新版本,问题相对较多。
update

(七)自动导入。

当你从其他地方复制了一段代码到Android Studio中,默认的Android Studio不会自动导入这段代码中使用到的类的引用。你可以这么设置。Settings –> Editor –> Auto Import ,勾选 Add unambiguous improts on the fly 。

auto

(八)有时很多人运行Android Studio会提醒你 JDK 或者 Android SDK 不存在,你需要重新设置。你需要到全局的Project Structure 页面下进行设置。进入全局的 Project Structure 页面方法如下:

选择 File –> Other Settings –> Default Project Structure

sdk

在下面页面下设置 JDK 或者 Android SDK 目录即可。

以上就是AndroidStudio的简单设置。对于第一次使用Studio一般都要设置一下的。

Eclipse的快捷键有些能在Studio上使用,有些不能!
比如Alt+/可以在Eclipse中进行补全,但是Studio不能~!

其实下面的快捷键不常用!!!~~~
—-常用快捷键
  1.Ctrl+E,可以显示最近编辑的文件列表
  2.Shift+Click可以关闭文件
  3.Ctrl+[或]可以跳到大括号的开头结尾
  4.Ctrl+Shift+Backspace可以跳转到上次编辑的地方
  5.Ctrl+F12,可以显示当前文件的结构
  6.Ctrl+F7可以查询当前元素在当前文件中的引用,然后按F3可以选择
  7.Ctrl+N,可以快速打开类
  8.Ctrl+Shift+N,可以快速打开文件
  9.Alt+Q可以看到当前方法的声明
  10.Ctrl+W可以选择单词继而语句继而行继而函数
  11.Alt+F1可以将正在编辑的元素在各个面板中定位
  12.Ctrl+P,可以显示参数信息
  13.Ctrl+Shift+Insert可以选择剪贴板内容并插入
  14.Alt+Insert可以生成构造器/Getter/Setter等
  15.Ctrl+Alt+V 可以引入变量。例如把括号内的SQL赋成一个变量
  16.Ctrl+Alt+T可以把代码包在一块内,例如try/catch
  17.Alt+Up and Alt+Down可在方法间快速移动
—-不常用快捷键
  18.在一些地方按Alt+Enter可以得到一些Intention Action,例如将”==”改为”equals()”
  19.Ctrl+Shift+Alt+N可以快速打开符号
  20.Ctrl+Shift+Space在很多时候都能够给出Smart提示
  21.Alt+F3可以快速寻找
  22.Ctrl+/和Ctrl+Shift+/可以注释代码
  23.Ctrl+Alt+B可以跳转到抽象方法的实现
  24.Ctrl+O可以选择父类的方法进行重写
  25.Ctrl+Q可以看JavaDoc
  26.Ctrl+Alt+Space是类名自动完成
  27.快速打开类/文件/符号时,可以使用通配符,也可以使用缩写
  28.Live Templates! Ctrl+J
  29.Ctrl+Shift+F7可以高亮当前元素在当前文件中的使用
  30.Ctrl+Alt+Up /Ctrl+Alt+Down可以快速跳转搜索结果
  31.Ctrl+Shift+J可以整合两行
  32.Alt+F8是计算变量值

作者:wenzhi20102321 发表于2016/10/18 12:26:30 原文链接
阅读:33 评论:0 查看评论

Android 从零开始打造异步处理框架

$
0
0

转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/52847872
本文出自【赵彦军的博客】

概述

在Android中会使用异步任务来处理耗时操作,避免出现界面卡顿的问题,当然到目前为止可以使用的异步任务框架有很多,比如:

  • 直接 new Thread()
  • 用Android自带的AsyncTask
  • 用RxJava
  • 等等

    今天我们就来自己尝试写一个异步任务处理框架,代码的设计思路参考AsyncTask

封装尝试

既然是异步的框架,那么肯定是在子线程中,所以第一步我们用自定义的ThreadTask继承Thread. 并且重写里面的run方法。

package com.zyj.app;

/**
 * Created by ${zyj} on 2016/10/17.
 */

public class ThreadTask extends Thread {

    @Override
    public void run() {
        super.run();
    }
}

然后子线程需要把处理结果回调给主线程,我们需要定义3个方法:

  • onStart 任务开始之前调用,运行在主线程。可以做显示进度条或者加载动画。
  • onDoInBackground 异步任务执行,运行在子线程。可以做耗时操作。
  • onResult 异步任务处理的结果,运行在主线程。

    onDoInBackground这个方法是要在子类中实现的,所以要写成抽象的方法,那么ThreadTask类自然也要写成抽象类。同时这个方法会返回异步处理结果,这个结果的类型需要写成泛型,以便在子类中灵活运用。

package com.zyj.app;

import android.support.annotation.MainThread;
import android.support.annotation.WorkerThread;

/**
 * Created by ${zyj} on 2016/10/17.
 */

public abstract class ThreadTask<T> extends Thread  {

    @Override
    public void run() {
        super.run();
    }

    /**
     * 任务开始之前调用,运行在主线程
     */
    @MainThread
    public void onStart(){ }

    /**
     * 子线程中调用,运行在子线程
     * @return
     */
    @WorkerThread
    public abstract T onDoInBackground() ;

    /**
     * 子线程返回的结果,运行在主线程
     * @param t
     */
    @MainThread
    public void onResult( T t ){ }
}

另外子线程和主线程通信我们用的是Handler。Handler的初始化工作放在ThreadTask构造函数中完成。

    private Handler handler ;

    public ThreadTask(){
        handler = new Handler( Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //在这里接收子线程发过来的消息
            }
        } ;
    }

最后还需要一个execute() 方法启动线程。在启动的前一刻最好调用Onstart方法。

    /**
     * 开始执行
     */
    public void execute(){
        onStart();
        start();
    }

最后一个完整的ThreadTask类是这样的

package com.zyj.app;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.MainThread;
import android.support.annotation.WorkerThread;

/**
 * Created by ${zyj} on 2016/10/17.
 */

public abstract class ThreadTask<T> extends Thread  {

    private Handler handler ;

    public ThreadTask(){
        handler = new Handler( Looper.getMainLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //在这里接收子线程发过来的消息
                onResult((T) msg.obj);
            }
        } ;
    }

    @Override
    public void run() {
        super.run();

        Message message = Message.obtain() ;
        message.obj = onDoInBackground() ;
        handler.sendMessage( message ) ;
    }

    /**
     * 任务开始之前调用,运行在主线程
     */
    @MainThread
    public void onStart(){ }

    /**
     * 子线程中调用,运行在子线程
     * @return
     */
    @WorkerThread
    public abstract T onDoInBackground() ;

    /**
     * 子线程返回的结果,运行在主线程
     * @param t
     */
    @MainThread
    public void onResult( T t ){ }


    /**
     * 开始执行
     */
    public void execute(){
        onStart();
        start();
    }
}

如何使用我们写好的框架?


    new ThreadTask<String>(){

        @Override
        public void onStart() {
            super.onStart();
            Log.d( "ThreadTask " , "onStart线程:" + Thread.currentThread().getName() ) ;
        }

        @Override
        public String onDoInBackground() {
            Log.d( "ThreadTask " , "onDoInBackground线程: " + Thread.currentThread().getName() ) ;

            //模拟耗时操作
            try {
                Thread.sleep( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "结果返回了";
        }

        @Override
        public void onResult(String s) {
            super.onResult(s);
            Log.d( "ThreadTask " , "onResult线程: " + Thread.currentThread().getName()  + " 结果:" + s ) ;
        }
    }.execute();

运行的结果:

ThreadTask: onStart线程:main
ThreadTask: onDoInBackground线程: Thread-229
ThreadTask: onResult线程: main 结果:结果返回了

Handler优化

到目前为止我们的框架初步就封装好了,但是有没有缺点呢,肯定是有的。首先每次创建一个ThreadTask的时候都会创建一个Handler,这显然不是我们想看到的。

  • 要保证Handler的实例的唯一性,可以用单例模式来获取Handler
    /**
     * 单例模式,保证handler只有一个实例
     * @return
     */
    private static Handler getHandler(){
        if ( handler == null ){
            synchronized ( MHandler.class ){
                if ( handler == null ){
                    handler= new MHandler( Looper.getMainLooper()) ;
                }
            }
        }
        return handler ;
    }
  • MHandler是我们自定义的一个Handler类
    private static class MHandler extends Handler {

        public MHandler( Looper looper ){
            super( looper );
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //在这里接收子线程发过来的消息
            ResultData resultData = (ResultData) msg.obj;
            resultData.threadTask.onResult( resultData.data );
        }
    }
  • ResultData是一个消息实体
  /**
     * handler发送数据的实体
     * @param <Data>
     */
    private static class ResultData<Data>{
        ThreadTask threadTask ;
        Data data ;
        public ResultData( ThreadTask threadTask  ,Data data  ){
            this.threadTask = threadTask ;
            this.data = data ;
        }
    }
  • 一个完整的代码实例
package com.zyj.app;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.MainThread;
import android.support.annotation.WorkerThread;

/**
 * Created by ${zyj} on 2016/10/17.
 */

public abstract class ThreadTask<T> extends Thread  {

    private static Handler handler ;

    public ThreadTask(){
    }

    @Override
    public void run() {
        super.run();
        Message message = Message.obtain() ;
        message.obj = new ResultData<T>( this , onDoInBackground() ) ;
        getHandler().sendMessage( message ) ;
    }

    /**
     * 任务开始之前调用,运行在主线程
     */
    @MainThread
    public void onStart(){ }

    /**
     * 子线程中调用,运行在子线程
     * @return
     */
    @WorkerThread
    public abstract T onDoInBackground() ;

    /**
     * 子线程返回的结果,运行在主线程
     * @param t
     */
    @MainThread
    public void onResult( T t ){ }


    /**
     * 开始执行
     */
    public void execute(){
        onStart();
        start();
    }

    /**
     * 单例模式,保证handler只有一个实例
     * @return
     */
    private static Handler getHandler(){
        if ( handler == null ){
            synchronized ( MHandler.class ){
                if ( handler == null ){
                    handler= new MHandler( Looper.getMainLooper()) ;
                }
            }
        }
        return handler ;
    }

    private static class MHandler extends Handler {

        public MHandler( Looper looper ){
            super( looper );
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //在这里接收子线程发过来的消息
            ResultData resultData = (ResultData) msg.obj;
            resultData.threadTask.onResult( resultData.data );
        }
    }

    /**
     * handler发送数据的实体
     * @param <Data>
     */
    private static class ResultData<Data>{
        ThreadTask threadTask ;
        Data data ;
        public ResultData( ThreadTask threadTask  ,Data data  ){
            this.threadTask = threadTask ;
            this.data = data ;
        }
    }
}

到现在已经解决了Handler多次创建的问题,那么这个ThreadTask本质上还是新建线程来运行异步任务,为了避免不断的创建线程,所以还需要一个线程池。

线程优化

  • 首选定义一个线程池,默认最大10个线程。
    /**
     * 线程池,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
     */
    private static ExecutorService executorService = Executors.newFixedThreadPool( 15 ) ;
  • 修改run()方法。
    private void run() {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                Message message = Message.obtain() ;
                message.obj = new ResultData<T>( ThreadTask.this , onDoInBackground() ) ;
                getHandler().sendMessage( message ) ;
            }
        });
    }
  • execute() 方法
    /**
     * 开始执行
     */
    public void execute(){
        onStart();
        run();
    }
  • 完整的代码实例
package com.zyj.app;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.MainThread;
import android.support.annotation.WorkerThread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by ${zyj} on 2016/10/17.
 */

public abstract class ThreadTask<T>  {

    private static Handler handler ;

    /**
     * 线程池,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
     */
    private static ExecutorService executorService = Executors.newFixedThreadPool( 15 ) ;

    public ThreadTask(){

    }

    private void run() {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                Message message = Message.obtain() ;
                message.obj = new ResultData<T>( ThreadTask.this , onDoInBackground() ) ;
                getHandler().sendMessage( message ) ;
            }
        });
    }

    /**
     * 任务开始之前调用,运行在主线程
     */
    @MainThread
    public void onStart(){ }

    /**
     * 子线程中调用,运行在子线程
     * @return
     */
    @WorkerThread
    public abstract T onDoInBackground() ;

    /**
     * 子线程返回的结果,运行在主线程
     * @param t
     */
    @MainThread
    public void onResult( T t ){ }


    /**
     * 开始执行
     */
    public void execute(){
        onStart();
        run();
    }

    /**
     * 单例模式,保证handler只有一个实例
     * @return
     */
    private static Handler getHandler(){
        if ( handler == null ){
            synchronized ( MHandler.class ){
                if ( handler == null ){
                    handler= new MHandler( Looper.getMainLooper()) ;
                }
            }
        }
        return handler ;
    }

    private static class MHandler extends Handler {

        public MHandler( Looper looper ){
            super( looper );
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //在这里接收子线程发过来的消息
            ResultData resultData = (ResultData) msg.obj;
            resultData.threadTask.onResult( resultData.data );
        }
    }

    /**
     * handler发送数据的实体
     * @param <Data>
     */
    private static class ResultData<Data>{
        ThreadTask threadTask ;
        Data data ;
        public ResultData( ThreadTask threadTask  ,Data data  ){
            this.threadTask = threadTask ;
            this.data = data ;
        }
    }
}

框架使用

  • 方式1
        new ThreadTask<String>(){

            @Override
            public String onDoInBackground() {
                return "我是线程";
            }
        }.execute();
  • 方式2
    new MyTask().execute();

    class MyTask extends ThreadTask<String> {

        @Override
        public void onStart() {
            super.onStart();
        }

        @Override
        public String onDoInBackground() {
            try {
                //模拟耗时操作
                Thread.sleep( 2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "ThreadTask" ;
        }

        @Override
        public void onResult(String s) {
            super.onResult(s);
        }
    }

参考资料

【1】Android AsyncTask 深度理解、简单封装、任务队列分析、自定义线程池
【2】Android 自定义线程池的实战
【3】Java 单例模式
【4】Android Handler、Loop 的简单使用
【5】Android 更新UI的几种方式

作者:zhaoyanjun6 发表于2016/10/18 12:34:00 原文链接
阅读:73 评论:0 查看评论

在Android动画中使用RxJava

$
0
0

在android中实现动画是非常容易的,ViewPropertyAnimator提供了开箱即用的解决方案能够非常容易的创建属性动画。将它与RxJava结合起来你将得到可以链接不同动画,产生不同随机行为等功能的强大工具。

开始之前需要注意:这篇博客的目的是向你展示在android中怎样把RxJava与动画结合起来在不用写太多嵌套代码的情况下创建一个良好的用户界面。为了掌握这篇博客对于RxJava基础知识的了解是必要的。即使你对RxJava不是太了解读完这篇博客你也应该能够了解到RxJava的强大与灵活性。怎样有效的使用他。本篇文章的所有代码均使用kotlin语言编写所以为了能够有效的运行例子代码,你需要在android studio上安装kotlin插件。本文的源码地址:https://github.com/chenyi2013/RxJavaAndAnimation

属性动画的基础

整篇文章,我们将使用通过调用ViewCompat.animate(targetView)函数来得到ViewPropertyAnimatorCompat。这个类能够自动优化在视图上选择的属性动画。它的语法方便为视图动画提供了极大的灵活性。

让我们来看看怎样使用他来为一个简单的视图添加动画。我们缩小一个按钮(通过缩放他到0)当动画结束的时候将它从父布局中移除。

ViewCompat.animate(someButton)
.scaleX(0f)// Scale to 0 horizontally
.scaleY(0f)// Scale to 0 vertically
.setDuration(300)// Duration of the animation in milliseconds.
.withEndAction{removeView(view)}// Called when the animation ends successfully.

这很方便和简单,但是在更加复杂的场景下事情可能会变得非常混乱,尤其是在withEndAction{}中使用嵌套回调的时候(当然你也可以使用 setListener() 来为每一个动画场景提供回调例如开始动画、取消动画)

添加RxJava

使用RxJava,我们将这个嵌套的listener转换为发送给observers的事件。因此对于每一个view我们都能够进行动画,例如调用onNext(view) 让他按顺序对view进行处理。

一种选择是通过创建简单的自定义操作符来为我们处理各种动画,例如创建水平或垂直方向上的平移动画。

接下来的例子在实际开发中可能不会用到,但是他将展示RxJava动画的强大威力,
如下图所示,在界面的左右两端分别放置一个正方形,初始的时候在左方的正方形中有一组圆,当点击下方的 “Animation”按钮的时候我们想让在左方的正方形中的圆依次移动到右方的正方形中,当再一次按下这个按钮的时候,动画应该逆转,圆应该从右边移动到左边。这些圆应该在相同的时间间隔内按顺序依次进行移动。

让我们来创建一个operator, 他将接收一个view然后执行它的动画并将这个view传递到subscriber的onNext方法,在这种情况下,RxJava会按顺序一个一个的执行每一个view的动画,如果前一个view的动画没有执行完,RxJava会处于等待状态直到之前的view的动画被传递完成才会执行当前view的动画。当然你也可以自定义操作符让当前的view不必等待上一个view的动画播放完成就立即执行动画播放。


TranslateViewOperator.kt

import android.support.v4.view.ViewCompat
import android.view.View
import android.view.animation.Interpolator
import rx.Observableimport rx.Subscriber
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicIntegerclass
TranslateViewOperator(private val translationX: Float,                            
                                      private val translationY: Float,
                                      private val duration: Long,
                                      private val interpolator: Interpolator) : Observable.Operator<View, View> {    
 // Counts the number of animations in progress.    
 // Used for properly propagating onComplete() call to the subscriber.    
 private val numberOfRunningAnimations = AtomicInteger(0)    
 // Indicates whether this operator received the onComplete() call or not.   
 private val isOnCompleteCalled = AtomicBoolean(false)   
 override fun call(subscriber: Subscriber<in View>) = object : Subscriber<View>() {         
          override fun onError(e: Throwable?) {   
              // In case of onError(), just pass it down to the subscriber.            
              if (!subscriber.isUnsubscribed) {                
                   subscriber.onError(e)           
               }        
          }        
          override fun onNext(view: View) {           
             // Don't start animation if the subscriber has unsubscribed.           
             if (subscriber.isUnsubscribed) return            
             // Run the animation.        
             numberOfRunningAnimations.incrementAndGet() 
                      ViewCompat.animate(view)                    
                     .translationX(translationX)                               
                     .translationY(translationY)                    
                     .setDuration(duration)
                     .setInterpolator(interpolator)                    
                     .withEndAction {
                         numberOfRunningAnimations.decrementAndGet()
                         // Once the animation is done, check if the subscriber is still subscribed                        
                         // and pass the animated view to onNext().
                        if (!subscriber.isUnsubscribed) {  
                          subscriber.onNext(view)                            
                             // If we received the onComplete() event sometime while the animation was running,                            
                             // wait until all animations are done and then call onComplete() on the subscriber.                           
                             if (numberOfRunningAnimations.get() == 0 && isOnCompleteCalled.get()) { 
                               subscriber.onCompleted()                            
                             }                        
                         }                   
                    }       
         }        
           override fun onCompleted() {
               isOnCompleteCalled.set(true)           
               // Call onComplete() immediately if all animations are finished.            
               if (!subscriber.isUnsubscribed && numberOfRunningAnimations.get() == 0) {
                subscriber.onCompleted()           
               }       
         }    
    }
}

现在在ViewGroup中放置了一些圆(CircleView)和正方形(RectangleView),我们能够非常容易的创建一个方法来平移这些view。

AnimationViewGroup.kt

fun Observable<View>.translateView(translationX: Float, 
                                  translationY: Float,
                                  duration: Long, 
                                  interpolator: Interpolator): Observable<View> 
 = lift<View>(TranslateViewOperator(translationX, translationY, duration, interpolator))

我们将圆用list保存,声明两个变量分别用于保存左边的正方形和右边的正方形。

AnimationViewGroup.kt

fun init() {    
  rectangleLeft = RectangleView(context)    
  rectangleRight = RectangleView(context)    
  addView(rectangleLeft)    
  addView(rectangleRight)    
  // Add 10 circles.    
  for (i in 0..9) {        
    val cv = CircleView(context);        
    circleViews.add(cv)        
    addView(cv)    
  }
}
//onLayout and other code omitted..

让我们来编写一个播放动画的方法,通过timer Observable发射Observable每隔一段时间我们能够得到圆views。

AnimationViewGroup.kt

fun startAnimation() {    // First, unsubscribe from previous animations.    
    animationSubscription?.unsubscribe()    
    // Timer observable that will emit every half second.    val     
    timerObservable = Observable.interval(0, 500, TimeUnit.MILLISECONDS)    
    // Observable that will emit circle views from the list.    
    val viewsObservable = Observable.from(circleViews)            // As each circle view is emitted, stop animations on it.              
    .doOnNext { v -> ViewCompat.animate(v).cancel() }            // Just take those circles that are not already in the right rectangle.            
    .filter { v -> v.translationX < rectangleRight!!.left }    // First, zip the timer and circle views observables, so that we get one circle view every half a second.   
    animationSubscription = Observable.zip(viewsObservable, timerObservable) { view, time -> view }            // As each view comes in, translate it so that it ends up inside the right rectangle.            
    .translateView(rectangleRight!!.left.toFloat(), rectangleRight!!.top.toFloat(), ANIMATION_DURATION_MS, DecelerateInterpolator())            
    .subscribe()}

你可以进行无限可能的扩展,例如,通过移除timer你能够同时移动所有view,当动画完成的时候你也可以处理下游的每一个view。

自定义操作符是件非常酷的事情,实现也很简单,但是创建自定义操作符并不总是一件好的事情他能导致挫折和问题例如不当的背压处理

在实际的开发中,大多数时候我们需要一种稍微不同的方式来处理动画,通常我们需要组合不同的动画,先播放什么动画,然后播放什么的动画,最后播放什么动画。

初识Completable

Completable是在RxJava1.1.1版本引入的,那么到底什么是Completable。
以下是来自RxJava wiki上的解释:

我们可以认为Completable对象是Observable的简化版本,他仅仅发射onError和onCompleted这两个终端事件。他看上去像Observable.empty()的一个具体类但是又不像empty(),Completable是一个活动的类。

我们可以使用Completable来执行一个动画,当这个动画执行完成的时候调用onComplete(),同时,另外的动画和任意的其它操作也都可以被执行。

现在让我们使用Completable来替代操作符,我们将使用一个简化版的Obserable以便当动画完成的时候我们不必不断的处理这些view,仅仅只需要通知这些observers被请求的动画已经完成了。

让我们来创建另外一个实用性更强的例子,我们有一个填充了一些图标的toolbar,我们想要提供一个setMenuItems()方法来折叠所有的图标到toolbar的左边,缩放他们直到他们消失,从父布局中移除他们。增加一组新的icons添加到父布局,然后放大他们,最后展开他们。

我们将从Completable.CompletableOnSubscribe的实现类来创建Completable。


ExpandViewsOnSubscribe.kt

class ExpandViewsOnSubscribe(private val views:List<FloatingActionButton>, 
                            private val animationType:AnimationType,
                            private val duration: Long, 
                            private val interpolator: Interpolator,
                            private val paddingPx:Int): Completable.CompletableOnSubscribe {
    lateinit private var numberOfAnimationsToRun: AtomicInteger    enum class AnimationType {
        EXPAND_HORIZONTALLY, COLLAPSE_HORIZONTALLY, 
       EXPAND_VERTICALLY, COLLAPSE_VERTICALLY    
    }
    override fun call(subscriber: Completable.CompletableSubscriber?) {
        if (views.isEmpty()) {
            subscriber!!.onCompleted()
            return
            // We need to run as much as animations as there are views.
        }        
        numberOfAnimationsToRun = AtomicInteger(views.size)        
        // Assert all FABs are the same size, we could count each item size if we're making
        // an implementation that possibly expects different-sized items.
        val fabWidth = views[0].width
        val fabHeight = views[0].height
        val horizontalExpansion = animationType == AnimationType.EXPAND_HORIZONTALLY
        val verticalExpansion = animationType == AnimationType.EXPAND_VERTICALLY
        // Only if expanding horizontally, we'll move x-translate each of the FABs by index * width.
        val xTranslationFactor = if (horizontalExpansion) fabWidth else 0
        // Only if expanding vertically, we'll move y-translate each of the FABs by index * height.
        val yTranslationFactor = if (verticalExpansion) fabHeight else 0        
        // Same with padding.
        val paddingX = if (horizontalExpansion) paddingPx else 0
        val paddingY = if (verticalExpansion) paddingPx else 0
        for (i in views.indices) {
            views[i].setImageResource(R.drawable.right_arrow)
            ViewCompat.animate(views[i])
                    .translationX(i * (xTranslationFactor.toFloat() + paddingX))
                    .translationY(i * (yTranslationFactor.toFloat() + paddingY))
                    .setDuration(duration)                    
                    .setInterpolator(interpolator)
                    .withEndAction {
                        // Once all animations are done, call onCompleted().
                        if (numberOfAnimationsToRun.decrementAndGet() == 0) {
                            subscriber!!.onCompleted()
                        }
                    }
        }
    }
}

现在我们创建一个方法从Completable.CompletableOnSubscribe的实现类中返回Completable


AnimationViewGroup2.kt

fun expandMenuItemsHorizontally(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(ExpandViewsOnSubscribe(items, ExpandViewsOnSubscribe.AnimationType.EXPAND_HORIZONTALLY, 300L, AccelerateDecelerateInterpolator(), 32))

fun collapseMenuItemsHorizontally(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(ExpandViewsOnSubscribe(items, ExpandViewsOnSubscribe.AnimationType.COLLAPSE_HORIZONTALLY, 300L, AccelerateDecelerateInterpolator(), 32))

初始的时候我们添加了一些items到这个布局中现在我们可以添加如下的代码对他们进行测试。
AnimationViewGroup2.kt

fun startAnimation() {
    expandMenuItemsHorizontally(currentItems).subscribe()}
fun reverseAnimation() {
    collapseMenuItemsHorizontally(currentItems).subscribe()}

运行效果如下:

动画链

使用相同的方式,通过实现Completable.CompletableOnSubscribe我们可以实现缩放和旋转。以下是简化过的代码详细实现请查看源码:
AnimationViewGroup2.kt

fun expandMenuItemsHorizontally(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(ExpandViewsOnSubscribe(items, ExpandViewsOnSubscribe.AnimationType.EXPAND_HORIZONTALLY, 300L, AccelerateDecelerateInterpolator(), 32))
fun collapseMenuItemsHorizontally(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(ExpandViewsOnSubscribe(items, ExpandViewsOnSubscribe.AnimationType.COLLAPSE_HORIZONTALLY, 300L, AccelerateDecelerateInterpolator(), 32))
fun rotateMenuItemsBy90(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(RotateViewsOnSubscribe(items, RotateViewsOnSubscribe.AnimationType.ROTATE_TO_90, 300L, DecelerateInterpolator()));
fun rotateMenuItemsToOriginalPosition(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(RotateViewsOnSubscribe(items, RotateViewsOnSubscribe.AnimationType.ROTATE_TO_0, 300L, DecelerateInterpolator()))
fun scaleDownMenuItems(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(ScaleViewsOnSubscribe(items, ScaleViewsOnSubscribe.AnimationType.SCALE_DOWN, 400L, DecelerateInterpolator()))
fun scaleUpMenuItems(items: MutableList<FloatingActionButton>): Completable =
        Completable.create(ScaleViewsOnSubscribe(items, ScaleViewsOnSubscribe.AnimationType.SCALE_UP, 400L, DecelerateInterpolator()))
fun removeMenuItems(items: MutableList<FloatingActionButton>): Completable =
 Completable.fromAction {
    removeAllViews()
}
fun addItemsScaledDownAndRotated(items: MutableList<FloatingActionButton>): Completable =
 Completable.fromAction {
    this.currentItems = items
    for (item in items) {
        item.scaleX = 0.0f
        item.scaleY = 0.0f
        item.rotation = 90f
        item.setImageResource(R.drawable.square_72px)
        addView(item)    
   }
}

实现setMenuItems方法

fun setMenuItems(newItems: MutableList<FloatingActionButton>) {
    collapseMenuItemsHorizontally(currentItems)
            .andThen(rotateMenuItemsBy90(currentItems))
            .andThen(scaleDownMenuItems(currentItems))
            .andThen(removeMenuItems(currentItems))
            .andThen(addItemsScaledDownAndRotated(newItems))
            .andThen(scaleUpMenuItems(newItems))
            .andThen(rotateMenuItemsToOriginalPosition(newItems))
            .andThen(expandMenuItemsHorizontally(newItems))
            .subscribe()}

设置新的菜单项后的运行效果:

局限性

记住不能使用mergeWith()来执行这些组合的动画,因为这些动画作用在同一个view上,前一个动画的监听器会被后一个动画的监听器覆盖因此merge操作将永远不会完成因为它在等待这些动画的Completable
s执行完成。如果你执行的动画是作用在不同的view上你可以正常的使用mergeWith()方法,被创建的Completable将会一直等待直到这些动画调用onComplete()完成。
如果想在现一个view上执行多个动画,一种解决方案是实现OnSubscribe例如RotateAndScaleViewOnSubscribe就实现了旋转和缩放动画。

本文写到这儿就告一段落了,由于水平有限写得不好的地方欢迎大家指出。本文主要内容均参考于:https://pspdfkit.com/blog/2016/android-animations-powered-by-rx-java/

作者:dkdjfkdjfk 发表于2016/10/18 12:52:06 原文链接
阅读:19 评论:0 查看评论

没有比这更完整的sdcard工具类了

$
0
0
package com.lt.an20_utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.StatFs;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created by 风情万种冷哥哥 on 2016/10/15.
 */
public class SDCardUtils {

    //判断sd卡是否被挂载
    public static boolean isSDCardMounted(){
        return Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED);
    }
    /**
     * Created by 风情万种冷哥哥 on 2016.
     * 获取sdcard常用目录
     */
    public static String getSDCardBaseDir(){
        if (isSDCardMounted()){
            return Environment.getExternalStorageDirectory().getAbsolutePath();
        }
        return null;
    }
    //获取sdcard公有的目录的路径
    public static String getSDCardPublicDir(String type){
        if(isSDCardMounted()){
            return Environment.getExternalStoragePublicDirectory(type).toString();
        }
        return null;
    }
    //获取sdcard私有cache的目录的路径
    public static String getSDCardPrivateCacheDir(Context context){
        if (isSDCardMounted()){
            return context.getExternalCacheDir().getAbsolutePath();
        }
        return null;
    }
    //获取sdcard私有file目录的路径
    public static String getSDCardPrivateFilesDir(Context context,String type){
        if (isSDCardMounted()){
            return context.getExternalFilesDir(type).getAbsolutePath();
        }
        return null;
    }
    /**
     * Created by 风情万种冷哥哥 on 2016.
     * 获取sdcard空间的大小
     */
    //获取sdcard的完整空间大小 。返回MB
    public static long getSDCardSize(){
        if (isSDCardMounted()){
            StatFs fs = new StatFs(getSDCardBaseDir());
            int count = fs.getBlockCount();
            int size = fs.getBlockSize();//此处过时了但也没有更好的方法更新
            return count*size/1024/1024;
        }
        return 0;
    }

    //获取sdcard的剩余空间的大小
    public static long getSDCardFreeSize(){
        if (isSDCardMounted()){
            StatFs fs = new StatFs(getSDCardBaseDir());
            int count = fs.getFreeBlocks();
            int size = fs.getBlockSize();
            return count*size/1024/1024;
        }
        return 0;
    }
    //获取sdcard的可用空间的大小
    public static long getSDCardAvailableSize(){
        if (isSDCardMounted()){
            StatFs fs = new StatFs(getSDCardBaseDir());
            int count = fs.getAvailableBlocks();
            int size = fs.getBlockSize();
            return count*size/1024/1024;

        }
        return 0;
    }
    /**
     * Created by 风情万种冷哥哥 on 2016.
     * 往sdcard的公有目录下保存文件
     *
     *
     */
    public static boolean saveFileToSDCardPublicDir(byte[] data,String type,String fileName){
        BufferedOutputStream bos = null;
        if (isSDCardMounted()){
            File file = Environment.getExternalStoragePublicDirectory(type);
            try {
                bos = new BufferedOutputStream(new FileOutputStream(new File(file,fileName)));
                bos.write(data);
                bos.flush();
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (bos != null){
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
        return false;
    }
    //往sdcard的自定义目录下保存文件
    public static boolean saveFileToSDCardCustomDir(byte[] data,String dir,String fileName){
        BufferedOutputStream bos = null;
        if (isSDCardMounted()){
            File file = new File(getSDCardBaseDir()+File.separator+dir);
            if (!file.exists()){
                file.mkdirs();//递归创建自定义目录
                try {
                    bos = new BufferedOutputStream(new FileOutputStream(new File(file,fileName)));
                    bos.write(data);
                    bos.flush();
                    return true;
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    if (bos != null){
                        try {
                            bos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
            }
            }
        }
        return false;
    }
    //往sdcard的私有files目录下保存文件
    public static boolean saveFlieToSDCardPrivateFileDir(byte[] data,String type,String fileName,Context context){
        BufferedOutputStream bos = null;
        if (isSDCardMounted()){
            File file = context.getExternalFilesDir(type);
            try {
                bos = new BufferedOutputStream(new FileOutputStream(new File(file,fileName)));
                bos.write(data);
                bos.flush();
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    if(bos != null){
                        bos.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }
    //往sdcard的私有cache目录下保存文件
    public static boolean saveFileToSDCardPrivateCacheDir(byte[] data,String fileName,Context context){
        BufferedOutputStream bos = null;
        if (isSDCardMounted()){
            File file = context.getExternalCacheDir();
            try {
                bos = new BufferedOutputStream(new FileOutputStream(new File(file,fileName)));
                bos.write(data);
                bos.flush();
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (bos != null){
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }
    //保存bitmap图片到sdcard的私有目录
    public static boolean saveBitmapToSDCardCacheDir(Bitmap bitmap,String fileName,Context context){
        if (isSDCardMounted()){
            BufferedOutputStream bos = null;
            //获取私有的cache的缓存目录
            File file = context.getExternalCacheDir();
            try {
                bos = new BufferedOutputStream(new FileOutputStream(new File(file,fileName)));
                if (fileName != null && (fileName.contains(".png") || fileName.contains(".PNG"))){
                    bitmap.compress(Bitmap.CompressFormat.PNG,100,bos);
                }else {
                    bitmap.compress(Bitmap.CompressFormat.JPEG,100,bos);
                }
                bos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (bos != null){
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
            return true;
        }else {
            return false;
        }
    }
    //将图片保存到sdcard公有目录
    public static boolean saveBitmapToSDCardPublicDir(Bitmap bm,String type,String fileName){
        if (isSDCardMounted()){
            String filepath = getSDCardPublicDir(type)+File.separator+fileName;
            BufferedOutputStream bos = null;
            try {
                bos = new BufferedOutputStream(new FileOutputStream(new File(filepath)));
                bm.compress(Bitmap.CompressFormat.PNG,100,bos);
                bos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (bos != null){
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return true;
            }
        }
        return false;
    }
    // 从SD卡获取文件
    public static byte[] loadFileFromSDCard(String fileDir) {
        BufferedInputStream bis = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        try {
            bis = new BufferedInputStream(
                    new FileInputStream(new File(fileDir)));
            byte[] buffer = new byte[8 * 1024];
            int c = 0;
            while ((c = bis.read(buffer)) != -1) {
                baos.write(buffer, 0, c);
                baos.flush();
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.close();
                }
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    // 从SDCard中寻找指定目录下的文件,返回Bitmap
    public Bitmap loadBitmapFromSDCard(String filePath) {
        byte[] data = loadFileFromSDCard(filePath);
        if (data != null) {
            Bitmap bm = BitmapFactory.decodeByteArray(data, 0, data.length);
            if (bm != null) {
                return bm;
            }
        }
        return null;
    }


    public static boolean isFileExist(String filePath) {
        File file = new File(filePath);
        return file.isFile();
    }

    // 从sdcard中删除文件
    public static boolean removeFileFromSDCard(String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            try {
                file.delete();
                return true;
            } catch (Exception e) {
                return false;
            }
        } else {
            return false;
        }
    }

/**
 * Created by 风情万种冷哥哥 on 2016.
 * 输入流转字节数组
 */
    public static byte[] streamToByteArray(InputStream is) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int c = 0;
        byte[] buffer = new byte[8 * 1024];
        try {
            while ((c = is.read(buffer)) != -1) {
                baos.write(buffer, 0, c);
                baos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return baos.toByteArray();
    }

   /**
    * Created by 风情万种冷哥哥 on 2016.
    *      * @return
    */
    public static String streamToString(InputStream is, String charsetName) {
        BufferedInputStream bis = new BufferedInputStream(is);
        StringBuilder sb = new StringBuilder();
        int c = 0;
        byte[] buffer = new byte[8 * 1024];
        try {
            while ((c = bis.read(buffer)) != -1) {
                sb.append(new String(buffer, charsetName));
            }
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

  /**
   * Created by 风情万种冷哥哥 on 2016.
   * 字符串转输入流
   */
    public static InputStream stringToInputStream(String str) {
        InputStream is = null;
        try {
            is = new ByteArrayInputStream(str.getBytes("UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return is;
    }
}




作者:First_CooMan 发表于2016/10/18 12:58:49 原文链接
阅读:28 评论:0 查看评论

类似咻一咻,水波纹实现

$
0
0

一、效果
这里写图片描述
点击开始:
这里写图片描述
点击停止:
这里写图片描述

二、在MainActivity中

import android.graphics.Paint;
import android.os.Bundle;
import android.support.v4.view.animation.LinearOutSlowInInterpolator;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //    private WaveView mWaveView1;
    private WaveView mWaveView2;
    private Button mStart;
    private Button mStop;
    private CircleImageView circleImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mStart = (Button) findViewById(R.id.start);
        mStop = (Button) findViewById(R.id.stop);
        circleImageView = (CircleImageView) findViewById(R.id.civ_info);
        circleImageView.setImageResource(R.mipmap.icon_ku);


        mWaveView2 = (WaveView) findViewById(R.id.wave_view2);
        mWaveView2.setDuration(5000);
        mWaveView2.setStyle(Paint.Style.FILL_AND_STROKE);
        mWaveView2.setColor(getResources().getColor(R.color.green));
        mWaveView2.setInterpolator(new LinearOutSlowInInterpolator());

        mStart.setOnClickListener(this);
        mStop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.start:
                mWaveView2.setColor(getResources().getColor(R.color.green));
                mWaveView2.start();
                circleImageView.setImageResource(R.mipmap.icon_xiao);
                break;
            case R.id.stop:
                mWaveView2.setColor(getResources().getColor(R.color.red));
                mWaveView2.stop();
                circleImageView.setImageResource(R.mipmap.icon_ku);
                break;
        }
    }
}

三、在activity_main中

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

    <Button
        android:id="@+id/start"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始" />

    <Button
        android:id="@+id/stop"
        android:layout_toRightOf="@id/start"
        android:layout_marginTop="10dp"
        android:layout_marginLeft="10dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="停止" />

    <cn.hnshangyu.xiuyixiu.WaveView
        android:id="@+id/wave_view2"
        android:layout_width="350dp"
        android:layout_height="350dp"
        android:layout_centerInParent="true"
        android:layout_marginTop="10dp"

        android:textSize="24dp" />

    <cn.hnshangyu.xiuyixiu.CircleImageView
        android:id="@+id/civ_info"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:src="@color/green"
        android:text="@string/app_name"
        android:textColor="@color/colorPrimary" />

四、在WaveView中:

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 水波纹特效
 */
public class WaveView extends View {
    private float mInitialRadius=100;   // 初始波纹半径
    private float mMaxRadiusRate = 0.85f;   // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate;
    private float mMaxRadius;   // 最大波纹半径
    private long mDuration = 2000; // 一个波纹从创建到消失的持续时间
    private int mSpeed = 2000;   // 波纹的创建速度,每2000ms创建一个
    private Interpolator mInterpolator = new LinearInterpolator();

    private List<Circle> mCircleList = new ArrayList<Circle>();
    private boolean mIsRunning;

    private boolean mMaxRadiusSet;

    private Paint mPaint;
    private long mLastCreateTime;

    private Runnable mCreateCircle = new Runnable() {
        @Override
        public void run() {
            if (mIsRunning) {
                newCircle();
                postDelayed(mCreateCircle, mSpeed);
            }
        }
    };

    public WaveView(Context context) {
        this(context, null);
    }

    public WaveView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        setStyle(Paint.Style.FILL);
    }

    public void setStyle(Paint.Style style) {
        mPaint.setStyle(style);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (!mMaxRadiusSet) {
            mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f;
        }
    }

    public void setMaxRadiusRate(float maxRadiusRate) {
        this.mMaxRadiusRate = maxRadiusRate;
    }

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

    /**
     * 开始
     */
    public void start() {
        if (!mIsRunning) {
            mIsRunning = true;
            mCreateCircle.run();
        }
    }

    /**
     * 停止
     */
    public void stop() {
        mIsRunning = false;
    }

    protected void onDraw(Canvas canvas) {
        Iterator<Circle> iterator = mCircleList.iterator();
        while (iterator.hasNext()) {
            Circle circle = iterator.next();
            if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {
                mPaint.setAlpha(circle.getAlpha());
                canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);
            } else {
                iterator.remove();
            }
        }
        if (mCircleList.size() > 0) {
            postInvalidateDelayed(10);
        }
    }

    public void setInitialRadius(float radius) {
        mInitialRadius = radius;
    }

    public void setDuration(long duration) {
        this.mDuration = duration;
    }

    public void setMaxRadius(float maxRadius) {
        this.mMaxRadius = maxRadius;
        mMaxRadiusSet = true;
    }

    public void setSpeed(int speed) {
        mSpeed = speed;
    }

    private void newCircle() {
        long currentTime = System.currentTimeMillis();
        if (currentTime - mLastCreateTime < mSpeed) {
            return;
        }
        Circle circle = new Circle();
        mCircleList.add(circle);
        invalidate();
        mLastCreateTime = currentTime;
    }

    private class Circle {
        private long mCreateTime;

        public Circle() {
            this.mCreateTime = System.currentTimeMillis();
        }

        public int getAlpha() {
            float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
            return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);
        }

        public float getCurrentRadius() {
            float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;
            return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);
        }
    }

    public void setInterpolator(Interpolator interpolator) {
        mInterpolator = interpolator;
        if (mInterpolator == null) {
            mInterpolator = new LinearInterpolator();
        }
    }
}

五、在CircleImageView中:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.util.AttributeSet;
import android.widget.ImageView;


public class CircleImageView extends ImageView {

    private static final ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;

    private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
    private static final int COLORDRAWABLE_DIMENSION = 2;

    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK;
    private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT;
    private static final boolean DEFAULT_BORDER_OVERLAY = false;

    private final RectF mDrawableRect = new RectF();
    private final RectF mBorderRect = new RectF();

    private final Matrix mShaderMatrix = new Matrix();
    private final Paint mBitmapPaint = new Paint();
    private final Paint mBorderPaint = new Paint();
    private final Paint mFillPaint = new Paint();

    private int mBorderColor = DEFAULT_BORDER_COLOR;
    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    private int mFillColor = DEFAULT_FILL_COLOR;

    private Bitmap mBitmap;
    private BitmapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;

    private float mDrawableRadius;
    private float mBorderRadius;

    private ColorFilter mColorFilter;

    private boolean mReady;
    private boolean mSetupPending;
    private boolean mBorderOverlay;
    private boolean mDisableCircularTransformation;

    public CircleImageView(Context context) {
        super(context);

        init();
    }

    public CircleImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0);

        mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_civ_border_width, DEFAULT_BORDER_WIDTH);
        mBorderColor = a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR);
        mBorderOverlay = a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY);
        mFillColor = a.getColor(R.styleable.CircleImageView_civ_fill_color, DEFAULT_FILL_COLOR);

        a.recycle();

        init();
    }

    private void init() {
        super.setScaleType(SCALE_TYPE);
        mReady = true;

        if (mSetupPending) {
            setup();
            mSetupPending = false;
        }
    }

    @Override
    public ScaleType getScaleType() {
        return SCALE_TYPE;
    }

    @Override
    public void setScaleType(ScaleType scaleType) {
        if (scaleType != SCALE_TYPE) {
            throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
        }
    }

    @Override
    public void setAdjustViewBounds(boolean adjustViewBounds) {
        if (adjustViewBounds) {
            throw new IllegalArgumentException("adjustViewBounds not supported.");
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mDisableCircularTransformation) {
            super.onDraw(canvas);
            return;
        }

        if (mBitmap == null) {
            return;
        }

        if (mFillColor != Color.TRANSPARENT) {
            canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
        }
        canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
        if (mBorderWidth > 0) {
            canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        setup();
    }

    @Override
    public void setPadding(int left, int top, int right, int bottom) {
        super.setPadding(left, top, right, bottom);
        setup();
    }

    @Override
    public void setPaddingRelative(int start, int top, int end, int bottom) {
        super.setPaddingRelative(start, top, end, bottom);
        setup();
    }

    public int getBorderColor() {
        return mBorderColor;
    }

    public void setBorderColor(@ColorInt int borderColor) {
        if (borderColor == mBorderColor) {
            return;
        }

        mBorderColor = borderColor;
        mBorderPaint.setColor(mBorderColor);
        invalidate();
    }

    /**
     * @deprecated Use {@link #setBorderColor(int)} instead
     */
    @Deprecated
    public void setBorderColorResource(@ColorRes int borderColorRes) {
        setBorderColor(getContext().getResources().getColor(borderColorRes));
    }

    /**
     * Return the color drawn behind the circle-shaped drawable.
     *
     * @return The color drawn behind the drawable
     * @deprecated Fill color support is going to be removed in the future
     */
    @Deprecated
    public int getFillColor() {
        return mFillColor;
    }

    /**
     * Set a color to be drawn behind the circle-shaped drawable. Note that
     * this has no effect if the drawable is opaque or no drawable is set.
     *
     * @param fillColor The color to be drawn behind the drawable
     * @deprecated Fill color support is going to be removed in the future
     */
    @Deprecated
    public void setFillColor(@ColorInt int fillColor) {
        if (fillColor == mFillColor) {
            return;
        }

        mFillColor = fillColor;
        mFillPaint.setColor(fillColor);
        invalidate();
    }

    /**
     * Set a color to be drawn behind the circle-shaped drawable. Note that
     * this has no effect if the drawable is opaque or no drawable is set.
     *
     * @param fillColorRes The color resource to be resolved to a color and
     *                     drawn behind the drawable
     * @deprecated Fill color support is going to be removed in the future
     */
    @Deprecated
    public void setFillColorResource(@ColorRes int fillColorRes) {
        setFillColor(getContext().getResources().getColor(fillColorRes));
    }

    public int getBorderWidth() {
        return mBorderWidth;
    }

    public void setBorderWidth(int borderWidth) {
        if (borderWidth == mBorderWidth) {
            return;
        }

        mBorderWidth = borderWidth;
        setup();
    }

    public boolean isBorderOverlay() {
        return mBorderOverlay;
    }

    public void setBorderOverlay(boolean borderOverlay) {
        if (borderOverlay == mBorderOverlay) {
            return;
        }

        mBorderOverlay = borderOverlay;
        setup();
    }

    public boolean isDisableCircularTransformation() {
        return mDisableCircularTransformation;
    }

    public void setDisableCircularTransformation(boolean disableCircularTransformation) {
        if (mDisableCircularTransformation == disableCircularTransformation) {
            return;
        }

        mDisableCircularTransformation = disableCircularTransformation;
        initializeBitmap();
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        super.setImageBitmap(bm);
        initializeBitmap();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        super.setImageDrawable(drawable);
        initializeBitmap();
    }

    @Override
    public void setImageResource(@DrawableRes int resId) {
        super.setImageResource(resId);
        initializeBitmap();
    }

    @Override
    public void setImageURI(Uri uri) {
        super.setImageURI(uri);
        initializeBitmap();
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        if (cf == mColorFilter) {
            return;
        }

        mColorFilter = cf;
        applyColorFilter();
        invalidate();
    }

    @Override
    public ColorFilter getColorFilter() {
        return mColorFilter;
    }

    private void applyColorFilter() {
        if (mBitmapPaint != null) {
            mBitmapPaint.setColorFilter(mColorFilter);
        }
    }

    private Bitmap getBitmapFromDrawable(Drawable drawable) {
        if (drawable == null) {
            return null;
        }

        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        try {
            Bitmap bitmap;

            if (drawable instanceof ColorDrawable) {
                bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
            } else {
                bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
            }

            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return bitmap;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private void initializeBitmap() {
        if (mDisableCircularTransformation) {
            mBitmap = null;
        } else {
            mBitmap = getBitmapFromDrawable(getDrawable());
        }
        setup();
    }

    private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }

        if (getWidth() == 0 && getHeight() == 0) {
            return;
        }

        if (mBitmap == null) {
            invalidate();
            return;
        }

        mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader);

        mBorderPaint.setStyle(Paint.Style.STROKE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(mBorderColor);
        mBorderPaint.setStrokeWidth(mBorderWidth);

        mFillPaint.setStyle(Paint.Style.FILL);
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(mFillColor);

        mBitmapHeight = mBitmap.getHeight();
        mBitmapWidth = mBitmap.getWidth();

        mBorderRect.set(calculateBounds());
        mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) / 2.0f, (mBorderRect.width() - mBorderWidth) / 2.0f);

        mDrawableRect.set(mBorderRect);
        if (!mBorderOverlay && mBorderWidth > 0) {
            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f);
        }
        mDrawableRadius = Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f);

        applyColorFilter();
        updateShaderMatrix();
        invalidate();
    }

    private RectF calculateBounds() {
        int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();

        int sideLength = Math.min(availableWidth, availableHeight);

        float left = getPaddingLeft() + (availableWidth - sideLength) / 2f;
        float top = getPaddingTop() + (availableHeight - sideLength) / 2f;

        return new RectF(left, top, left + sideLength, top + sideLength);
    }

    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;

        mShaderMatrix.set(null);

        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / (float) mBitmapHeight;
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f;
        } else {
            scale = mDrawableRect.width() / (float) mBitmapWidth;
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f;
        }

        mShaderMatrix.setScale(scale, scale);
        mShaderMatrix.postTranslate((int) (dx + 0.5f) + mDrawableRect.left, (int) (dy + 0.5f) + mDrawableRect.top);

        mBitmapShader.setLocalMatrix(mShaderMatrix);
    }

}

六、在attrs中

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleImageView">
        <attr name="civ_border_width" format="dimension" />
        <attr name="civ_border_color" format="color" />
        <attr name="civ_border_overlay" format="boolean" />
        <attr name="civ_fill_color" format="color" />
    </declare-styleable>
    <declare-styleable name="RangeSeekbar">
        <attr name="seekbarHeight" format="dimension" />
        <attr name="textSize" format="dimension" />
        <attr name="spaceBetween" format="dimension" />

        <attr name="leftCursorBackground" format="reference" />
        <attr name="rightCursorBackground" format="reference" />
        <attr name="markTextArray" format="reference" />

        <attr name="textColorNormal" format="color" />
        <attr name="textColorSelected" format="color" />
        <attr name="seekbarColorNormal" format="color" />
        <attr name="seekbarColorSelected" format="color" />

        <attr name="autoMoveDuration" format="integer" />

    </declare-styleable>
</resources>

最后感谢 hackware 提供的思路

作者:huangxiaoguo1 发表于2016/10/19 11:05:06 原文链接
阅读:48 评论:0 查看评论

颜色配表

$
0
0

网上找的一些颜色值
android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

android颜色对应的xml配置值,颜色表

Java代码 复制代码 收藏代码
  1. <?xml version="1.0" encoding="utf-8" ?>   
  2. <resources>   
  3. <color name="white">#FFFFFF</color><!--白色 -->   
  4. <color name="ivory">#FFFFF0</color><!--象牙色 -->   
  5. <color name="lightyellow">#FFFFE0</color><!--亮黄色 -->   
  6. <color name="yellow">#FFFF00</color><!--黄色 -->   
  7. <color name="snow">#FFFAFA</color><!--雪白色 -->   
  8. <color name="floralwhite">#FFFAF0</color><!--花白色 -->   
  9. <color name="lemonchiffon">#FFFACD</color><!--柠檬绸色 -->   
  10. <color name="cornsilk">#FFF8DC</color><!--米绸色 -->   
  11. <color name="seashell">#FFF5EE</color><!--海贝色 -->   
  12. <color name="lavenderblush">#FFF0F5</color><!--淡紫红 -->   
  13. <color name="papayawhip">#FFEFD5</color><!--番木色 -->   
  14. <color name="blanchedalmond">#FFEBCD</color><!--白杏色 -->   
  15. <color name="mistyrose">#FFE4E1</color><!--浅玫瑰色 -->   
  16. <color name="bisque">#FFE4C4</color><!--桔黄色 -->   
  17. <color name="moccasin">#FFE4B5</color><!--鹿皮色 -->   
  18. <color name="navajowhite">#FFDEAD</color><!--纳瓦白 -->   
  19. <color name="peachpuff">#FFDAB9</color><!--桃色 -->   
  20. <color name="gold">#FFD700</color><!--金色 -->   
  21. <color name="pink">#FFC0CB</color><!--粉红色 -->   
  22. <color name="lightpink">#FFB6C1</color><!--亮粉红色 -->   
  23. <color name="orange">#FFA500</color><!--橙色 -->   
  24. <color name="lightsalmon">#FFA07A</color><!--亮肉色 -->   
  25. <color name="darkorange">#FF8C00</color><!--暗桔黄色 -->   
  26. <color name="coral">#FF7F50</color><!--珊瑚色 -->   
  27. <color name="hotpink">#FF69B4</color><!--热粉红色 -->   
  28. <color name="tomato">#FF6347</color><!--西红柿色 -->   
  29. <color name="orangered">#FF4500</color><!--红橙色 -->   
  30. <color name="deeppink">#FF1493</color><!--深粉红色 -->   
  31. <color name="fuchsia">#FF00FF</color><!--紫红色 -->   
  32. <color name="magenta">#FF00FF</color><!--红紫色 -->   
  33. <color name="red">#FF0000</color><!--红色 -->   
  34. <color name="oldlace">#FDF5E6</color><!--老花色 -->   
  35. <color name="lightgoldenrodyellow">#FAFAD2</color><!--亮金黄色 -->   
  36. <color name="linen">#FAF0E6</color><!--亚麻色 -->   
  37. <color name="antiquewhite">#FAEBD7</color><!--古董白 -->   
  38. <color name="salmon">#FA8072</color><!--鲜肉色 -->   
  39. <color name="ghostwhite">#F8F8FF</color><!--幽灵白 -->   
  40. <color name="mintcream">#F5FFFA</color><!--薄荷色 -->   
  41. <color name="whitesmoke">#F5F5F5</color><!--烟白色 -->   
  42. <color name="beige">#F5F5DC</color><!--米色 -->   
  43. <color name="wheat">#F5DEB3</color><!--浅黄色 -->   
  44. <color name="sandybrown">#F4A460</color><!--沙褐色 -->   
  45. <color name="azure">#F0FFFF</color><!--天蓝色 -->   
  46. <color name="honeydew">#F0FFF0</color><!--蜜色 -->   
  47. <color name="aliceblue">#F0F8FF</color><!--艾利斯兰 -->   
  48. <color name="khaki">#F0E68C</color><!--黄褐色 -->   
  49. <color name="lightcoral">#F08080</color><!--亮珊瑚色 -->   
  50. <color name="palegoldenrod">#EEE8AA</color><!--苍麒麟色 -->   
  51. <color name="violet">#EE82EE</color><!--紫罗兰色 -->   
  52. <color name="darksalmon">#E9967A</color><!--暗肉色 -->   
  53. <color name="lavender">#E6E6FA</color><!--淡紫色 -->   
  54. <color name="lightcyan">#E0FFFF</color><!--亮青色 -->   
  55. <color name="burlywood">#DEB887</color><!--实木色 -->   
  56. <color name="plum">#DDA0DD</color><!--洋李色 -->   
  57. <color name="gainsboro">#DCDCDC</color><!--淡灰色 -->   
  58. <color name="crimson">#DC143C</color><!--暗深红色 -->   
  59. <color name="palevioletred">#DB7093</color><!--苍紫罗兰色 -->   
  60. <color name="goldenrod">#DAA520</color><!--金麒麟色 -->   
  61. <color name="orchid">#DA70D6</color><!--淡紫色 -->   
  62. <color name="thistle">#D8BFD8</color><!--蓟色 -->   
  63. <color name="lightgray">#D3D3D3</color><!--亮灰色 -->   
  64. <color name="lightgrey">#D3D3D3</color><!--亮灰色 -->   
  65. <color name="tan">#D2B48C</color><!--茶色 -->   
  66. <color name="chocolate">#D2691E</color><!--巧可力色 -->   
  67. <color name="peru">#CD853F</color><!--秘鲁色 -->   
  68. <color name="indianred">#CD5C5C</color><!--印第安红 -->   
  69. <color name="mediumvioletred">#C71585</color><!--中紫罗兰色 -->   
  70. <color name="silver">#C0C0C0</color><!--银色 -->   
  71. <color name="darkkhaki">#BDB76B</color><!--暗黄褐色 -->   
  72. <color name="rosybrown">#BC8F8F</color><!--褐玫瑰红 -->   
  73. <color name="mediumorchid">#BA55D3</color><!--中粉紫色 -->   
  74. <color name="darkgoldenrod">#B8860B</color><!--暗金黄色 -->   
  75. <color name="firebrick">#B22222</color><!--火砖色 -->   
  76. <color name="powderblue">#B0E0E6</color><!--粉蓝色 -->   
  77. <color name="lightsteelblue">#B0C4DE</color><!--亮钢兰色 -->   
  78. <color name="paleturquoise">#AFEEEE</color><!--苍宝石绿 -->   
  79. <color name="greenyellow">#ADFF2F</color><!--黄绿色 -->   
  80. <color name="lightblue">#ADD8E6</color><!--亮蓝色 -->   
  81. <color name="darkgray">#A9A9A9</color><!--暗灰色 -->   
  82. <color name="darkgrey">#A9A9A9</color><!--暗灰色 -->   
  83. <color name="brown">#A52A2A</color><!--褐色 -->   
  84. <color name="sienna">#A0522D</color><!--赭色 -->   
  85. <color name="darkorchid">#9932CC</color><!--暗紫色 -->   
  86. <color name="palegreen">#98FB98</color><!--苍绿色 -->   
  87. <color name="darkviolet">#9400D3</color><!--暗紫罗兰色 -->   
  88. <color name="mediumpurple">#9370DB</color><!--中紫色 -->   
  89. <color name="lightgreen">#90EE90</color><!--亮绿色 -->   
  90. <color name="darkseagreen">#8FBC8F</color><!--暗海兰色 -->   
  91. <color name="saddlebrown">#8B4513</color><!--重褐色 -->   
  92. <color name="darkmagenta">#8B008B</color><!--暗洋红 -->   
  93. <color name="darkred">#8B0000</color><!--暗红色 -->   
  94. <color name="blueviolet">#8A2BE2</color><!--紫罗兰蓝色 -->   
  95. <color name="lightskyblue">#87CEFA</color><!--亮天蓝色 -->   
  96. <color name="skyblue">#87CEEB</color><!--天蓝色 -->   
  97. <color name="gray">#808080</color><!--灰色 -->   
  98. <color name="grey">#808080</color><!--灰色 -->   
  99. <color name="olive">#808000</color><!--橄榄色 -->   
  100. <color name="purple">#800080</color><!--紫色 -->   
  101. <color name="maroon">#800000</color><!--粟色 -->   
  102. <color name="aquamarine">#7FFFD4</color><!--碧绿色 -->   
  103. <color name="chartreuse">#7FFF00</color><!--黄绿色 -->   
  104. <color name="lawngreen">#7CFC00</color><!--草绿色 -->   
  105. <color name="mediumslateblue">#7B68EE</color><!--中暗蓝色 -->   
  106. <color name="lightslategray">#778899</color><!--亮蓝灰 -->   
  107. <color name="lightslategrey">#778899</color><!--亮蓝灰 -->   
  108. <color name="slategray">#708090</color><!--灰石色 -->   
  109. <color name="slategrey">#708090</color><!--灰石色 -->   
  110. <color name="olivedrab">#6B8E23</color><!--深绿褐色 -->   
  111. <color name="slateblue">#6A5ACD</color><!--石蓝色 -->   
  112. <color name="dimgray">#696969</color><!--暗灰色 -->   
  113. <color name="dimgrey">#696969</color><!--暗灰色 -->   
  114. <color name="mediumaquamarine">#66CDAA</color><!--中绿色 -->   
  115. <color name="cornflowerblue">#6495ED</color><!--菊兰色 -->   
  116. <color name="cadetblue">#5F9EA0</color><!--军兰色 -->   
  117. <color name="darkolivegreen">#556B2F</color><!--暗橄榄绿 -->   
  118. <color name="indigo">#4B0082</color><!--靛青色 -->   
  119. <color name="mediumturquoise">#48D1CC</color><!--中绿宝石 -->   
  120. <color name="darkslateblue">#483D8B</color><!--暗灰蓝色 -->   
  121. <color name="steelblue">#4682B4</color><!--钢兰色 -->   
  122. <color name="royalblue">#4169E1</color><!--皇家蓝 -->   
  123. <color name="turquoise">#40E0D0</color><!--青绿色 -->   
  124. <color name="mediumseagreen">#3CB371</color><!--中海蓝 -->   
  125. <color name="limegreen">#32CD32</color><!--橙绿色 -->   
  126. <color name="darkslategray">#2F4F4F</color><!--暗瓦灰色 -->   
  127. <color name="darkslategrey">#2F4F4F</color><!--暗瓦灰色 -->   
  128. <color name="seagreen">#2E8B57</color><!--海绿色 -->   
  129. <color name="forestgreen">#228B22</color><!--森林绿 -->   
  130. <color name="lightseagreen">#20B2AA</color><!--亮海蓝色 -->   
  131. <color name="dodgerblue">#1E90FF</color><!--闪兰色 -->   
  132. <color name="midnightblue">#191970</color><!--中灰兰色 -->   
  133. <color name="aqua">#00FFFF</color><!--浅绿色 -->   
  134. <color name="cyan">#00FFFF</color><!--青色 -->   
  135. <color name="springgreen">#00FF7F</color><!--春绿色 -->   
  136. <color name="lime">#00FF00</color><!--酸橙色 -->   
  137. <color name="mediumspringgreen">#00FA9A</color><!--中春绿色 -->   
  138. <color name="darkturquoise">#00CED1</color><!--暗宝石绿 -->   
  139. <color name="deepskyblue">#00BFFF</color><!--深天蓝色 -->   
  140. <color name="darkcyan">#008B8B</color><!--暗青色 -->   
  141. <color name="teal">#008080</color><!--水鸭色 -->   
  142. <color name="green">#008000</color><!--绿色 -->   
  143. <color name="darkgreen">#006400</color><!--暗绿色 -->   
  144. <color name="blue">#0000FF</color><!--蓝色 -->   
  145. <color name="mediumblue">#0000CD</color><!--中兰色 -->   
  146. <color name="darkblue">#00008B</color><!--暗蓝色 -->   
  147. <color name="navy">#000080</color><!--海军色 -->   
  148. <color name="black">#000000</color><!--黑色 -->   
  149. </resources>  
作者:renshen0123 发表于2016/10/19 11:33:06 原文链接
阅读:39 评论:1 查看评论

iOS开发------获取系统联系人(Contacts篇)

$
0
0

Contacts.framework是Apple在 iOS9.0 替代AddressBook.framework的框架,至于AddressBook是做什么的框架,楼主默认看到博文的开发者是知道的 O(∩_∩)O。

如果想了解AddressBook的使用欢迎查看一下楼主之前关于AddressBook的博文,本篇不做过多的缀余:
iOS开发——获取系统联系人(AddressBook篇)
iOS开发——操作通讯录(AddressBook篇)&通讯录UI(AddressBookUI篇)

每次iOS发布新的版本(甚至每年的WWDC大会举行完毕)很多敏锐的开发者都准备或者对新版本特性进行适配。当然这些大神肯定会在iOS9发布后在第一时间对通讯录功能进行适配,一些稍微不太敏锐的开发者鉴于AddressBook在iOS9下初次提醒以及讨厌适配的繁琐,也就不以为然。

但随着iOS10的发布,那么适配相关框架就显得格外重要(不是说AddressBook不能使用了,但为了项目的健壮性以及良好的体验性,还是非常建议第一时间适配的。当然,这句话不仅限于Contacts部分)。

如果大家的项目还需要适配iOS8(当然,大多数公司肯定是也不会抛弃iOS7的用户),那么使用AddressBook是必然的;但如果在iOS9+的系统上,楼主还是非常建议使用最新的Contacts.framework框架的.

个人推荐的主要是下面两点原因(来源于楼主查看官方文档,编写Demo以及使用instruments的体会):

  1. AddressBook与其他相关废弃框架相似一样 (ex:ALAsset-图片库),语言风格更接近于C语言(当然也可以说就是C语言),不在ARC管理之下(对于习惯使用ARC下的开发者算是不小的挑战),使用不太便利并容易造成内存泄露。

  2. 新的框架无论在查看开发文档、使用、读取速度还是灵活性都远好于废弃框架,内存泄露易于查找以及补漏。

这里还是要分享一下源码,楼主整合AddressBook.framework以及Contacts.framework的DEMO

预览图

左边为AddressBook框架进行的演示,右边为Contact框架进行的演示.
根据不同的版本进行自动适配,如果是iOS9,自动使用Contact.framework.

 


权限描述

在iOS10上由于权限有很多的坑,本博文的内容需要使用通讯录权限.
那么不要忘记在项目的info.plist文件中加入如下描述:Privacy - Contacts Usage Description,描述字符串:RITL want to use your Contacts(这个随意),尽可能的写点东西吧,听说如果不写上线可能会被Apple拒绝..

获取权限-CNContactStore

负责获得权限、请求权限以及执行操作请求的类就是CNContactStore,具体Demo中的代码如下:

/**
 检测权限并作响应的操作
 */
- (void)__checkAuthorizationStatus
{
    //这里有一个枚举类:CNEntityType,不过没关系,只有一个值:CNEntityTypeContacts
    switch ([CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts])
    {
            //存在权限
        case CNAuthorizationStatusAuthorized:
            //获取通讯录
            [self __obtainContacts:self.completeBlock];
            break;

            //权限未知
        case CNAuthorizationStatusNotDetermined:
            //请求权限
            [self __requestAuthorizationStatus];break;

            //如果没有权限
        case CNAuthorizationStatusRestricted:
        case CNAuthorizationStatusDenied://需要提示
            self.defendBlock();break;
    }
}


请求联系人列表-CNContactStore

这里有几种比较常用的思路

1.使用自带的枚举方法一次性获得所有的属性

// 使用枚举方法对所有的联系人对象(CNContact)进行列出,该方法是同步的
- (BOOL)enumerateContactsWithFetchRequest:(CNContactFetchRequest *)fetchRequest
                                    error:(NSError *__nullable *__nullable)error
                               usingBlock:(void (^)(CNContact *contact, BOOL *stop))block;

2.先获取所有联系人的identifier,再根据identifier读取联系人信息(Demo中使用的该思路)

// 通过identifer获得一个唯一的CNContact
- (nullable CNContact *)unifiedContactWithIdentifier:(NSString *)identifier
                                         keysToFetch:(NSArray<id<CNKeyDescriptor>> *)keys
                                               error:(NSError *__nullable *__nullable)error;


遍历请求类-CNContactFetchRequest

感觉这里介绍一下CNContactFetchRequest类还是有必要的,毕竟当初在这里也是浪费了点时间,它是一个遍历请求的类,我们可以通过初始化该类的实例对象,告诉contactStore我们需要遍历contact的某些属性:

//实例化CNContactFetchRequest对象,通过一个遍历键的描述数组
- (instancetype)initWithKeysToFetch:(NSArray <id<CNKeyDescriptor>>*)keysToFetch NS_DESIGNATED_INITIALIZER;


键值描述协议-CNKeyDescriptor

如果我们单纯的进入开发文档,我们会发现他是一个空协议,刚开始看到这里的时候楼主表示很蒙B

//没有任何的required和optional方法
@protocol CNKeyDescriptor <NSObject, NSSecureCoding, NSCopying>
@end

但很快就发现了下面这个Category

// //Allows contact property keys to be used with keysToFetch.
// 允许contact的属性键作为遍历的键
@interface NSString (Contacts) <CNKeyDescriptor>
@end

如果还是有点疑惑,那么相信看到下面就不会再有困惑了呢。没错,可以直接将下列字符串当成CNKeyDescriptor对象写入数组

//标识符
CONTACTS_EXTERN NSString * const CNContactIdentifierKey                      NS_AVAILABLE(10_11, 9_0);
//姓名前缀
CONTACTS_EXTERN NSString * const CNContactNamePrefixKey                      NS_AVAILABLE(10_11, 9_0);
//姓名
CONTACTS_EXTERN NSString * const CNContactGivenNameKey                       NS_AVAILABLE(10_11, 9_0);
//中间名
CONTACTS_EXTERN NSString * const CNContactMiddleNameKey                      NS_AVAILABLE(10_11, 9_0);
//姓氏
CONTACTS_EXTERN NSString * const CNContactFamilyNameKey                      NS_AVAILABLE(10_11, 9_0);
//之前的姓氏(ex:国外的女士)
CONTACTS_EXTERN NSString * const CNContactPreviousFamilyNameKey              NS_AVAILABLE(10_11, 9_0);
//姓名后缀
CONTACTS_EXTERN NSString * const CNContactNameSuffixKey                      NS_AVAILABLE(10_11, 9_0);
//昵称
CONTACTS_EXTERN NSString * const CNContactNicknameKey                        NS_AVAILABLE(10_11, 9_0);
//公司(组织)
CONTACTS_EXTERN NSString * const CNContactOrganizationNameKey                NS_AVAILABLE(10_11, 9_0);
//部门
CONTACTS_EXTERN NSString * const CNContactDepartmentNameKey                  NS_AVAILABLE(10_11, 9_0);
//职位
CONTACTS_EXTERN NSString * const CNContactJobTitleKey                        NS_AVAILABLE(10_11, 9_0);
//名字的拼音或音标
CONTACTS_EXTERN NSString * const CNContactPhoneticGivenNameKey               NS_AVAILABLE(10_11, 9_0);
//中间名的拼音或音标
CONTACTS_EXTERN NSString * const CNContactPhoneticMiddleNameKey              NS_AVAILABLE(10_11, 9_0);
//形式的拼音或音标
CONTACTS_EXTERN NSString * const CNContactPhoneticFamilyNameKey              NS_AVAILABLE(10_11, 9_0);
//公司(组织)的拼音或音标(iOS10 才开始存在的呢)
CONTACTS_EXTERN NSString * const CNContactPhoneticOrganizationNameKey        NS_AVAILABLE(10_12, 10_0);
//生日
CONTACTS_EXTERN NSString * const CNContactBirthdayKey                        NS_AVAILABLE(10_11, 9_0);
//农历
CONTACTS_EXTERN NSString * const CNContactNonGregorianBirthdayKey            NS_AVAILABLE(10_11, 9_0);
//备注
CONTACTS_EXTERN NSString * const CNContactNoteKey                            NS_AVAILABLE(10_11, 9_0);
//头像
CONTACTS_EXTERN NSString * const CNContactImageDataKey                       NS_AVAILABLE(10_11, 9_0);
//头像的缩略图
CONTACTS_EXTERN NSString * const CNContactThumbnailImageDataKey              NS_AVAILABLE(10_11, 9_0);
//头像是否可用
CONTACTS_EXTERN NSString * const CNContactImageDataAvailableKey              NS_AVAILABLE(10_12, 9_0);
//类型
CONTACTS_EXTERN NSString * const CNContactTypeKey                            NS_AVAILABLE(10_11, 9_0);
//电话号码
CONTACTS_EXTERN NSString * const CNContactPhoneNumbersKey                    NS_AVAILABLE(10_11, 9_0);
//邮箱地址
CONTACTS_EXTERN NSString * const CNContactEmailAddressesKey                  NS_AVAILABLE(10_11, 9_0);
//住址
CONTACTS_EXTERN NSString * const CNContactPostalAddressesKey                 NS_AVAILABLE(10_11, 9_0);
//其他日期
CONTACTS_EXTERN NSString * const CNContactDatesKey                           NS_AVAILABLE(10_11, 9_0);
//url地址
CONTACTS_EXTERN NSString * const CNContactUrlAddressesKey                    NS_AVAILABLE(10_11, 9_0);
//关联人
CONTACTS_EXTERN NSString * const CNContactRelationsKey                       NS_AVAILABLE(10_11, 9_0);
//社交
CONTACTS_EXTERN NSString * const CNContactSocialProfilesKey                  NS_AVAILABLE(10_11, 9_0);
//即时通信
CONTACTS_EXTERN NSString * const CNContactInstantMessageAddressesKey         NS_AVAILABLE(10_11, 9_0);


获取联系人姓名属性

// RITLContactNameObject获取姓名属性的类目方法
-(void)contactObject:(CNContact *)contact
{
    [super contactObject:contact];

    //设置姓名属性
    self.nickName = contact.nickname;                   //昵称
    self.givenName = contact.givenName;                 //名字
    self.familyName = contact.familyName;               //姓氏
    self.middleName = contact.middleName;               //中间名
    self.namePrefix = contact.namePrefix;               //名字前缀
    self.nameSuffix = contact.nameSuffix;               //名字的后缀
    self.phoneticGivenName = contact.phoneticGivenName; //名字的拼音或音标
    self.phoneticFamilyName = contact.phoneticFamilyName;//姓氏的拼音或音标
    self.phoneticMiddleName = contact.phoneticMiddleName;//中间名的拼音或音标

#ifdef __IPHONE_10_0
    self.phoneticOrganizationName = contact.phoneticOrganizationName;//公司(组织)的拼音或音标
#endif
}


获取联系人的类型

这里需要判断一下该属性是否可用(不只该属性,所有的属性都应先判断一下)不然会抛出异常.

/**
 *  获得联系人类型信息
 */
+ (RITLContactType)__contactTypeProperty
{
    if (![self.currentContact isKeyAvailable:CNContactTypeKey])
    {
        return RITLContactTypeUnknown;//没有可用就是未知
    }

    else if (self.currentContact.contactType == CNContactTypeOrganization)
    {
        return RITLContactTypeOrigination;//如果是组织
    }

    else{
        return RITLContactTypePerson;
    }
}


获得联系人的头像图片

/**
 *  获得联系人的头像图片
 */
+ (UIImage * __nullable)__contactHeadImagePropery
{
    //缩略图Data
    if ([self.currentContact isKeyAvailable:CNContactThumbnailImageDataKey])
    {
        NSData * thumImageData = self.currentContact.thumbnailImageData;

        return [UIImage imageWithData:thumImageData];
    }
    return nil;
}


获取联系人的电话信息

/**
 *  获得电话号码对象数组
 */
+ (NSArray <RITLContactPhoneObject *> *)__contactPhoneProperty
{

    if (![self.currentContact isKeyAvailable:CNContactPhoneNumbersKey])
    {
        return @[];
    }

    //外传数组
    NSMutableArray <RITLContactPhoneObject *> * phones = [NSMutableArray arrayWithCapacity:self.currentContact.phoneNumbers.count];

    for (CNLabeledValue * phoneValue in self.currentContact.phoneNumbers)
    {
        //初始化PhoneObject对象
        RITLContactPhoneObject * phoneObject = [RITLContactPhoneObject new];

        //setValue
        phoneObject.phoneTitle = phoneValue.label;
        phoneObject.phoneNumber = ((CNPhoneNumber *)phoneValue.value).stringValue;

        [phones addObject:phoneObject];
    }

    return [NSArray arrayWithArray:phones];
}


获取联系人的工作信息

/**
 *  获得工作的相关属性
 */
+ (RITLContactJobObject *)__contactJobProperty
{
    RITLContactJobObject * jobObject = [[ RITLContactJobObject alloc]init];

    if ([self.currentContact isKeyAvailable:CNContactJobTitleKey])
    {
        //setValue
        jobObject.jobTitle = self.currentContact.jobTitle;
        jobObject.departmentName = self.currentContact.departmentName;
        jobObject.organizationName = self.currentContact.organizationName;
    }

    return jobObject;
}


获取联系人的邮件信息

/**
 *  获得Email对象的数组
 */
+ (NSArray <RITLContactEmailObject *> *)__contactEmailProperty
{
    if (![self.currentContact isKeyAvailable:CNContactEmailAddressesKey])
    {
        return @[];
    }

    //外传数组
    NSMutableArray <RITLContactEmailObject *> * emails = [NSMutableArray arrayWithCapacity:self.currentContact.emailAddresses.count];

    for (CNLabeledValue * emailValue in self.currentContact.emailAddresses)
    {
        //初始化RITLContactEmailObject对象
        RITLContactEmailObject * emailObject = [[RITLContactEmailObject alloc]init];

        //setValue
        emailObject.emailTitle = emailValue.label;
        emailObject.emailAddress = emailValue.value;

        [emails addObject:emailObject];

    }

    return [NSArray arrayWithArray:emails];
}


获取联系人的地址信息

/**
 *  获得Address对象的数组
 */
+ (NSArray <RITLContactAddressObject *> *)__contactAddressProperty
{
    if (![self.currentContact isKeyAvailable:CNContactPostalAddressesKey]) {

        return @[];

    }

    //外传数组
    NSMutableArray <RITLContactAddressObject *> * addresses = [NSMutableArray arrayWithCapacity:self.currentContact.postalAddresses.count];

    for (CNLabeledValue * addressValue in self.currentContact.postalAddresses)
    {
        //初始化地址对象
        RITLContactAddressObject * addressObject = [[RITLContactAddressObject alloc]init];

        //setValues
        addressObject.addressTitle = addressValue.label;

        //setDetailValue
        [addressObject contactObject:addressValue.value];

        //add object
        [addresses addObject:addressObject];
    }

    return [NSArray arrayWithArray:addresses];
}


获得联系人的生日信息

/**
 *  获得生日的相关属性
 */
+ (RITLContactBrithdayObject *)__contactBrithdayProperty
{
    //实例化对象
    RITLContactBrithdayObject * brithdayObject = [[RITLContactBrithdayObject alloc]init];


    if ([self.currentContact isKeyAvailable:CNContactBirthdayKey])
    {
        //set value
        brithdayObject.brithdayDate = [self.currentContact.birthday.calendar dateFromComponents:self.currentContact.birthday];
        brithdayObject.leapMonth = self.currentContact.birthday.isLeapMonth;
    }

    if ([self.currentContact isKeyAvailable:CNContactNonGregorianBirthdayKey])
    {
        brithdayObject.calendar = self.currentContact.nonGregorianBirthday.calendar.calendarIdentifier;
        brithdayObject.era = self.currentContact.nonGregorianBirthday.era;
        brithdayObject.day = self.currentContact.nonGregorianBirthday.day;
        brithdayObject.month = self.currentContact.nonGregorianBirthday.month;
        brithdayObject.year = self.currentContact.nonGregorianBirthday.year;
    }

    //返回对象
    return brithdayObject;
}


获取联系人的即时通信信息

/**
 *  获得即时通信账号相关信息
 */
+ (NSArray <RITLContactInstantMessageObject *> *)__contactMessageProperty
{
    if (![self.currentContact isKeyAvailable:CNContactInstantMessageAddressesKey])
    {
        return @[];
    }

    //存放数组
    NSMutableArray <RITLContactInstantMessageObject *> * instantMessages = [NSMutableArray arrayWithCapacity:self.currentContact.instantMessageAddresses.count];

    for (CNLabeledValue * instanceAddressValue in self.currentContact.instantMessageAddresses)
    {
        RITLContactInstantMessageObject * instaceObject = [[RITLContactInstantMessageObject alloc]init];

        //set value
        instaceObject.identifier = instanceAddressValue.identifier;
        instaceObject.service = ((CNInstantMessageAddress *)instanceAddressValue.value).service;
        instaceObject.userName = ((CNInstantMessageAddress *)instanceAddressValue.value).username;

        //add
        [instantMessages addObject:instaceObject];
    }

    return [NSArray arrayWithArray:instantMessages];
}


获得联系人的关联人信息

/**
 *  获得联系人的关联人信息
 */
+ (NSArray <RITLContactRelatedNamesObject *> *)__contactRelatedNamesProperty
{
    if (![self.currentContact isKeyAvailable:CNContactRelationsKey])
    {
        return @[];
    }

    //存放数组
    NSMutableArray <RITLContactRelatedNamesObject *> * relatedNames = [NSMutableArray arrayWithCapacity:self.currentContact.contactRelations.count];

    for (CNLabeledValue * relationsValue in self.currentContact.contactRelations)
    {
        RITLContactRelatedNamesObject * relatedObject = [[RITLContactRelatedNamesObject alloc]init];

        //set value
        relatedObject.identifier = relationsValue.identifier;
        relatedObject.relatedTitle = relationsValue.label;
        relatedObject.relatedName = ((CNContactRelation *)relationsValue.value).name;

        [relatedNames addObject:relatedObject];

    }

    return [NSArray arrayWithArray:relatedNames];
}


获取联系人的社交简介信息

/**
 *  获得联系人的社交简介信息
 */
+ (NSArray <RITLContactSocialProfileObject *> *)__contactSocialProfilesProperty
{
    if (![self.currentContact isKeyAvailable:CNContactSocialProfilesKey])
    {
        return @[];
    }

    //外传数组
    NSMutableArray <RITLContactSocialProfileObject *> * socialProfiles = [NSMutableArray arrayWithCapacity:self.currentContact.socialProfiles.count];

    for (CNLabeledValue * socialProfileValue in self.currentContact.socialProfiles) {

        RITLContactSocialProfileObject * socialProfileObject = [[RITLContactSocialProfileObject alloc]init];

        //获得CNSocialProfile对象
        CNSocialProfile * socialProfile = socialProfileValue.value;

        //set value
        socialProfileObject.identifier = socialProfileValue.identifier;
        socialProfileObject.socialProfileTitle = socialProfile.service;
        socialProfileObject.socialProFileAccount = socialProfile.username;
        socialProfileObject.socialProFileUrl = socialProfile.urlString;

        [socialProfiles addObject:socialProfileObject];
    }

    return [NSArray arrayWithArray:socialProfiles];
}


获取联系人的备注信息

/**
 获得联系人的备注信息
 */
+ (NSString * __nullable)__contactNoteProperty
{
    if ([self.currentContact isKeyAvailable:CNContactNoteKey])
    {
        return self.currentContact.note;
    }

    return nil;
}


接收外界通讯录发生变化的方法

这里不再是直接使用C语言的函数赋址来进行方法注册,方法更加的ObjC,选用了更多的通知中心。

/**
 添加变化监听
 */
- (void)__addStoreDidChangeNotification
{
    if (self.notificationDidAdd == false)
    {
        //添加通知
        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(__contactDidChange:) name:CNContactStoreDidChangeNotification object:nil];
        self.notificationDidAdd = !self.notificationDidAdd;
    }  
}

下面是执行变化后的方法:

楼主的测试的时候通讯录变化会连续触发3次通知方法,后面的延迟3s就是解决连续触发的问题,也不知道是个人的程序出问题还是Contact框架的bug,如果大家有什么好办法或者什么好的建议,也请告知一下,十分感谢。

/**
 通讯录发生变化进行的回调

 @param notication 发送的通知
 */
- (void)__contactDidChange:(NSNotification *)notication
{
    //重新获取通讯录
    if (self.contactDidChange != nil )
    {
        //如果可以进行回调
        if (self.shouldResponseContactChange == true)
        {
            //重新加载缓存
            [[RITLContactCatcheManager sharedInstace]reloadContactIdentifiers:^(NSArray<NSString *> * _Nonnull identifiers) {

                NSArray * contacts = [self __contactHandleWithIdentifiers:identifiers];

                //回调
                self.contactDidChange([contacts mutableCopy]);
            }];

            self.responseContactChange = false;

            //延迟3s
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

                self.responseContactChange = true;

            });
        }
    }
}
作者:RunIntoLove 发表于2016/10/19 11:52:05 原文链接
阅读:40 评论:0 查看评论

Android N 新wifi scan流程分析

$
0
0

Android N wifi

Android N 的wifi架构真的是改动挺大,从文件目录看,添加了不少文件,实际上则是对整个wifi模块进行大卸耦,很多东西被才成独立的模块,便于维护,添加feature,这里貌似采用了门面设计模式,可以看FrameworkFacade.java,。下面就看下android N wifi scan都做了哪些修改。

startScan

startScan的API没有做什么变化,也是从wifiManager一直call到WIFiStateMachine里面。
startScan

但是在WiFiStatemachine中处理CMD_START_SCAN这里就开始发生变化了。从handleScanRequest 开始看吧

/*WifiStateMachine.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)
*/

    private void handleScanRequest(Message message) {
        ScanSettings settings = null;
        WorkSource workSource = null;

        // unbundle parameters
        Bundle bundle = (Bundle) message.obj;

        if (bundle != null) {
            settings = bundle.getParcelable(CUSTOMIZED_SCAN_SETTING);
            workSource = bundle.getParcelable(CUSTOMIZED_SCAN_WORKSOURCE);
        }

        Set<Integer> freqs = null;
        //setting里面记录了fred的信息
        if (settings != null && settings.channelSet != null) {
            freqs = new HashSet<Integer>();
            for (WifiChannel channel : settings.channelSet) {
                freqs.add(channel.freqMHz);
            }
        }

        // Retrieve the list of hidden networkId's to scan for.
        Set<Integer> hiddenNetworkIds = mWifiConfigManager.getHiddenConfiguredNetworkIds();

        // call wifi native to start the scan
        //这里是重点
        if (startScanNative(freqs, hiddenNetworkIds, workSource)) {
            // a full scan covers everything, clearing scan request buffer
            if (freqs == null)
                mBufferedScanMsg.clear();
            messageHandlingStatus = MESSAGE_HANDLING_STATUS_OK;
            if (workSource != null) {
                // External worksource was passed along the scan request,
                // hence always send a broadcast
                mSendScanResultsBroadcast = true;
            }
            return;
        }

        // if reach here, scan request is rejected
      .....
    }

看下这个startScanNative都做了些什么,其实这个函数起了一个wifiScanner和WifiStatemachine之间的桥梁作用。

    // TODO this is a temporary measure to bridge between WifiScanner and WifiStateMachine until
    // scan functionality is refactored out of WifiStateMachine.
    /**
     * return true iff scan request is accepted
     */
    private boolean startScanNative(final Set<Integer> freqs, Set<Integer> hiddenNetworkIds,
            WorkSource workSource) {
            //创建了一个ScanSetting对象,目的是为了设置scan的频率模式
        WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
        if (freqs == null) {
            /*假如我们前面没有指定freqs是什么,这里默认会采用WIFI_BAND_BOTH_WITH_DFS
              Both 2.4 GHz band and 5 GHz band; with DFS channels 
              public static final int WIFI_BAND_BOTH_WITH_DFS = 7;  
                   both bands with DFS channels 
            */
            settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
        } else {
            settings.band = WifiScanner.WIFI_BAND_UNSPECIFIED;
            int index = 0;
            settings.channels = new WifiScanner.ChannelSpec[freqs.size()];
            for (Integer freq : freqs) {
                settings.channels[index++] = new WifiScanner.ChannelSpec(freq);
            }
        }
        settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
                | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
        if (hiddenNetworkIds != null && hiddenNetworkIds.size() > 0) {
            int i = 0;
            settings.hiddenNetworkIds = new int[hiddenNetworkIds.size()];
            for (Integer netId : hiddenNetworkIds) {
                settings.hiddenNetworkIds[i++] = netId;
            }
        }
        //创建一个监听对象,这个后面调用mWifiScanner.startScan会用到
        WifiScanner.ScanListener nativeScanListener = new WifiScanner.ScanListener() {
                // ignore all events since WifiStateMachine is registered for the supplicant events
                public void onSuccess() {
                }
                public void onFailure(int reason, String description) {
                    mIsScanOngoing = false;
                    mIsFullScanOngoing = false;
                }
                public void onResults(WifiScanner.ScanData[] results) {
                }
                public void onFullResult(ScanResult fullScanResult) {
                }
                public void onPeriodChanged(int periodInMs) {
                }
            };
        //跳转到mWifiScanner.startScan()
        mWifiScanner.startScan(settings, nativeScanListener, workSource);
        mIsScanOngoing = true;
        mIsFullScanOngoing = (freqs == null);
        lastScanFreqs = freqs;
        return true;
    }

wifiScann是android N新添加进来的东西,其实这个一个client端。在android N,wifi scan被做成一个独立的service。这边是client端,通过asynchannel会发送CMD去service端,跑真正的scan函数。

//WifiScanner.java (frameworks\base\wifi\java\android\net\wifi) 

    /**
     * starts a single scan and reports results asynchronously
     * @param settings specifies various parameters for the scan; for more information look at
     * {@link ScanSettings}
     * @param listener specifies the object to report events to. This object is also treated as a
     *                 key for this scan, and must also be specified to cancel the scan. Multiple
     *                 scans should also not share this object.
     */
    public void startScan(ScanSettings settings, ScanListener listener) {
        startScan(settings, listener, null);
    }

    /**
     * starts a single scan and reports results asynchronously
     * @param settings specifies various parameters for the scan; for more information look at
     * {@link ScanSettings}
     * @param workSource WorkSource to blame for power usage
     * @param listener specifies the object to report events to. This object is also treated as a
     *                 key for this scan, and must also be specified to cancel the scan. Multiple
     *                 scans should also not share this object.
     */
    public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
        Preconditions.checkNotNull(listener, "listener cannot be null");
        int key = addListener(listener);
        if (key == INVALID_KEY) return;
        validateChannel();
        Bundle scanParams = new Bundle();
        scanParams.putParcelable(SCAN_PARAMS_SCAN_SETTINGS_KEY, settings);
        scanParams.putParcelable(SCAN_PARAMS_WORK_SOURCE_KEY, workSource);
        mAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, scanParams);
    }

WifiScanningServiceImpl里面的状态机比较奇怪的,他维护了几类小状态机,针对不同的scan类型做不同的处理,我们这里是简单的从api 调用下来的SingleScan类型。其他的类型,后面再补上。

    public void startService() {
        mClientHandler = new ClientHandler(mLooper);
        mBackgroundScanStateMachine = new WifiBackgroundScanStateMachine(mLooper);
        mWifiChangeStateMachine = new WifiChangeStateMachine(mLooper);
        //
        mSingleScanStateMachine = new WifiSingleScanStateMachine(mLooper);
        mPnoScanStateMachine = new WifiPnoScanStateMachine(mLooper);

        mContext.registerReceiver(
                new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        int state = intent.getIntExtra(
                                WifiManager.EXTRA_SCAN_AVAILABLE, WifiManager.WIFI_STATE_DISABLED);
                        if (DBG) localLog("SCAN_AVAILABLE : " + state);
                        if (state == WifiManager.WIFI_STATE_ENABLED) {
                            mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
                            mSingleScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
                            mPnoScanStateMachine.sendMessage(CMD_DRIVER_LOADED);
                        } else if (state == WifiManager.WIFI_STATE_DISABLED) {
                            mBackgroundScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
                            mSingleScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
                            mPnoScanStateMachine.sendMessage(CMD_DRIVER_UNLOADED);
                        }
                    }
                }, new IntentFilter(WifiManager.WIFI_SCAN_AVAILABLE));

        mBackgroundScanStateMachine.start();
        mWifiChangeStateMachine.start();
        mSingleScanStateMachine.start();
        mPnoScanStateMachine.start();
    }

mSingleScanStateMachine,里面有几个简单的状态。

/*
WifiScanningServiceImpl.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi\scanner)
*/
    /**
     * State machine that holds the state of single scans. Scans should only be active in the
     * ScanningState. The pending scans and active scans maps are swaped when entering
     * ScanningState. Any requests queued while scanning will be placed in the pending queue and
     * executed after transitioning back to IdleState.
     */
        WifiSingleScanStateMachine(Looper looper) {
            super("WifiSingleScanStateMachine", looper);

            setLogRecSize(128);
            setLogOnlyTransitions(false);

            // CHECKSTYLE:OFF IndentationCheck
            addState(mDefaultState);
                addState(mDriverStartedState, mDefaultState);
                    addState(mIdleState, mDriverStartedState);
                    addState(mScanningState, mDriverStartedState);
            // CHECKSTYLE:ON IndentationCheck

            setInitialState(mDefaultState);
        }
 class DefaultState extends State {
            @Override
            public void enter() {
                mActiveScans.clear();
                mPendingScans.clear();
            }
            @Override
            public boolean processMessage(Message msg) {
                switch (msg.what) {
                    //前面如果收到发过来的driver已经加载的CMD_DRIVER_LOADED,则调到mIdleState
                    case CMD_DRIVER_LOADED:
                        transitionTo(mIdleState);
                        ....
  }

        class IdleState extends State {
            @Override
            public void enter() {
                //这里会去做一次尝试扫描,即当driver加载成功的时候,会自己做一次scan的操作
                tryToStartNewScan();
            }

            @Override
            public boolean processMessage(Message msg) {
                return NOT_HANDLED;
            }
        }

所以我们当前其实是处于WifiSingleScanStateMachine.IdleState,当收到CMD_START_SINGLE_SCAN这个CMD的时候,IdleState自己根本无法处理,所以交个他的上级。即在DriverStartedState中处理

        /**
         * State representing when the driver is running. This state is not meant to be transitioned
         * directly, but is instead indented as a parent state of ScanningState and IdleState
         * to hold common functionality and handle cleaning up scans when the driver is shut down.
         */
        class DriverStartedState extends State {
            @Override
            public void exit() {
                mWifiMetrics.incrementScanReturnEntry(
                        WifiMetricsProto.WifiLog.SCAN_FAILURE_INTERRUPTED,
                        mPendingScans.size());
                sendOpFailedToAllAndClear(mPendingScans, WifiScanner.REASON_UNSPECIFIED,
                        "Scan was interrupted");
            }

            @Override
            public boolean processMessage(Message msg) {
                ClientInfo ci = mClients.get(msg.replyTo);

                switch (msg.what) {
                    case WifiScanner.CMD_START_SINGLE_SCAN:
                        mWifiMetrics.incrementOneshotScanCount();
                        int handler = msg.arg2;
                        Bundle scanParams = (Bundle) msg.obj;
                        if (scanParams == null) {
                            logCallback("singleScanInvalidRequest",  ci, handler, "null params");
                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "params null");
                            return HANDLED;
                        }
                        scanParams.setDefusable(true);
                        ScanSettings scanSettings =
                                scanParams.getParcelable(WifiScanner.SCAN_PARAMS_SCAN_SETTINGS_KEY);
                        WorkSource workSource =
                                scanParams.getParcelable(WifiScanner.SCAN_PARAMS_WORK_SOURCE_KEY);
                        if (validateAndAddToScanQueue(ci, handler, scanSettings, workSource)) {
                            replySucceeded(msg);
                            // If were not currently scanning then try to start a scan. Otherwise
                            // this scan will be scheduled when transitioning back to IdleState
                            // after finishing the current scan.
                            //假如当前没有在scanning,那就try 
                            if (getCurrentState() != mScanningState) {
                                tryToStartNewScan();
                            }
                        } else {
                            logCallback("singleScanInvalidRequest",  ci, handler, "bad request");
                            replyFailed(msg, WifiScanner.REASON_INVALID_REQUEST, "bad request");
                            mWifiMetrics.incrementScanReturnEntry(
                                    WifiMetricsProto.WifiLog.SCAN_FAILURE_INVALID_CONFIGURATION, 1);
                        }
                        return HANDLED;
     .....

tryToStartNewScan中做了一次scan之后,如果没有失败,则会跳转到mScanningState

 void tryToStartNewScan() {
            if (mScannerImpl.startSingleScan(settings, this)) {
                // swap pending and active scan requests
                RequestList<ScanSettings> tmp = mActiveScans;
                mActiveScans = mPendingScans;
                mPendingScans = tmp;
                // make sure that the pending list is clear
                mPendingScans.clear();
                transitionTo(mScanningState);
  }

mScannerImpl其实是抽象类WifiScannerImpl的对象,这只是一个接口,要找到实现方,才知道做了什么。google卸耦真是干净利落。

/*
SupplicantWifiScannerImpl.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi\scanner)   
*/
public class SupplicantWifiScannerImpl extends WifiScannerImpl implements Handler.Callback {
    .........
    @Override
    public boolean startSingleScan(WifiNative.ScanSettings settings,
            WifiNative.ScanEventHandler eventHandler) {
        if (eventHandler == null || settings == null) {
            Log.w(TAG, "Invalid arguments for startSingleScan: settings=" + settings
                    + ",eventHandler=" + eventHandler);
            return false;
        }
        if (mPendingSingleScanSettings != null
                || (mLastScanSettings != null && mLastScanSettings.singleScanActive)) {
            Log.w(TAG, "A single scan is already running");
            return false;
        }
        synchronized (mSettingsLock) {
            mPendingSingleScanSettings = settings;
            mPendingSingleScanEventHandler = eventHandler;
            //因为有多种scan共存,为防止打架,所以google搞了这个函数来做指挥
            processPendingScans();
            return true;
        }
    }
.......
    private void processPendingScans() {
        synchronized (mSettingsLock) {
            // Wait for the active scan result to come back to reschedule other scans,
            // unless if HW pno scan is running. Hw PNO scans are paused it if there
            // are other pending scans,
            if (mLastScanSettings != null && !mLastScanSettings.hwPnoScanActive) {
                return;
            }

            ChannelCollection allFreqs = mChannelHelper.createChannelCollection();
            Set<Integer> hiddenNetworkIdSet = new HashSet<Integer>();
            final LastScanSettings newScanSettings =
                    new LastScanSettings(mClock.elapsedRealtime());

            // Update scan settings if there is a pending scan
            if (!mBackgroundScanPaused) {
                if (mPendingBackgroundScanSettings != null) {
                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
                    mNextBackgroundScanPeriod = 0;
                    mPendingBackgroundScanSettings = null;
                    mPendingBackgroundScanEventHandler = null;
                    mBackgroundScanPeriodPending = true;
                }
                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
                            ++bucket_id) {
                        WifiNative.BucketSettings bucket =
                                mBackgroundScanSettings.buckets[bucket_id];
                        if (mNextBackgroundScanPeriod % (bucket.period_ms
                                        / mBackgroundScanSettings.base_period_ms) == 0) {
                            if ((bucket.report_events
                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
                            }
                            if ((bucket.report_events
                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
                            }
                            // only no batch if all buckets specify it
                            if ((bucket.report_events
                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
                            }

                            allFreqs.addChannels(bucket);
                        }
                    }
                    if (!allFreqs.isEmpty()) {
                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
                                mBackgroundScanSettings.report_threshold_num_scans,
                                mBackgroundScanSettings.report_threshold_percent);
                    }

                    int[] hiddenNetworkIds = mBackgroundScanSettings.hiddenNetworkIds;
                    if (hiddenNetworkIds != null) {
                        int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length,
                                MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
                        for (int i = 0; i < numHiddenNetworkIds; i++) {
                            hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
                        }
                    }

                    mNextBackgroundScanPeriod++;
                    mBackgroundScanPeriodPending = false;
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                            mClock.elapsedRealtime() + mBackgroundScanSettings.base_period_ms,
                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
                }
            }

            if (mPendingSingleScanSettings != null) {
                boolean reportFullResults = false;
                ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
                for (int i = 0; i < mPendingSingleScanSettings.num_buckets; ++i) {
                    WifiNative.BucketSettings bucketSettings =
                            mPendingSingleScanSettings.buckets[i];
                    if ((bucketSettings.report_events
                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
                        reportFullResults = true;
                    }
                    singleScanFreqs.addChannels(bucketSettings);
                    allFreqs.addChannels(bucketSettings);
                }
                newScanSettings.setSingleScan(reportFullResults, singleScanFreqs,
                        mPendingSingleScanEventHandler);
                int[] hiddenNetworkIds = mPendingSingleScanSettings.hiddenNetworkIds;
                if (hiddenNetworkIds != null) {
                    int numHiddenNetworkIds = Math.min(hiddenNetworkIds.length,
                            MAX_HIDDEN_NETWORK_IDS_PER_SCAN);
                    for (int i = 0; i < numHiddenNetworkIds; i++) {
                        hiddenNetworkIdSet.add(hiddenNetworkIds[i]);
                    }
                }
                mPendingSingleScanSettings = null;
                mPendingSingleScanEventHandler = null;
            }

            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
                    && !allFreqs.isEmpty()) {
                pauseHwPnoScan();
                Set<Integer> freqs = allFreqs.getSupplicantScanFreqs();
                boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet);
                if (success) {
                    // TODO handle scan timeout
                    if (DBG) {
                        Log.d(TAG, "Starting wifi scan for freqs=" + freqs
                                + ", background=" + newScanSettings.backgroundScanActive
                                + ", single=" + newScanSettings.singleScanActive);
                    }
                    mLastScanSettings = newScanSettings;
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                            mClock.elapsedRealtime() + SCAN_TIMEOUT_MS,
                            TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
                } else {
                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
                    // indicate scan failure async
                    mEventHandler.post(new Runnable() {
                            public void run() {
                                if (newScanSettings.singleScanEventHandler != null) {
                                    newScanSettings.singleScanEventHandler
                                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
                                }
                            }
                        });
                    // TODO(b/27769665) background scans should be failed too if scans fail enough
                }
            } else if (isHwPnoScanRequired()) {
                newScanSettings.setHwPnoScan(mPnoEventHandler);
                if (startHwPnoScan()) {
                    mLastScanSettings = newScanSettings;
                } else {
                    Log.e(TAG, "Failed to start PNO scan");
                    // indicate scan failure async
                    mEventHandler.post(new Runnable() {
                        public void run() {
                            if (mPnoEventHandler != null) {
                                mPnoEventHandler.onPnoScanFailed();
                            }
                            // Clean up PNO state, we don't want to continue PNO scanning.
                            mPnoSettings = null;
                            mPnoEventHandler = null;
                        }
                    });
                }
            }
        }
    }

好吧上面的东西有点多,抽点重点出来看

            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
                    && !allFreqs.isEmpty()) {
                pauseHwPnoScan();
                Set<Integer> freqs = allFreqs.getSupplicantScanFreqs();
                //下一个scan的命令下去
                boolean success = mWifiNative.scan(freqs, hiddenNetworkIdSet);
                if (success) {
                    // TODO handle scan timeout
                    if (DBG) {
                        Log.d(TAG, "Starting wifi scan for freqs=" + freqs
                                + ", background=" + newScanSettings.backgroundScanActive
                                + ", single=" + newScanSettings.singleScanActive);
                    }
                    mLastScanSettings = newScanSettings;
                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                            mClock.elapsedRealtime() + SCAN_TIMEOUT_MS,
                            TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
                } else {
                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
                    // indicate scan failure async
                    mEventHandler.post(new Runnable() {
                            public void run() {
                                if (newScanSettings.singleScanEventHandler != null) {
                                    newScanSettings.singleScanEventHandler
                                            .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
                                }
                            }
                        });
                    // TODO(b/27769665) background scans should be failed too if scans fail enough
                }

最后跑到wifinative,这个和之前的差别不大,

    /**
     * Start a scan using wpa_supplicant for the given frequencies.
     * @param freqs list of frequencies to scan for, if null scan all supported channels.
     * @param hiddenNetworkIds List of hidden networks to be scanned for.
     */
    public boolean scan(Set<Integer> freqs, Set<Integer> hiddenNetworkIds) {
        String freqList = null;
        String hiddenNetworkIdList = null;
        if (freqs != null && freqs.size() != 0) {
            freqList = createCSVStringFromIntegerSet(freqs);
        }
        if (hiddenNetworkIds != null && hiddenNetworkIds.size() != 0) {
            hiddenNetworkIdList = createCSVStringFromIntegerSet(hiddenNetworkIds);
        }
        return scanWithParams(freqList, hiddenNetworkIdList);
    }

    private boolean scanWithParams(String freqList, String hiddenNetworkIdList) {
        StringBuilder scanCommand = new StringBuilder();
        scanCommand.append("SCAN TYPE=ONLY");
        if (freqList != null) {
            scanCommand.append(" freq=" + freqList);
        }
        if (hiddenNetworkIdList != null) {
            scanCommand.append(" scan_id=" + hiddenNetworkIdList);
        }
        return doBooleanCommand(scanCommand.toString());
    }

setScanResults

scanResults的上报,还是跟之前一样,接收supplicant的CMD。但是这个函数被重构了,写得更优雅了。用了一个类NetworkDetail来描述热点的信息。

总结

在scan模块,google主要添加了一个scanservice,独立出来一个service,添加了类似管理中心的东西,便于处理多种不同场景下的scan需求。在之前,wifi的scan确实引发了不少bug,希望重构之后,这个模块不要再打到其他模块了。

作者:izobs 发表于2016/10/19 11:57:30 原文链接
阅读:49 评论:0 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>