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

性能优化十五之电量优化案例

$
0
0

前言

     之前说了那么的电量优化理论,到现在自己都没有去做个小案例实践一下,这次博客的内容是根据实际的使用环境想出的一个小案例,方便自己去理解,电量优化该怎么用?

问题抛出:

     为了省电,有些工作可以放当手机插上电源的时候去做。往往这样的情况非常多。像这些不需要及时地和用户交互的操作可以放到后面处理。比如:360手机助手,当充上电的时候,才会自动清理手机垃圾,自动备份上传图片、联系人等到云端。例如:拍照和图片处理是否可以做一些优化,假如现在手机的电量比较低,并且没有充电,拍照动作是要立马执行的,但是图片的处理是否可以放在用户插上手机充电器之后去处理?

首先我们得知道如何去获取到手机目前的充电状态:

    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = this.registerReceiver(null, filter);

    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);
    boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
    boolean wirelessCharge = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);
    }
    return (usbCharge || acCharge || wirelessCharge);

在上面的例子演示了如何立即获取到手机的充电状态,得到充电状态信息之后,我们可以有针对性的对部分代码做优化。比如我们可以判断只有当前手机为AC充电状态时才去执行一些非常耗电的操作.

下面给出一个针对上面案例的代码

小案例代码:
public class WaitForPowerActivity extends ActionBarActivity {
    public static final String LOG_TAG = "WaitForPowerActivity";

    TextView mPowerMsg;
    ImageView mCheyennePic;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_power);

        mPowerMsg = (TextView) findViewById(R.id.cheyenne_txt);
        mCheyennePic = (ImageView) findViewById(R.id.cheyenne_img);

        Button theButtonThatTakesPhotos = (Button) findViewById(R.id.power_take_photo);
        theButtonThatTakesPhotos.setText(R.string.take_photo_button);

        final Button theButtonThatFiltersThePhoto = (Button) findViewById(R.id.power_apply_filter);
        theButtonThatFiltersThePhoto.setText(R.string.filter_photo_button);

        theButtonThatTakesPhotos.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                takePhoto();
                // After we take the photo, we should display the filter option.
                theButtonThatFiltersThePhoto.setVisibility(View.VISIBLE);
            }
        });

        theButtonThatFiltersThePhoto.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                applyFilter();
            }
        });
    }


    private void takePhoto() {  
        // Make photo of Cheyenne appear.
        mPowerMsg.setText(R.string.photo_taken);
        mCheyennePic.setImageResource(R.drawable.cheyenne);
    }


    private void applyFilter() {
        //是否在充电
        if(!checkForPower()){
            mPowerMsg.setText("请充上电,再处理!");
            return;
        }
        //实际开发中这边应该是对拍照保存下来的图片进行处理
        mCheyennePic.setImageResource(R.drawable.pink_cheyenne);
        mPowerMsg.setText(R.string.photo_filter);
    }

    private boolean checkForPower(){
        //获取电池的充电状态
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = this.registerReceiver(null,filter);

        //BatteryManager
        int  chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED,-1);
        boolean usb = chargePlug ==BatteryManager.BATTERY_PLUGGED_USB;//usb充电
        boolean ac = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;//交流电
        //无线充电---API>=17
        boolean wireless = false;
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1) {
            wireless = chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS;
        }
        return  (usb||ac||wireless);
    }
}

上面的案例就是当手机处于充电状态的时才会去执行处理图片的逻辑。

作者:hpc19950723 发表于2017/4/7 16:57:31 原文链接
阅读:78 评论:0 查看评论

从头开始学 RecyclerView(五) ItemDecoration 详解

$
0
0

前言


RecyclerView.ItemDecoration,通过名字来看,它就是用来装饰Item的。
在类ListView的视图中,可能需要绘制分隔线;在类GridView的网格视图中,可能需要绘制单元格样式… 这些都可以由重写RecyclerView.ItemDecoration来进行定制。
然后调用mRecyclerView.addItemDecoration(itemDecoration); 即可

分析


看下RecyclerView.ItemDecoration的源码:

public static abstract class ItemDecoration {

    public void onDraw(Canvas c, RecyclerView parent, State state) {
        onDraw(c, parent);
    }


    public void onDrawOver(Canvas c, RecyclerView parent, State state) {
        onDrawOver(c, parent);
    }


    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                parent);
    }
}

这里找的是,support-v7-25.3.1的RecyclView源码,去除了已过时的方法。

关于这三方法的作用:

  • onDraw
    它在item之前绘制,就像装饰了一个item的背景,在item之前绘制

  • onDrawOver
    它在item之后绘制,就像装饰了一个item的前景,在item之后绘制

  • getItemOffsets
    指定item间的偏移量。由指定outRect的left、top、right、bottom的像素值,来偏移item。指定了偏移量后,可能会影响item的宽或高。

这三个方法的执行顺序:getItemOffsets > onDraw > onDrawOver

ItemDecoration示例


DividerItemDecoration

DividerItemDecoration,在support-v7中已经集成了。全称 android.support.v7.widget.DividerItemDecoration

通过通的注释可知,它是用于在LinearLayoutManager所管理的item之间添加分隔线。

详细代码就不贴了。值得一提的是,它里面使用了一个系统属性来指定drawable资源:private static final int[] ATTRS = new int[]{ android.R.attr.listDivider };。 一般在ListView中我们就经常使用一个xml属性:android:divider。而这个属性对应的就是android:listDivider。
所以在styles.xml 中就可以定义这个属性:

<resources>
  <style ...>   
    <item name="android:listDivider">@drawable/divider_hori</item>
  </style>
</resources>

效果图:

类ListView分隔线
这条 蓝红绿颜色渐变的水平线,就是@drawable/divider_hori
 

GridDecoration

当使用GridLayoutManager时,想在每个item外绘制一个矩形框。
实现:重写RecyclerView.ItemDecoration的onDraw,绘制矩形框;重写getItemOffsets,指定item间偏移量。

@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

    Paint paint = new Paint();
    paint.setColor(mBackColor);

    for (int i = 0, len = parent.getLayoutManager().getChildCount(); i < len; i++) {
        final View child = parent.getChildAt(i);

        int offset = 5;
        float left = child.getLeft() - offset;
        float top = child.getTop() - offset;
        float right = child.getRight() + offset;
        float bottom = child.getBottom() + offset;

        c.drawRect(left, top, right, bottom, paint);
    }
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
     outRect.offset(20, 30);

     int position = parent.getChildAdapterPosition(view);
     if (position % 2 == 1) {
         outRect.offsetTo(100, 20);
     }
 }

效果图:

Grid-Item外框

示例详情见:https://github.com/aa86799/RecyclerView/tree/recycler-restart/

作者:jjwwmlp456 发表于2017/4/7 17:58:24 原文链接
阅读:254 评论:0 查看评论

初识Volley的基本用法

$
0
0
转载自:

http://blog.csdn.net/guolin_blog/article/details/17482095


1. Volley简介


我们平时在开发Android应用的时候不可避免地都需要用到网络技术,而多数情况下应用程序都会使用HTTP协议来发送和接收网络数据。Android系统中主要提供了两种方式来进行HTTP通信,HttpURLConnection和HttpClient,几乎在任何项目的代码中我们都能看到这两个类的身影,使用率非常高。


不过HttpURLConnection和HttpClient的用法还是稍微有些复杂的,如果不进行适当封装的话,很容易就会写出不少重复代码。于是乎,一些Android网络通信框架也就应运而生,比如说AsyncHttpClient,它把HTTP所有的通信细节全部封装在了内部,我们只需要简单调用几行代码就可以完成通信操作了。再比如Universal-Image-Loader,它使得在界面上显示网络图片的操作变得极度简单,开发者不用关心如何从网络上获取图片,也不用关心开启线程、回收图片资源等细节,Universal-Image-Loader已经把一切都做好了。


Android开发团队也是意识到了有必要将HTTP的通信操作再进行简单化,于是在2013年Google I/O大会上推出了一个新的网络通信框架——Volley。Volley可是说是把AsyncHttpClient和Universal-Image-Loader的优点集于了一身,既可以像AsyncHttpClient一样非常简单地进行HTTP通信,也可以像Universal-Image-Loader一样轻松加载网络上的图片。除了简单易用之外,Volley在性能方面也进行了大幅度的调整,它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。


下图所示的这些应用都是属于数据量不大,但网络通信频繁的,因此非常适合使用Volley。



2. 下载Volley


介绍了这么多理论的东西,下面我们就准备开始进行实战了,首先需要将Volley的jar包准备好,如果你的电脑上装有Git,可以使用如下命令下载Volley的源码:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. git clone https://android.googlesource.com/platform/frameworks/volley  

下载完成后将它导入到你的Eclipse工程里,然后再导出一个jar包就可以了。如果你的电脑上没有Git,那么也可以直接使用我导出好的jar包,下载地址是:http://download.csdn.net/detail/sinyu890807/7152015 。


新建一个Android项目,将volley.jar文件复制到libs目录下,这样准备工作就算是做好了。


3. StringRequest的用法


前面已经说过,Volley的用法非常简单,那么我们就从最基本的HTTP通信开始学习吧,即发起一条HTTP请求,然后接收HTTP响应。首先需要获取到一个RequestQueue对象,可以调用如下方法获取到:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. RequestQueue mQueue = Volley.newRequestQueue(context);  

注意这里拿到的RequestQueue是一个请求队列对象,它可以缓存所有的HTTP请求,然后按照一定的算法并发地发出这些请求。RequestQueue内部的设计就是非常合适高并发的,因此我们不必为每一次HTTP请求都创建一个RequestQueue对象,这是非常浪费资源的,基本上在每一个需要和网络交互的Activity中创建一个RequestQueue对象就足够了。


接下来为了要发出一条HTTP请求,我们还需要创建一个StringRequest对象,如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. StringRequest stringRequest = new StringRequest("http://www.baidu.com",  
  2.                         new Response.Listener<String>() {  
  3.                             @Override  
  4.                             public void onResponse(String response) {  
  5.                                 Log.d("TAG", response);  
  6.                             }  
  7.                         }, new Response.ErrorListener() {  
  8.                             @Override  
  9.                             public void onErrorResponse(VolleyError error) {  
  10.                                 Log.e("TAG", error.getMessage(), error);  
  11.                             }  
  12.                         });  

可以看到,这里new出了一个StringRequest对象,StringRequest的构造函数需要传入三个参数,第一个参数就是目标服务器的URL地址,第二个参数是服务器响应成功的回调,第三个参数是服务器响应失败的回调。其中,目标服务器地址我们填写的是百度的首页,然后在响应成功的回调里打印出服务器返回的内容,在响应失败的回调里打印出失败的详细信息。


最后,将这个StringRequest对象添加到RequestQueue里面就可以了,如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. mQueue.add(stringRequest);  

另外,由于Volley是要访问网络的,因此不要忘记在你的AndroidManifest.xml中添加如下权限:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. <uses-permission android:name="android.permission.INTERNET" />  

好了,就是这么简单,如果你现在运行一下程序,并发出这样一条HTTP请求,就会看到LogCat中会打印出如下图所示的数据。




没错,百度返回给我们的就是这样一长串的HTML代码,虽然我们看起来会有些吃力,但是浏览器却可以轻松地对这段HTML代码进行解析,然后将百度的首页展现出来。

这样的话,一个最基本的HTTP发送与响应的功能就完成了。你会发现根本还没写几行代码就轻易实现了这个功能,主要就是进行了以下三步操作:


1. 创建一个RequestQueue对象。

2. 创建一个StringRequest对象。

3. 将StringRequest对象添加到RequestQueue里面。


不过大家都知道,HTTP的请求类型通常有两种,GET和POST,刚才我们使用的明显是一个GET请求,那么如果想要发出一条POST请求应该怎么做呢?StringRequest中还提供了另外一种四个参数的构造函数,其中第一个参数就是指定请求类型的,我们可以使用如下方式进行指定:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener);  
可是这只是指定了HTTP请求方式是POST,那么我们要提交给服务器的参数又该怎么设置呢?很遗憾,StringRequest中并没有提供设置POST参数的方法,但是当发出POST请求的时候,Volley会尝试调用StringRequest的父类——Request中的getParams()方法来获取POST参数,那么解决方法自然也就有了,我们只需要在StringRequest的匿名类中重写getParams()方法,在这里设置POST参数就可以了,代码如下所示:
[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. StringRequest stringRequest = new StringRequest(Method.POST, url,  listener, errorListener) {  
  2.     @Override  
  3.     protected Map<String, String> getParams() throws AuthFailureError {  
  4.         Map<String, String> map = new HashMap<String, String>();  
  5.         map.put("params1""value1");  
  6.         map.put("params2""value2");  
  7.         return map;  
  8.     }  
  9. };  

你可能会说,每次都这样用起来岂不是很累?连个设置POST参数的方法都没有。但是不要忘记,Volley是开源的,只要你愿意,你可以自由地在里面添加和修改任何的方法,轻松就能定制出一个属于你自己的Volley版本。


4. JsonRequest的用法


学完了最基本的StringRequest的用法,我们再来进阶学习一下JsonRequest的用法。类似于StringRequest,JsonRequest也是继承自Request类的,不过由于JsonRequest是一个抽象类,因此我们无法直接创建它的实例,那么只能从它的子类入手了。JsonRequest有两个直接的子类,JsonObjectRequest和JsonArrayRequest,从名字上你应该能就看出它们的区别了吧?一个是用于请求一段JSON数据的,一个是用于请求一段JSON数组的。


至于它们的用法也基本上没有什么特殊之处,先new出一个JsonObjectRequest对象,如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html"null,  
  2.         new Response.Listener<JSONObject>() {  
  3.             @Override  
  4.             public void onResponse(JSONObject response) {  
  5.                 Log.d("TAG", response.toString());  
  6.             }  
  7.         }, new Response.ErrorListener() {  
  8.             @Override  
  9.             public void onErrorResponse(VolleyError error) {  
  10.                 Log.e("TAG", error.getMessage(), error);  
  11.             }  
  12.         });  
可以看到,这里我们填写的URL地址是http://m.weather.com.cn/data/101010100.html,这是中国天气网提供的一个查询天气信息的接口,响应的数据就是以JSON格式返回的,然后我们在onResponse()方法中将返回的数据打印出来。


最后再将这个JsonObjectRequest对象添加到RequestQueue里就可以了,如下所示:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. mQueue.add(jsonObjectRequest);  

这样当HTTP通信完成之后,服务器响应的天气信息就会回调到onResponse()方法中,并打印出来。现在运行一下程序,发出这样一条HTTP请求,就会看到LogCat中会打印出如下图所示的数据。




由此可以看出,服务器返回给我们的数据确实是JSON格式的,并且onResponse()方法中携带的参数也正是一个JSONObject对象,之后只需要从JSONObject对象取出我们想要得到的那部分数据就可以了。


你应该发现了吧,JsonObjectRequest的用法和StringRequest的用法基本上是完全一样的,Volley的易用之处也在这里体现出来了,会了一种就可以让你举一反三,因此关于JsonArrayRequest的用法相信已经不需要我再去讲解了吧。

作者:qq_18738333 发表于2017/4/7 22:47:13 原文链接
阅读:200 评论:0 查看评论

Xcode工程项目改名的操作步骤

$
0
0

大家知道很多时候我们不想重新建立一个Xcode工程,而是想简单拷贝一个已存在的项目然后改个名字再在此基础上做出一些修改.

但是只是简单的改变Xcode工程项目的目录的名字还足够,为了编译构建成功,我们还得做出其他一些修改:

这里写图片描述

首先先改蓝色的project6a这里,然后Xcode会弹出对话框提示是否将新名称应用到一些关键地方,当然选择Yes.

接着是修改黄色的project6a名称.

这里写图片描述

然后在Xcode顶部的旧项目名称上点击,然后进入Manage Schemes:

这里写图片描述

现在可以修改Schemes的名称了.

但是磁盘上实际项目的文件夹名称还没有修改哎:

这里写图片描述

我们需要将project6a文件夹更名,完了之后会发现Xcode提示找不到文件夹

这里写图片描述

此时点击左上角黄色的项目名称,在右边的分栏中点击location右下角的那个灰色的文件夹图标已选择你修改后的的文件路径.

你以为这样就结束了吗?然而并没有… ;(

你会发现此时编译还是通不过,提示找不到info.plist文件的路径.但你会发现此时info.plist的路径没毛病啊:

这里写图片描述

这解决起来也很容易,打开如下位置:

这里写图片描述

重新选择正确的plist路径即可!

作者:mydo 发表于2017/4/8 11:21:51 原文链接
阅读:114 评论:0 查看评论

Activity生命周期全面分析

$
0
0

当你想跨入高级开发的时候,你会发现,你总是会欠缺很多基础的东西.


本文章将会将Activity的生命周期分为两部分进行讲解.一种是典型情况下.另一种是异常情况下的.

典型情况的生命周期是指:

由用户参与的情况下,Activity所经历的生命周期的改变.

异常情况是指:

Activity被系统回收或者是由于当前设备的Configuration发生改变从而导致Activity被销毁重建.

典型情况

在正常情况下,Activity会经历如下的生命周期

  • onCreate:表示Activity正在被创建,这是生命周期的第一个方法.在这个方法中我们可以做一些初始化操作.例如去设置加载布局资源(setContentView()),初始化Activity所需要的数据等.
  • onRestart:表示Activity重新被启动.一般情况下,当当前的Activity从不可见到可见的情况下就会调用onRestart()方法.这种情形一般都是用户的一些行为导致的,例如用户按下home键,又重新进入App,或者是用户打开新的页面,又按返回按钮,重新回到之前的页面就会出现这种情况
  • onStart:表示Activity正在被启动,即将开始,这是Activity已经可见了,但是还没有出现在前台.还无法和用户交互.我们可以理解为,Activity已经出现了,但是我们看不到.
  • onResume:表示Activity已经可见了.并且出现在了前台.已经可以活动.这个跟onStart()都是可见了,但是区别在于后者是可以看到了,前者还不可以看到.
  • onPause:表示Activity正在停止.正常情况下,紧着着onStop方法也会被调用.此时可以进行过度动画,存储数据等操作.但是注意一定不能耗时,不然会影响新的界面的展示.因为只有在之前onPause方法执行完毕,新的界面的onResume才会执行.
  • onStop:表示Activity将要停止,可以稍微做些重量级的回收工作.不能太耗时.
  • onDestroy:表示Activity即将被销毁,这是Activity生命周期方法的最后一个回调.在此时我们可以做些回收工作和资源释放等.

正常情况下,生命周期只有上边的七个,下图会进行说明.
这里写图片描述

针对此图在做些说明.

  • 针对一个特定的Activity,回调方法顺序如下.onCreate()–>onStart()–>Resume();
  • 当用户打开新的Activity或者切换到桌面的时候,回调顺序如下onPause()–>onStop();(如果遇到特殊情况,例如该Activity采用了透明的主题的话,那么就不会调用onStop()方法);
  • 当用户再次回到之前的Activity的时候,回调方法如下onRestart()–>onStart()–>onResume();
  • 当用户点击back键的时候,当前Activity的回调方法如下:onPause()–>onStop()–>onDestroy();
  • 当Activity被系统回收并重新打开的时候生命周期是onCreate()–>onStart()–>Resume();(只是生命周期方法一样,过程不一样)
  • 从整个生命周期来说.onCreate()和onDestroy()是配对的,分别标识着Activity的创建和销毁.并且只可能有一次调用.从是否可见来说onStart()和onStop()方法是配对的.随着用户的操作或者屏幕电量和熄灭,这两个方法会被调用多次.从是否在前台来说.onResume()和onPause()是配对的.随着用户的操作或者屏幕的点亮和熄灭,这两个方法会被调用多次.

一个Activity被打开的方法调用

04-08 03:26:42.873 1735-1735/cn.yuan.xiaoyu I/AActivity: onCreate()
04-08 03:26:42.873 1735-1735/cn.yuan.xiaoyu I/AActivity: onStart()
04-08 03:26:42.875 1735-1735/cn.yuan.xiaoyu I/AActivity: onResume()

A界面打开B界面方法调用

04-08 03:29:35.362 4205-4205/cn.yuan.xiaoyu I/AActivity: onPause()
04-08 03:29:35.406 4205-4205/cn.yuan.xiaoyu I/BActivity: onCreate()
04-08 03:29:35.407 4205-4205/cn.yuan.xiaoyu I/BActivity: onStart()
04-08 03:29:35.408 4205-4205/cn.yuan.xiaoyu I/BActivity: onResume()
04-08 03:29:36.194 4205-4205/cn.yuan.xiaoyu I/AActivity: onStop()

B界面按back键

04-08 03:30:32.405 4205-4205/cn.yuan.xiaoyu I/BActivity: onPause()
04-08 03:30:32.438 4205-4205/cn.yuan.xiaoyu I/AActivity: onRestart()
04-08 03:30:32.438 4205-4205/cn.yuan.xiaoyu I/AActivity: onStart()
04-08 03:30:32.438 4205-4205/cn.yuan.xiaoyu I/AActivity: onResume()
04-08 03:30:33.070 4205-4205/cn.yuan.xiaoyu I/BActivity: onStop()

异常情况

资源相关的系统配置发生改变导致Activity被杀死并重新创建

在默认情况下,如果我们的Activity不做特殊处理,那么当系统配置发生改变后,Activity就会被销毁并创建.如图所示:
这里写图片描述
当系统配置发生改变后,Activity会被销毁,其onPause(),onStop(),onDestroy()均会被调用.同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态,这个方法的调用时机是在onStop()之前,这个方法只有在发生异常情况下才会调用,正常情况下,不会调用这个方法.当Activity重新被创建成功之后,系统会调用onRestoreInstanceState(),并且把Activity销毁时onSaveInstanceState()方法所保存的bundle对象作为参数同时传递给onRestoreInstanceState()和onCreate()方法.

作者:EaskShark 发表于2017/4/8 16:27:33 原文链接
阅读:184 评论:0 查看评论

[JNI] 开发实例(2) 编译libwebsocket,封装jni函数,搭建IM通信基础服务

$
0
0

WebSocket协议是基于TCP的一种新的协议。WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符。它实现了浏览器与服务器全双工(full-duplex)通信。这段介绍来自百科。

当然websocket也可以用于android建立长链接,实现IM通信
优势:节省内存空间。当然这个是服务端并发时候节省内存空间,支持的并发量更大 ,这个我没做服务端没有验证。

原理和优势不再本文讨论范围内,今天主要记录下怎么编译libwebsocket.so

1.下载Libwebseocket库

git clone https://github.com/warmcat/libwebsockets.git

2.环境准备(Mac版)

2.1安装zlib :

brew install zlib

2.2安装makedepend:

brew install makedepend

2.3安装cmake :

brew install cmake

3.编译.a静态文件

有了上面这些工具,准备工作差不多了,然后通过libwebsocket/contrib目录下的android-make-script.sh编译.a文件 这个sh文件有些问题,比如:只能编译出arm架构的文件,并且在编译zlib库时使用的libtool有问题,libwebsocket原文件编译出错。

针对这些问题作了一些修改:

3.1 修改output.c和http2.c原文件编译错误,这个已经提交到libwebsocket base2.2版本上了

 https://github.com/warmcat/libwebsockets/commit/34842d7492b728349f6a6898e3893b08d70625fa

3.2 替换libtool文件

mv ./Makefile ./Makefile.old
sed "s/AR=libtool/AR=`echo ${AR}|sed 's#\/#\\\/#g'`/" ./Makefile.old > Makefile.mid
sed "s/ARFLAGS=-o/ARFLAGS=-r/" ./Makefile.mid > Makefile

3.3 增加build x86架构文件编译

android支持的架构不光是arm,还有x86 x86-64等架构的机器,常用的armabi 和x86 这里提供了编译方式 如果还需要其他架构的可以根据android-make-script-all.sh文件 修改对应的参数
其他架构的参数可以在原始项目的test-server中的NativeLibs.mk中找到,然后修改android-make-script-all.sh文件

4.JNI封装websocket.

4.1初始化 initLws

初始化context
先准备好必要的参数 :host ,port ,path,timeout, ping,ca cert
涉及到函数:

jni_setConnectionParameters // host port path
jni_setTimeout  //超时
jni_setPingInterval //ping 间隔
jni_setCaCert //证书

然后调用lws_create_context初始化context

jni_initLws

4.2连接 connect

当context初始化后,就可以连接服务器了

jni_connectLws

在jni_connectLws调用lws_client_connect_via_info连接服务器

还有一个重要的函数

jni_serviceLws

这个方法的作用是,轮训,不断的去检查是否有消息接收,检查到有新消息到达,通过callback回调,然后通过receiveMessage回调到java层

4.3发送/接收消息 writeLws/callback

接收消息

我们在初始化context时注册了一个协议,这个protocol中包含一个callback,我们就是通过这个callback来接收服务端的消息,及错误信息

static int callback(
        struct lws *wsi,
        enum lws_callback_reasons reason,
        void *user,
        void *in,
        size_t len
) {
    //
    return 0;
}

只需要关注下面两个参数

reason:回执code 
in :消息体

发送消息

jni_sendMessageLws

调用的lws_write来发送消息,这里要注意的是发送消息的大小(初始化时候设置的),如果超过这个大小消息会分片,分片的消息需要自行处理,在callback中LWS_CALLBACK_SERVER_WRITEABLE处理剩下的报文。
当然我们平时发送的消息体不会太大,一般几百个字节最够了,我这里设置的4K
够发1500+个汉字。

4.4断开 exitLws

调用lws_context_destroy断开连接

重要函数参数:

状态值 含义
LWS_CALLBACK_WSI_CREATE 含义:正在创建ws连接对象备注:此时表1中wsi对象和user对象依然为空指针,因此,还不能初始化用户自定义对象。回调函数的参数含义: context: 全局上下文 wsi: 空指针 user: 空指针 in: 空指针len: 0
LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION 使用lws库的人员可以在此过滤协议。备注:在此处返回非0值时,lws库将会关闭该链接;该处返回0时,表示ws连接已经建立成功。此时表1中的wsi对象和user对象已不为空,因此,此时可以对用户自定义对象user进行初始化处理。
LWS_CALLBACK_RECEIVE 表示WS服务端收到客户端发送过来的一帧完整数据,此时表1中的in表示收到的数据,len表示收到的数据长度。需要注意的是:指针in的回收、释放始终由LWS框架管理,只要出了回调函数,该空间就会被LWS框架回收。因此,开发者若想将接收的数据进行转发,则必须对该数据进行拷贝
LWS_CALLBACK_CLOSED ws连接已经断开 备注:不能在此释放内存空间,否则存在内存泄漏的风险!!!因为连接断开时,并不总是会回调LWS_CALLBACK_CLOSED的处理!
LWS_CALLBACK_WSI_DESTROY 正在销毁ws连接对象 表示libwebsockets框架即将销毁wsi对象。此时如果用户自定义对象中存在动态分配的空间,则需要在此时进行释放


重要函数:

状态值 功能
lws_send_pipe_choked 功能:判断ws连接是否阻塞 备注:如果ws连接阻塞,则返回1,否则返回0
lws_create_context 功能:初始化websocket服务,返回context 如果不为null 初始化成功
lws_client_connect_via_info 功能:建立websocket连接 ,返回wsi 如果不为null 连接成功
lws_write 功能:将数据发送给对端 备注:函数参数说明 wsi: ws连接对 buf: 需要发送数据的起始地址 返回:result > 0 成功,result = 0 失败
lws_callback_on_writable 将ws连接加入可写事件监听
lws_service 检查是否有新消息接收
lws_context_destroy 断开websocket服务


5.搭建简易服务端测试

进入到python服务器所在目录 运行下面命令 启动python服务器

python testServer.py 

这个py服务器比较简单,只能支持1024字节传输,超过这个长度会乱码,要测试超长字符,服务器还是要自己去搭建。

通过上面的步骤,就可以编译了下面是我编译好的源码及demo,有需要可以下载

https://github.com/honjane/buildLibWebSocket

demo使用步骤:

1.进入buildws目录,运行sh文件 生成.a文件,然后把生成的.a文件拷贝到websocket/src/main/jni/ 对应架构下 libcrypto.a libssl.a libwebsocket.a libz.a

2.使用build-ndk 编译 jni目录生成对应libwebsocket.so 怎么编译就不多说,生成的so文件会在libs和obj目录下,把这些生存的so拷贝到jniLibs/对应架构下目录

3.启动python服务器 ,连接服务器,发送消息

作者:tsdfk1455 发表于2017/4/8 16:55:00 原文链接
阅读:106 评论:0 查看评论

从头开始学 RecyclerView(六) LayoutManager

$
0
0

前言


在前面的文章中,每个示例,都使用了LayoutManager,毕竟它是RecyclerView不可缺少的一部分。

LayoutManager,顾名思义,就是『布局管理器』。

使用如下代码,设置RecyclerView的LayoutManager:

mRecyclerView.setLayoutManager(layoutManager);

 

已提供的LayoutManager

android.support.v7.widget.LinearLayoutManager
android.support.v7.widget.GridLayoutManager
android.support.v7.widget.StaggeredGridLayoutManager


LinearLayoutManager

线性 水平或垂直 布局

构造函数如下:

public LinearLayoutManager(Context context) {
    this(context, VERTICAL, false);
}

public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    setOrientation(orientation);
    setReverseLayout(reverseLayout);
    setAutoMeasureEnabled(true);
}

public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    ...
}

第1个中,内部使用了第2个。第3个是xml中配置时使用的。实现跟第2个的实现类似。这里就解释下第2个构造方法中的参数意义:
orientation —— 取值 LinearLayoutManager.HORIZONTAL,表示水平方向;取值 LinearLayoutManager.VERTICAL,表示垂直方向
reverseLayout —— 是否需要布局反转。true,表示需要:若是方向为HORIZONTAL,则内容会从右到左显示,滚动方向也是;同样,方向为VERTICAL时,则内容会从下向上显示,滚动方向也是

 

GridLayoutManager

网格布局。

构造函数如下:

public GridLayoutManager(Context context, int spanCount) {
    super(context);
    setSpanCount(spanCount);
}

public GridLayoutManager(Context context, int spanCount, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    setSpanCount(spanCount);
}

public GridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {…} //xml

由于GridLayoutManager 继承了 LinearLayoutManager,所以构建函数中的参数意思差不多。
主要说下参数 spanCount 意义:在方向为HORIZONTAL时,spanCount就表示有几行;在方向为VERTICAL时,spanCount就表示有几列

StaggeredGridLayoutManager

交错的网格布局。

构造函数如下:

public StaggeredGridLayoutManager(int spanCount, int orientation){
    mOrientation = orientation;
    setSpanCount(spanCount);
    setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
    mLayoutState = new LayoutState();
    createOrientationHelpers();
}
public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {…} //xml

StaggeredGridLayoutManager 继承了 GridLayoutManager。参数意义与GridLayoutManager类似。

要实现交错式,除了设定RV的layoutManger为StaggeredGridLayoutManager外,还要设置item的宽或高的尺寸。
当方向为HORIZONTAL时,spanCount表示总的行数,这时为item设置不一样的宽度,即有横向交错的感觉。
当方向为HORIZONTAL时,spanCount表示总的列数,这时为item设置不一样的高度,即有纵向交错的感觉。

如果只是对item设置LayoutParams,那么还需要相应的设置item的内容view的LayoutParams。所以如果可以,直接改变item内容view的LayoutParams即可

关于改变宽或高的示例代码:

@Override
public void bindCustomViewHolder(BaseHolder holder, int position) {
    holder.itemView.setFocusable(true);//加了这句,电视上就能滚动了

    TextView tvTitle = holder.getView(R.id.tv_title);
    tvTitle.setText(getItem(position));

    View vImg = holder.getView(R.id.v_img);
    vImg.setBackgroundColor(getColor());

    if (mIsStaggered) {
        float size = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics());//100dip转px
        int w = mOrientation == LinearLayoutManager.HORIZONTAL ? (int)size : -1;
        int h = mOrientation == LinearLayoutManager.HORIZONTAL ? -1 : (int)size;
        if (mOrientation == LinearLayoutManager.HORIZONTAL) {
            w = (int) (size + Math.random() * size);
        } else {
            h = (int) (size + Math.random() * size);
        }
//                    holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(w, h));
        vImg.setLayoutParams(new RelativeLayout.LayoutParams(w, h));
    }
}

注:由于这里设置成宽高随机值,所以每次重新滑动到开始位置时,都会重新布局。如果给一个定长就不会了:
w = (int)size;
if (position % 2 == 1) {
w = w / 2;
}

示例详情:https://github.com/aa86799/RecyclerView/tree/recycler-restart/

作者:jjwwmlp456 发表于2017/4/8 18:03:49 原文链接
阅读:68 评论:0 查看评论

Xcode中iOS项目目标变为Mac的解决办法

$
0
0

之前改过名称的一个项目,目标设备是iPhone,但是在另一台Mac上打开该工程后发现,竟然变为了Mac项目,而且无法编译和运行.

这里写图片描述

解决办法是打开Scheme管理

这里写图片描述

然后在当前Scheme基础上复制一个新的规划:

这里写图片描述

然后选取新的规划即可!

作者:mydo 发表于2017/4/8 19:39:43 原文链接
阅读:67 评论:0 查看评论

自定义控件三部曲视图篇(三)——瀑布流容器WaterFallLayout实现

$
0
0

前言:只要在前行,梦想就不再遥远

系列文章:

Android自定义控件三部曲文章索引:http://blog.csdn.net/harvic880925/article/details/50995268

前面两节讲解了有关ViewGroup的onMeasure、onLayout的知识,这节我们深入性地探讨一下,如何实现经常见到的瀑布流容器,本节将实现的效果图如下:

这里写图片描述

从效果图中可以看出这里要完成的几个功能:

1、图片随机添加
2、在添加图片时,总是将新图片插入到当前最短的列中
3、每个Item后,会弹出当前Item的索引

一、初步实现WaterFallLayout

1.1 自定义控件WaterFallLayout

首先,我们自定义一个派生自ViewGroup的控件WaterFallLayout,然后再定义几个变量:

public class WaterfallLayout extends ViewGroup {
    private int columns = 3;
    private int hSpace = 20;
    private int vSpace = 20;
    private int childWidth = 0;
    private int top[];

    public WaterfallLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        top = new int[colums];
    }

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

    public WaterfallLayout(Context context) {
        this(context, null);
    }
    …………
}

这里定义了几个变量:int columns用于指定当前的列数,这里指定的是三列;hSpace与vSpace用于指定每个图片间的水平间距和垂直间距。由于控件的宽度是一定的,当指定了列数以后,每个图片的宽度都是相同的,所以childWidth表示当前每个图片的宽度;由于每个图片的宽高比不同,所以他们的宽度相同,而高度则不同的,需要单独计算,也就没必要写成全局变量了。在开篇时,我们已经提到,我们需要把新增的图片放在容器最靠上的空白处,所以要有个top[columns]来保存当前每列的高度,以实时找到最短的高度的位置,将新增的图片放在那里。

1.2 设定onMeasure结果

通过前两篇我们知道对于ViewGroup而言onMeasure和onLayout的作用,onMeasure是告诉当前控件的父控件,它要占用的大小,以便让它的父控件给它预留。而onLayout则是布局ViewGroup中的各个元素用的。

所以首先,我们需要先计算出整个ViewGroup所要占据的大小,然后通过setMeasuredDimension()函数通知ViewGroup的父控件以预留位置,所以我们需要先求出控件所占的宽和高。

1.2.1 计算每个图片所占的宽度

所以,我们需要先求出来控件所占的宽度:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  int widthMode = MeasureSpec.getMode(widthMeasureSpec);
  int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
  measureChildren(widthMeasureSpec, heightMeasureSpec);

  childWidth = (sizeWidth - (columns - 1) * hSpace) / columns;
  …………
}  

首先,需要利用measureChildren(widthMeasureSpec, heightMeasureSpec);让每个子控件先测量自己,只有测量过自己之后,再调用子控件的getMeasuredWidth()才会有值,所以我们在派生自ViewGroup的控件在onMeasure的时候,一般都会首先调用measureChildren()函数,以防在用到子控件的getMeasuredWidth方法的时候没值。

然后,我们需要先求个每个子控件的宽度。根据widthMeasureSpec得到的sizeWidth,是父控件建议摆放的宽度,一般也就是我们最大摆放的宽度。所以,我们根据这个宽度求出在图片摆放三列的情况下,每个控件的宽度,公式就是:

childWidth = (sizeWidth - (columns - 1) * hSpace) / columns;

由于每个图片宽度相同,而且每两个图片间是有一定间距的,距离是hSpace;在columns列的情况下,有(columns - 1)个间距,因为每两个控件的间距是hSpace,所以总的间距就是(columns - 1) * hSpace;所以计算原理就是根据总宽度减去总间距得到的就是所有子控件的总宽度和,然后除以列数,就得到了每个item的宽度。

1.2.2 求得控件总宽度

然后我们就可以根据子控件的数量是不是超过设定的列数来得到总的宽度,由于我们设定的每行的有三列,所以,如果所有子控件数并没有超过三列,那么总的控件宽度就是当前个数子控件的宽度总和组成。如果子控件数超过了三个,那说明肯定能撑满一行了,宽度也就是父控件建议的sizeWidth宽度了

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ………………
    int wrapWidth;
    int childCount = getChildCount();
    if (childCount < columns) {
        wrapWidth = childCount * childWidth + (childCount - 1) * hSpace;
    } else {
        wrapWidth = sizeWidth;
    }
    …………
}   

1.2.3 求得控件总高度

在求得总宽度以后,我们要想办法得到控件的总高度,难点在于,我们在摆放控件时,总是先找到最短的列,然后把新的控件摆放在这列中,如:

这里写图片描述

很明显,在这张图片中,第三列是最短的,所以我们在新插入图片时,会放在第三列中。

这就需要我们有一个数组来标识每列在添加图片后的高度,以便在每次插入图片时,找到当前最短的列。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    …………

    clearTop();
    for (int i = 0; i < childCount; i++) {
        View child = this.getChildAt(i);
        int childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth();
        int minColum = getMinHeightColum();
        top[minColum] += vSpace + childHeight;
    }
    int wrapHeight;
    wrapHeight = getMaxHeight();
    setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? wrapWidth : sizeWidth, wrapHeight);
}    

首先,每次在计算高度之前,我们应该先把top[]数组清空,以防上次的数据影响这次的计算,clearTop()的实现为:

private void clearTop() {
    for (int i = 0; i < columns; i++) {
        top[i] = 0;
    }
}

然后就要开始计算每列的最大高度了,我们需要轮询每个控件,然后将每个控件按他所在的位置计算一遍,最后得到每列的最大高度。

for (int i = 0; i < childCount; i++) {
    View child = this.getChildAt(i);
    int childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth();
    int minColum = getMinHeightColum();
    top[minColum] += vSpace + childHeight;
}

首先得到当前要摆放控件的高度:int childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth(),因为我们每张图片要摆放的宽度都是相同的,所以我们需要将图片伸缩到指定的宽度,然后得到对应的高度,才是它所摆放的高度。

然后通过getMinHeightColum()得到top[]数组的最短的列,getMinHeightColum()的实现如下:

private int getMinHeightColum() {
    int minColum = 0;
    for (int i = 0; i < columns; i++) {
        if (top[i] < top[minColum]) {
            minColum = i;
        }
    }
    return minColum;
}

实现很简单,直接能top数组轮询,得到它的最短列的索引;

在得到最短列以后,将当前控件放在最短列中:top[minColum] += vSpace + childHeight;然后再计算下一个控件所在位置,并且放到当前的最短列中,当所有控件轮询结束以后,top[]数组中所保留的数据就是所有图片摆放完以后各列的高度。

最后,通过getMaxHeight()得到最长列的高度就是整个控件应有的高度值,然后通过setMeasuredDimension函数将计算得到的wrapWdith和wrapHeight提交给父控件即可:

int wrapHeight;
wrapHeight = getMaxHeight();
setMeasuredDimension(widthMode == MeasureSpec.AT_MOST ? wrapWidth : sizeWidth, wrapHeight);

1.3 onLayout摆放子控件

在了解onMeasure中如何计算当前图片所在的列之后,摆放就容易多了,只需要计算每个Item所在位置的left,top,right,bottom值,然后利用layout(left,top,right,bottom)函数将控件摆放在指定位置即可:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    clearTop();
    for (int i = 0; i < childCount; i++) {
        View child = this.getChildAt(i);
        int childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth();
        int minColum = getMinHeightColum();
        int tleft = minColum * (childWidth + hSpace);
        int ttop = top[minColum];
        int tright = tleft + childWidth;
        int tbottom = ttop + childHeight;
        top[minColum] += vSpace + childHeight;
        child.layout(tleft, ttop, tright, tbottom);
    }
}

同样是每个控件轮询,然后通过int childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth()得到当前要摆放图片的高度,然后根据int minColum = getMinHeightColum()得到最短的列,准备将这个控件摆放在这个列中。

下面就是根据要摆放的列的位置,得到要摆放图片的left,top,right,bottom值;其中top很容易得到,top[minColum]就是当前要摆放图片的top值,bottom也容易,加上图片的自身高度就是bottom值;稍微有点难度的地方是left值,因为通过getMinHeightColum()得到的是当前最短列的索引,因为索引是从0开始的,所以,假设我们当前最短的是第三列,所以通过getMinHeightColum()得到的值是2;因为每个图片都是由图片本身和中间的间距组成,所以当前控件的left值就是2*(childWidth + hSpace);在计算出left、top、right、bottom以后,通过child.layout函数将它们摆放在当前位置即可。最后更新top[minColum]的高度:top[minColum] += vSpace + childHeight;

到这里,有关测量和摆放就全部结束了,但是我们自定义的布局,应该对每个Item添加上点击响应,这是布局控件最基本的特性。

1.4 添加Item点击响应

对自定义的ViewGroup中的子控件添加点击响应是非常简单的,首先,我人需要自定义一个接口来回调控件被点击的事件:

public interface OnItemClickListener {
    void onItemClick(View v, int index);
}

然后轮询所有的子控件,并且在每个子控件在点击的时候,回调出去即可:

public void setOnItemClickListener(final OnItemClickListener listener) {
    for (int i = 0; i < getChildCount(); i++) {
        final int index = i;
        View view = getChildAt(i);
        view.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                listener.onItemClick(v, index);
            }
        });
    }
}

二、使用WaterFallLayout

在使用时,首先在XML中引入:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res/com.harvic.BlogWaterfallLayout"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent">

    <Button
            android:id="@+id/add_btn"
            android:layout_alignParentTop="true"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="随机添加图片"/>

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

        <com.harvic.BlogWaterfallLayout.WaterfallLayout
                android:id="@+id/waterfallLayout"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_below="@+id/add_btn"/>
    </ScrollView>
</LinearLayout>

因为WaterfallLayout是派生自ViewGroup的,所以当范围超出屏幕时,不会自带滚动,所以我们需要在外层包一个ScrollView来实现滚动。

然后在代码中,当点击按钮时,随便添加图片:

public class MyActivity extends Activity {
    private static int IMG_COUNT = 5;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        final WaterfallLayout waterfallLayout = ((WaterfallLayout)findViewById(R.id.waterfallLayout));
        findViewById(R.id.add_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addView(waterfallLayout);
            }
        });

    }
    …………
}    

其中addView的实现为:

public void addView(WaterfallLayout waterfallLayout) {
    Random random = new Random();
    Integer num = Math.abs(random.nextInt());
    WaterfallLayout.LayoutParams layoutParams = new WaterfallLayout.LayoutParams(WaterfallLayout.LayoutParams.WRAP_CONTENT,
        WaterfallLayout.LayoutParams.WRAP_CONTENT);
    ImageView imageView = new ImageView(this);
    if (num % IMG_COUNT == 0) {
        imageView.setImageResource(R.drawable.pic_1);
    } else if (num % IMG_COUNT == 1) {
        imageView.setImageResource(R.drawable.pic_2);
    } else if (num % IMG_COUNT == 2) {
        imageView.setImageResource(R.drawable.pic_3);
    } else if (num % IMG_COUNT == 3) {
        imageView.setImageResource(R.drawable.pic_4);
    } else if (num % IMG_COUNT == 4) {
        imageView.setImageResource(R.drawable.pic_5);
    }
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);

    waterfallLayout.addView(imageView, layoutParams);

    waterfallLayout.setOnItemClickListener(new com.harvic.BlogWaterfallLayout.WaterfallLayout.OnItemClickListener() {
        @Override
        public void onItemClick(View v, int index) {
            Toast.makeText(MyActivity.this, "item=" + index, Toast.LENGTH_SHORT).show();
        }
    });
}

代码很容易理解,首先随机生成一个数字,因为我们有五张图片,所以对生成的数字对图片数取余,然后指定一个图片资源,这样就实现了随机添加图片的效果,然后将ImageView添加到自定义控件waterfallLayout中,最后添加点击响应,在点击某个Item时,弹出这个Item的索引。

到这里,整个自定义控件部分和使用都讲完了,效果图就如开篇所示。

三、改进Waterfalllayout实现

从上面的实现中可以看出一个问题,就是需要在onMeasure和onLayout中都需要重新计算每列的高度,如果布局比较复杂的话,这种轮询的计算是非常耗性能的,而且onMeasure中已经计算过一次,我们如果在OnMeasure计算时,直接将每个item所在的位置保存起来,那么在onLayout中就可以直接使用了。

那么问题来了,怎么保存这些参数呢,难不成要生成一个具有数组来保存每个item的变量吗?利用数组来保存当然是一种解决方案,但并不是最优的,因为我们的item可能会有几千个,那当这个数组可能就已经非常占用内存,而且当数组很大的时候,存取也是比较耗费性能的。回想一下,我们在《自定义控件三部曲视图篇(一)——测量与布局》中在讲解MarginLayoutParams时,系统会把各个margin间距保存在MarginLayoutParams中:

public static class MarginLayoutParams extends ViewGroup.LayoutParams {
    public int leftMargin;
    public int topMargin;
    public int rightMargin;
    public int bottomMargin;
    …………
}

那方法来了,我们可不可以仿照MarginLayoutParams自定义一个LayoutParams,然后每次将计算后的left、top、right、bottom的值保存在这个自定义的LayoutParmas中,在布局的时候,取出来就可以了。

首先,我们自定义一个LayoutParams:

public static class WaterfallLayoutParams extends ViewGroup.LayoutParams {
      public int left = 0;
      public int top = 0;
      public int right = 0;
      public int bottom = 0;

      public WaterfallLayoutParams(Context arg0, AttributeSet arg1) {
          super(arg0, arg1);
      }

      public WaterfallLayoutParams(int arg0, int arg1) {
          super(arg0, arg1);
      }

      public WaterfallLayoutParams(android.view.ViewGroup.LayoutParams arg0) {
          super(arg0);
      }
}

这里相对原来的ViewGroup.LayoutParams,只添加几个变量来保存图片的各点位置。

然后仿照MarginLayoutParams的使用方法,重写generateLayoutParams()函数:

@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
  return new WaterfallLayoutParams(getContext(), attrs);
}

@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
  return new WaterfallLayoutParams(WaterfallLayoutParams.WRAP_CONTENT, WaterfallLayoutParams.WRAP_CONTENT);
}

@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
  return new WaterfallLayoutParams(p);
}

然后在onMeasure时,将代码进行修改,在计算每列高度的时候,同时计算出每个Item的位置保存在WaterfallLayoutParams中:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    …………

    clearTop();
    for (int i = 0; i < childCount; i++) {
        View child = this.getChildAt(i);
        int childHeight = child.getMeasuredHeight() * childWidth / child.getMeasuredWidth();
        int minColum = getMinHeightColum();

        WaterfallLayoutParams lParams = (WaterfallLayoutParams)child.getLayoutParams();
        lParams.left = minColum * (childWidth + hSpace);
        lParams.top = top[minColum];
        lParams.right = lParams.left + childWidth;
        lParams.bottom = lParams.top + childHeight;

        top[minColum] += vSpace + childHeight;
    }

    …………
}

然后在布局时,直接从布局参数中,取出来布局即可:

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        WaterfallLayoutParams lParams = (WaterfallLayoutParams)child.getLayoutParams();
        child.layout(lParams.left, lParams.top, lParams.right, lParams.bottom);
    }
}

万事具备之后,直接运行,发现在点击添加图片Item时,报了Crash:

AndroidRuntime: FATAL EXCEPTION: main 
java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to com.harvic.BlogWaterfallLayout.WaterfallLayoutImprove$WaterfallLayoutParams
at com.harvic.BlogWaterfallLayout.WaterfallLayoutImprove.onMeasure(WaterfallLayoutImprove.java:92)
at android.view.View.measure(View.java:15518)
at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1217)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
at android.widget.ScrollView.onMeasure(ScrollView.java:321)
at android.view.View.measure(View.java:15518)

奇怪了,明明仿照MarginLayoutParams来自定义的布局参数,为什么并没有生效呢?

这是因为在,自定义ViewGroup的布局参数时,需要重写另一个函数:

@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof WaterfallLayoutParams;
}

之所以需要重写checkLayoutParams,是因为在ViewGroup源码中在添加子控件时,有如下代码:

private void addViewInner(View child, int index, LayoutParams params,
        boolean preventRequestLayout) {

    …………
    if (!checkLayoutParams(params)) {
        params = generateLayoutParams(params);
    }
    …………
}    

很明显,当checkLayoutParams返回false时才会调用generateLayoutParams,checkLayoutParams的默认实现是:

protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return  p != null;
}

即ViewGroup.LayoutParams不为空,就不会再走generateLayoutParams(params)函数,也就没办法使用我们自定义LayoutParams。所以我们必须重写,当LayoutParams不是WaterfallLayoutParams时,就需要进入generateLayoutParams函数,以使用自定义布局参数。

到此,整个自定义控件就结束了,在讲了原理之后,还需要对自定义的控件进行封装,比如这里的列数和每两个图片间的间距都是需要用户指定的,所以我们需要在自定义控件属性来实现这些功能。这些都是自定义控件的一部分,以前写的一篇文章已经写过了,这里就不再重写了,想了解的同学,参考下: 《PullScrollView详解(一)——自定义控件属性》

如果本文有帮到你,记得加关注哦
源码下载地址:http://download.csdn.net/detail/harvic880925/9807928

转载请标明出处,http://blog.csdn.net/harvic880925/article/details/69787359谢谢

如果你喜欢我的文章,那么你将会更喜欢我的微信公众号,将定期推送博主最新文章与收集干货分享给大家(一周一次)
这里写图片描述

作者:harvic880925 发表于2017/4/8 21:02:49 原文链接
阅读:96 评论:0 查看评论

Android ShareSDKQQ 第三方登录so easy?

$
0
0

昨天群里有个群友看到我之前做的那个qq第三方登录怎么做的,于是乎思考了一下,还是决定写一篇博客记录下,其实都不难的,其实之前我又写到FaceBook的第三方登录不知道看下这Android集成FaceBook登入《-》 今天只举例QQ登录其他像微信大同小异需要微信工具签名然后md5+包名等。

开车啦

这里写图片描述

准备工作需要下载ShareSDK这里我不再赘述不知道的看我的这篇文章ShareSDK社会化分享之那些年我们踩过的坑我这里不再赘述因为很多步骤是重复的。

然后在项目工程中assets文件下修改ShareSDK.xml文件更改您的Appkey

这里写图片描述

然后QQ第三方登录准备的Jar点击直接下载opensdk
然后解压将jar放到工程中的libs中如下图所示

这里写图片描述

1、申请appid和appkey的用途

appid :应用的唯一标识。在OAuth2.0认证过程中,appid的值即为oauth_consumer_key的值。

appkey:appid对应的密钥,访问用户资源时用来验证应用的合法性。在OAuth2.0认证过程中,appkey的值即为oauth_consumer_secret的值。

这里写图片描述

2、配置清单文件AndroidMainfest.xml
2.1添加相关权限

<!-- 添加权限 -->
 <uses-permission android:name="android.permission.GET_TASKS" />
 <uses-permission android:name="android.permission.INTERNET" />
 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
 <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
 <!-- 蓝牙分享所需的权限 -->
  <uses-permission android:name="android.permission.BLUETOOTH" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
            <activity
            android:name="com.mob.tools.MobUIShell"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:theme="@android:style/Theme.Translucent.NoTitleBar"
            android:windowSoftInputMode="stateHidden|adjustResize">

2.2微信授权回调

<!--微信分享回调 -->
           <activity
            android:name=".wxapi.WXEntryActivity"
            android:configChanges="keyboardHidden|orientation|screenSize"
            android:exported="true"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />

2.3注册Activity

  <!-- 注册SDKActivity -->
           <activity
            android:name="com.tencent.tauth.AuthActivity"
            android:launchMode="singleTask"
            android:noHistory="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data android:scheme="tencent1105658914" /> <!-- 开放平台获取的APPID -->
            </intent-filter>
        </activity>
         <activity
            android:name="com.tencent.connect.common.AssistActivity"
            android:screenOrientation="portrait"
            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
        <activity android:name=".bdmap.BMapActivity" />

3、代码简单实现

声明变量

    private Tencent mTencent;
    private BaseUiListener mIUiListener;
    private UserInfo mUserInfo;
    private CallbackManager  mCallBackManager;

SDK初始化这个大多数都会有需要注意的这里是传入getApplicationContext

  mTencent=Tencent.createInstance(AppConstant.APP_ID,LoginActivity.this.getApplicationContext());

3.1 QQ登录授权接口回调


    public void QQLogin(View v) {
        mIUiListener = new BaseUiListener();
        //all表示获取所有权限
        mTencent.login(LoginActivity.this,"all", mIUiListener);
        //授权成功后跳转到引导页面
        startActivity(new Intent(LoginActivity.this,WelcomeGuideActivity.class));
    }

3.2自定义监听器实现IUiListener接口后,需要实现的3个方法 onComplete完成 onError错误 onCancel取消

    /**
     * 自定义监听器实现IUiListener接口后,需要实现的3个方法
     * onComplete完成 onError错误 onCancel取消
     */
    private class BaseUiListener implements IUiListener{

        @Override
        public void onComplete(Object response) {
            Toast.makeText(LoginActivity.this, "授权成功", Toast.LENGTH_SHORT).show();
            Log.e(TAG, "response:" + response);
            JSONObject obj = (JSONObject) response;
            try {
                String openID = obj.getString("openid");
                String accessToken = obj.getString("access_token");
                String expires = obj.getString("expires_in");
                mTencent.setOpenId(openID);
                mTencent.setAccessToken(accessToken,expires);
                QQToken qqToken = mTencent.getQQToken();
                mUserInfo = new UserInfo(getApplicationContext(),qqToken);
                mUserInfo.getUserInfo(new IUiListener() {
                    @Override
                    public void onComplete(Object response) {
                        Log.e(TAG,"登录成功"+response.toString());
                    }

                    @Override
                    public void onError(UiError uiError) {
                        Log.e(TAG,"登录失败"+uiError.toString());
                    }

                    @Override
                    public void onCancel() {
                        Log.e(TAG,"登录取消");

                    }
                });
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onError(UiError uiError) {
            Toast.makeText(LoginActivity.this, "授权失败", Toast.LENGTH_SHORT).show();

        }

        @Override
        public void onCancel() {
            Toast.makeText(LoginActivity.this, "授权取消", Toast.LENGTH_SHORT).show();

        }


    }

3.3在调用Login的Activity或者Fragment中重写onActivityResult方法

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == Constants.REQUEST_LOGIN){
            Tencent.onActivityResultData(requestCode,resultCode,data,mIUiListener);
        }
               super.onActivityResult(requestCode, resultCode, data);
    }

总结:

整个流程如下图所示

这里写图片描述

效果如下图所示360高清无码正在加载中别眨眼!为了做个这东西把自己QQ都卖了我容易吗?我没办法老司机!
这里写图片描述

转载请注明出处同时欢迎大家加我的群号,欢迎开车,自己可以去尝试做一下我这里可能也不是很完整,最好建议大家去管网看文档!应该是没问题的!我这人有一点不好不喜欢拖拉!喜欢的事情就去做!因为年轻!因为任性!因为代码是敲出来的!还记得我有个群友代码9.99评分豆瓣棒棒哒!来个合照疯狂Android进阶之旅

作者:qq_15950325 发表于2017/4/8 10:39:34 原文链接
阅读:1005 评论:0 查看评论

Shader2D: 一些2D效果的Shader实现

$
0
0

刚刚开源了自己积累的一些2D效果的Shader实现,项目地址。效果在下面列出,我使用的Unity版本是5.3.5p8,可用不低于此版本的unity打开查看。需要注意的是,我的实现初衷在于原理的理解,并未斟酌优化,如果项目中使用请考虑优化。本文会不定期更新,添加新研究的效果。后面如果有时间,我可能会开一系列博客详细写写每个效果的原理和实现细节,欢迎朋友和我一起讨论。(P.S. 如果对你有帮助,别忘了点github右上角的star,谢谢!)

  • Blur 效果: 模糊 原理: 采样附近上下左右四个相邻像素的颜色,与当前像素颜色按比例混合(简单滤波)
    Blur
    原图(左)                               模糊效果图(右)
  • BlurBox 效果: box模糊 原理: 采样周边8个相邻像素的颜色,与当前像素颜色按平均比例混合(Box滤波器)
  • BlurGauss 效果: 高斯模糊 原理: 采样周边8个相邻像素的颜色,与当前像素颜色按比例混合(高斯滤波器)
  • Sharpen 效果: 拉普拉斯锐化 原理: 先将自身与周围的8个象素相减,表示自身与周围象素的差别,再将这个差别加上自身作为新象素的颜色
    BlurSharpen
    原图(左上)、Laplace锐化(右上)、模糊-Box过滤器(左下)、模糊-高斯过滤器(右下)

  • CircleHole 效果: 圆形遮挡过场动画 原理: 圆形遮盖随时间缩小,用于过场动画
    CircleHole

  • EarthRotate 效果: 地球旋转动画 原理: 天空盒,UV动画。这个漂亮的实现来自风宇冲的blog
    http://blog.sina.com.cn/lsy835375
    Earth

  • Emboss 效果: 浮雕 原理: 图像的前景前向凸出背景。把象素和左上方的象素进行求差运算,并加上一个灰度(背景)。
    Emboss
    原图(左)、浮雕效果(右)

  • Pencil 效果: 铅笔画描边 原理: 如果在图像的边缘处,灰度值肯定经过一个跳跃,我们可以计算出这个跳跃,并对这个值进行一些处理,来得到边缘浓黑的描边效果,就像铅笔画一样。
    Pencil
    原图(左)、铅笔画(右)

  • Fade 效果: 渐隐 原理: 根据距离渐隐渐现
    Fade

  • Flash 效果: 闪光特效 原理: 叠加平行四边形亮光带,随时间运动划过图片,就像一束光带飘过
    Flash

  • Gray 效果: 灰化 原理: 0.3*R, 0.59*G, 0.11*B
    Gray
    原图(左)、灰化(右)

  • OldPhoto 效果: 老照片 原理: r = 0.393*r + 0.769*g + 0.189*b; g = 0.349*r + 0.686*g + 0.168*b; b = 0.272*r + 0.534*g + 0.131*b;
    OldPhoto
    原图(左)、旧照片效果图(右)

  • HexagonClip 效果: 正六边形裁剪 原理:
    HexagonClip
    原图(左)、正六边形裁剪(右)

  • Mosaic 效果: 马赛克 原理: n x n方块内取同一颜色
    Mosaic
    原图(左)、马赛克效果图(右)

  • InnerGlow 效果: 内发光 原理: 采样周边像素alpha取平均值,叠加发光效果
  • OutterGlow 效果: 外发光 原理: 采样周边像素alpha取平均值,给外部加发光效果(1-col.a可避免内部发光)
    Glow
    内发光、外发光
  • RoundRect 效果: 圆角 原理: 最简单的笨方法,效率差
  • RoundCorner 效果: 同上 原理: 比较巧妙的算法,效率高。详见:
    http://www.cnblogs.com/jqm304775992/p/4987793.html
    RoundRect
    原图(左)、圆角1(中)、圆角2(右)

  • Saturation 效果: 调整饱和度 原理: RGB转HSL,增加S再转回RGB
    Saturation
    原图(左)、提高饱和度(右)

  • SectorWarp 效果: 扇形映射 原理: 采样图片上的点,映射到一个扇形区域中
    SectorWarp
    原图(左)、扇形映射(右)

  • SeqAnimate 效果: 序列帧动画 原理: 从mxn的动画图片中扣出当前帧动作图
    SeqAnimate

  • Shutter 效果: 百叶窗 原理: 划定窗页宽度,2张纹理间隔采样
    Shutter

  • Twirl 效果: 旋转效果 原理: 旋转纹理UV坐标,越靠近中心旋转角度越大,越往外越小
  • TwirlEffect 效果: 旋转效果 原理: 旋转纹理UV坐标。相比上一个,这个没有根据距离调整角度,并且演示了屏幕后处理特效
    Twirl
    原图(左)、旋转(右)

  • Vortex 效果: 旋涡效果 原理: 旋转纹理UV坐标。相比Twirl,离中心越远,旋转角度越大。
    Vortex
    原图(左)、旋涡效果(右)

  • HDR 效果: HDR效果 原理: 让亮的地方更亮,同时为了过渡更平滑柔和,亮度采用高斯模糊后的亮度(灰度值)
    HDR
    原图(上)、HDR效果(下)

  • WaterColor 效果: 水彩画 原理: 随机采样周围颜色,模拟颜色扩散;然后把RGB由原来的8位量化为更低位,这样颜色的过渡就会显得不那么的平滑,呈现色块效果。
    WaterColor
    原图(左)、水彩画效果(右)

  • Wave 效果: 波浪效果 原理: 让顶点的Y轴按正弦或余弦变化。
    Wave

  • WaterRipple 效果: 水滴波动效果 原理: 正弦波,越远波长越长,振幅越小。
    WaterRipple
    原图(左)、水滴波动效果(右)


参考

  1. Java Image Filters http://www.jhlabs.com/index.html 一款基于Java的图像处理类库,在图像滤镜特效方面,非常强大,几乎提供了PS上大部分的图像特效,比如反色、扭曲、水波等效果。本文一些效果的算法参考了此项目。

  2. 数字图像处理 随便一本高校用的教材即可。

  3. 其它一些参考已经在具体效果的原理中列出。如有遗漏请指出,谢谢。

作者:ynnmnm 发表于2017/4/9 1:59:37 原文链接
阅读:284 评论:0 查看评论

Xcode项目横竖屏切换控件元素无法响应用户操作的原因及解决

$
0
0

一个简单的iOS游戏,适配横屏和竖屏.但是窗口最底下的switch按钮在竖屏时表现正常,但是在横屏时虽然可以看到,但无法响应用户的点击.

在其上绑定用户Action,还是无法触发!

这里写图片描述

因为switch按钮和其左侧的说明label都放在一个view中以便于做layout限制,所以我们可以不用实际再运行App,而是利用Xcode8.x的界面自动演示来检查实际在横屏时到底发生了神马:

这里写图片描述

原来由于我设置了layout限制,导致在横屏时view的高度被压缩为0,所以其中包含的子元素自然无法得到用户响应了.

解决办法也很简单,就是给view加上高度和宽度限制即可:

这里写图片描述

然后运行App,基本满足需要了:

这里写图片描述

作者:mydo 发表于2017/4/9 9:56:55 原文链接
阅读:150 评论:0 查看评论

Android Studio创建AIDL文件并实现进程间通讯

$
0
0

在Android系统中,跨进程通信是非常普遍的事情,它用到了Binder机制处理进程之间的交互。Binder机制会开放一些接口给java层,供android开发工程师调用进程之间通信。这些接口android封装到了AIDL文件里,当我们项目用到跨进程通信时可以创建.aidl文件,.aidl文件可以协助我们达到跨进程的通信。下面简单介绍用AndroidStudio创建AIDL文件的过程。

a.新建AIDL文件

1.项目文件夹右键---> new --->选择AIDL



2.自定义一个接口名称



3.创建之后我们看到了xxx.aidl文件,然后编辑自己项目需要实现的方法,这里很简单就获取一个字符串的方法getAllName。



4.写好之后,我们需要重新ReBuild,完后在项目build/generated/source/aidl/debug/包名 目录下就看到了系统为我们生成的以刚才.aidl文件名命名的java文件。



该java文件系统会自动生成代码:

Stub:描述了一个Java服务,对应是一个远程的Service。

Proxy:描述了一个Java服务的代理对象,在Client端就会得到这个对象。

这两者都实现了IPersonManager接口。

asInterface:将Java服务的代理对象即一个BinderProxy封装成了一个IPersonManager.Stub.Proxy对象,实现了IPersonManager接口。

onTransact:负责接收分发进程间的通信。它首先会收到Client发来的请求,不同的方法进入相应的case代码中,然后交给Stub的子类去处理事件,例如 java.lang.String _result = this.getAllName();   这里的this就可以让它的子类去接收该请求并处理。

IBinder的transact方法:用来发送进程间的请求。


b.利用AIDL实现进程间的通讯

一:接口文件中只含有基础数据类型

如上aidl文件,IPersonManager中只用到了基本数据类型,此时要完善Server端的小项目,还需要新建一个Service。

Server端代码如下

public class PersonService extends Service {

    private static String names = "alice & iland";
    public PersonBinder mPersonBinder;
    @Override
    public void onCreate() {
        super.onCreate();
        mPersonBinder = new PersonBinder();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mPersonBinder;
    }
    public class PersonBinder extends IPersonManager.Stub{

        @Override
        public String getAllName() throws RemoteException {
            return names;
        }
    }

}

继承系统的Service,并建立一个内部类继承IPersonManager.Stub,这里很简单,当客户端请求要获取名字时我们这里把names给到客户端。


Client端代码如下

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = "MainActivity";
    private Button btnGet;
    private EditText etShow;
    public IPersonManager mIPersonManager;
    ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            mIPersonManager = IPersonManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: ");
            mIPersonManager = null;
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnGet = (Button) findViewById(R.id.btn_getname);
        etShow = (EditText) findViewById(R.id.et_allnamef);
        btnGet.setOnClickListener(this);

        Intent intent = new Intent("com.ly.testaidlserver.aidl.AIDL_SERVICE");
        intent.setPackage("com.ly.testaidlserver");
        bindService(intent,sc, Service.BIND_AUTO_CREATE);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_getname:
                String names = null;
                try {
                    if (mIPersonManager!=null)
                        names = mIPersonManager.getAllName();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                etShow.setText(names);
                break;
            default:
                break;
        }
    }@Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(sc);
    }

}

在onServiceConnected方法中拿到IPersonManager的代理对象,最终获取到 alice & ilan,与服务端数据一致。


注意:

1.bindService方法在5.0以后做出改变,隐式意图需要设置Package 或者 Commponent,直接定义一个action是报异常的。

        Intent intent = new Intent("com.ly.testaidlserver.aidl.AIDL_SERVICE");
        intent.setPackage("com.ly.testaidlserver");
        bindService(intent,sc, Service.BIND_AUTO_CREATE);

2.我们需要把Server端的aidl文件复制到Client端,在Client中存放aidl的文件夹也需要跟Server端包名一致。

如图:


上图为aidl文件在Server端存放的路径,下图为复制到Client端aidl文件的路径,这里要保持一致,因此Client端需要针对Server端的包名新建一个Package。

3.当我们启动项目的时候,如果在Activity中IPersonManager找不到报出异常,请在app的build.gradle中添加aidl文件指名目录,如本例中添加,

    sourceSets{
        main {
            aidl.srcDirs = ['src/main/aidl','src/main/java']
        }
    }


二:接口文件中含有复杂数据类型、

1.新建一个Person.aidl     内容非常简单

parcelable Person;

2.新建一个Person实体类,为了能在进程间进行通信必须实现Parcelable接口。

3.在IPersonManager中添加了一个方法,这里注意用到的Person类必须将包名improt进去。

4.将IPersonManager.aidl、Person.aidl、Person.java复制到客户端的aidl包下。

5.查看是否需要修改build.gradle中sourceSets设置


代码基本没有变化:


Person.java

public class Person implements Parcelable {
    String name;
    int age;

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

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in.readString(), in.readInt());
        }

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

IPersonManager.aidl

interface IPersonManager {
   String getAllName();
   List<Person> getPersonList();
}


Server

public class PersonService extends Service {

    private List<Person> persons = new ArrayList<Person>();
    public PersonBinder mPersonBinder;
    @Override
    public void onCreate() {
        super.onCreate();
        mPersonBinder = new PersonBinder();
        Person p1 = new Person("alice",23);
        persons.add(p1);
        Person p2 = new Person("iland",18);
        persons.add(p2);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mPersonBinder;
    }
    public class PersonBinder extends IPersonManager.Stub{

        @Override
        public String getAllName() throws RemoteException {
            return "";
        }

        @Override
        public List<Person> getPersonList() throws RemoteException {
            return persons;
        }
    }
}


Clent

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    private static final String TAG = "MainActivity";
    private Button btnGet;
    private EditText etShow;
    public IPersonManager mIPersonManager;
    ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected: ");
            mIPersonManager = IPersonManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected: ");
            mIPersonManager = null;
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnGet = (Button) findViewById(R.id.btn_getname);
        etShow = (EditText) findViewById(R.id.et_allnamef);
        btnGet.setOnClickListener(this);

        Intent intent = new Intent("com.ly.testaidlserver.aidl.AIDL_SERVICE");
        intent.setPackage("com.ly.testaidlserver");
        bindService(intent,sc, Service.BIND_AUTO_CREATE);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_getname:
                ArrayList<Person> persons = null;
                try {
                    if (mIPersonManager!=null)
                        persons = (ArrayList<Person>) mIPersonManager.getPersonList();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                String result = "";
                for (Person person : persons){
                    result = result+person.getName()+"__"+person.getAge();
                }
                etShow.setText(result);
                break;
            default:
                break;
        }
    }@Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(sc);
    }

}









作者:qq_36713816 发表于2017/4/9 16:36:44 原文链接
阅读:78 评论:0 查看评论

GreenDAO之「04.GreenDao的各种查询方法」

$
0
0

程序员之所以犯错误,不是因为他们不懂,而是因为他们自以为什么都懂(来自csdn首页)

GreenDAO之「02.Eclipse环境下的基本操作」GreenDAO之「03.AS环境下的基本操作」两篇文章中,我们一起学习了greendao的一些不同环境下的基本操作

那么,这篇文章让我们一起学习一下greendao的各种查询方法

首先,我们看一下greendao四种基本查询方法:

  1. list()
  2. listLazy()
  3. listLazyUncached()
  4. listIterator()

下面我们分别来看一下:

1.list()

其实,我们的list()方法在前两篇文章中我们已经使用了它查询我们添加到数据库中的方法,这里就不贴代码了
它是将所有实体载入内存,以ArrayList形式返回,使用起来也是最简单的

2.listLazy()

下面我们还是使用前两篇文章所写的代码来演示一下listLazy()方法是如何使用的
首先我们将addPerson()方法先注释掉,因为我们的代码已经运行过一次,数据也都插入到数据库当中了
然后我们再新建一个querData()方法
在queryData()方法内部加入以下代码:

LazyList<Son> sonList = sonDao.queryBuilder().listLazy();
for (Son son : sonList) {
        Log.d("MAIN_TAG", "queryAll() called" + son);
                        }
sonList.close();

我们可以看到我们调用listLazy()方法返回了一个LazyList类型的对象
然后打印查询结果的方法是与list()方法相同的
其实listLazy()要求实体按需加载到内存。当列表中的其中一个元素第一次被访问,他会被加载并缓存备用,这里我们并没有体会到,这里我们先学习一下他是如何使用的
然后我们应该注意到listLazy()方法使用完之后是必须需要使用close()方法关闭的

3.listLazyUncached()

这个方法使用起来是与listLazy()方法无异的,我们只需要将调用的listLazy()方法修改为listLazyUncached()方法:

LazyList<Son> sonList = sonDao.queryBuilder().listLazyUncached();
for (Son son : sonList) {
        Log.d("MAIN_TAG", "queryAll() called" + son);
                        }
sonList.close();

其实,listLazyUncached()方法是创建一个虚拟的实体列表,任何访问列表中的元素都会从数据库中读取
当然,它使用完也是需要关闭的

4.listIterator()

listIterator()方法使用起来就与前面介绍的方法不同了:

Iterator<Son> sonList = sonDao.queryBuilder().listIterator();
while (sonList.hasNext()) {
     Son son = sonList.next();
     Log.d("MAIN_TAG", "queryAll() called" + son);
                          }

首先,它返回的是一个Iterator对象,所以我们需要使用一个Iterator对象去接收
然后它在输出数据的时候需要调用一个类似游标的方法
我们使用while循环来遍历sonList中的所有数据,然后打印每一个数据
其实,它是可迭代访问结果集,按需加载数据,数据不缓存

5.查看sql语句的拼装

当我们想看一下greendao到底是如何拼装sql数据的时候
greendao也为我们提供了一种方式
首先,我们在onCreate方法的内部添加两个常量:

QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;

然后我们直接运行程序,我们在控制台搜索greenDAO标签下的打印数据,我们就可以看到:

这里写图片描述

我们可以看到SELECT T.”NAME”,T.”AGE”,T.”_id”,T.”FATHER_ID” FROM “SON” T 这样一条sql语句

到此,我们greendao几种查询方法学习完成了!

但是感到非常抱歉,这篇文章讲解的内容比较抽象,个人能力有限,但我会在后期将自己的一些理解更新到这篇文章当中!大家也可以先查看一下greenDao官方文档中的一些介绍

接下来的文章,我们将一起学习一下GreenDAO的条件查询,一起期待吧!


往期回顾:
GreenDAO之「01.初始GreenDAO」
GreenDAO之「02.Eclipse环境下的基本操作」
GreenDAO之「03.AS环境下的基本操作」

彩蛋!!

看到这里的朋友你有福利了!我的个人微信公众号上线了!众多热门技术文章,众多有趣好玩的脑洞,请扫描下方二维码关注!

二维码
程序猿干货分享,欢迎您的到来!

作者:qq_34358104 发表于2017/4/9 16:47:49 原文链接
阅读:97 评论:0 查看评论

GreenDAO之「05.条件查询」

$
0
0

代码是最为耐心、最能忍耐和最令人愉快的伙伴,在任何艰难困苦的时刻,它都不会抛弃你(来自csdn首页)

这篇博文我们一起学习一下使用greendao如何进行条件查询,同样我们这里使用实例来演示一下

1.准备工作

我们的代码仍然使用前面的文章用到的GreendaoDemo项目
利用我们前面学到的知识,我们为数据库添加一下三条数据:
Father表:

姓名 年龄
James 45
Tom 60
Jet 40

Son表:

姓名 年龄 fatherId
小猿 20 fatherDao.insert(father0)
小明 28 fatherDao.insert(father1)
小明子 15 fatherDao.insert(father2)

到此,准备工作完成!

2.eq 和 noteq 和 like 查询

1.首先,第一种条件查询我们使用到的是一个eq方法,我们封装了一个queryEq方法,先看代码:

private void queryEq() {
    Son mingEq = sonDao.queryBuilder().where(SonDao.Properties.Name.eq("小明")).unique();
    Log.d("mingEq", "queryEq() called" + mingEq);
}

然后我们运行,通过在logcat中搜索mingEq标签,我们就可以看到,控制台只打印了小明的数据
其实这个参数就是指定某一数据进行查找
它返回的是一个Son型的数据
通过where关键字判断Name为小明来查找到小明的信息
eq方法为具体查找

2.我们再来看一下noteq方法,我们封装了一个querynoteq方法,代码如下:

private void queryNotEq() {
    List mingNotEq = sonDao.queryBuilder().where(SonDao.Properties.Age.notEq(15)).list();
    Log.d("mingNotEq", "queryNotEq() called" + mingNotEq);
}

我们运行代码,通过搜索mingNotEq标签,我们可以看到小猿和小明的数据
我们可以看到与eq不同,notEq方法返回的数据时一个list

3.我们再来看一下like方法,我们封装了一个queryLike方法,看一下代码:

private void queryLike() {
    List mingLike = sonDao.queryBuilder().where(SonDao.Properties.Name.like("小明%")).list();
    Log.d("mingLike", "queryLike() called" + mingLike);
}

好,我们运行代码,通过搜索mingLike标签,我们就可以看到小明和小明子的数据
为什么呢,其实,like方法就是搜索一个通配符
所以只要含有小明的数据,都会搜索出来了
我们知道这是相当于一个模糊搜索,并不是返回一条数据,而是一个列表了

3. >、<、>=、<= 查询

我们看一下使用“>”查找的代码:
private void queryGt() {
List mingGt = sonDao.queryBuilder().where(SonDao.Properties.Age.gt(18)).list();
Log.d(“mingGt”, “queryGt() called” + mingGt);
}
通过logcat我们可以看到我们搜索的是年龄大于18的数据
这是因为我们调用了gt方法,gt方法就是实现的“>”的条件查询

其实标题中的四种条件对应的就是四个方法
1. > : gt
2. < : lt
3. >= : ge
4. <= : le

这里我们不一一演示了,可以自己敲代码感受一下!

4. isNull 和 isNotNull 查询

这两个查询就是查询数据库中有无数据的
使用方法与上文的查询方法类似,大家可以自己体验一下!
这里不再演示

5. 排序

我们重点来学习一下对数据中的数据升序或降序排列并打印出来

首先我们实现数据的升序排列,我们封装了一个queryAsc的方法:
private void queryAsc() {
List data0 = sonDao.queryBuilder().orderAsc(SonDao.Properties.Age).list();
Log.d(“data0”,”queryAsc() called”+data0);
我们运行,搜索data0标签,可以看到数据的打印顺序是小明子、小猿、小明
我们通过orderAsc方法就轻松实现了数据的升序排列!

降序排列呢,我们可以将orderAsc方法替换为orderDesc就可以实现了!

6. 多线程查询

当我们的数据库非常庞大的时候
我们知道在主线程中查询数据时非常耗时的
所以我们这需要把查询放到子线程中
下面我们封装一个queryThread方法:

private void queryThread() {

    final Query query = sonDao.queryBuilder().build();
    new Thread(){
        @Override
        public void run() {
            List list = query.list();
            Log.d("queryThread", "run() called" + list);
        }
    }.start();
}

我们运行程序,发现程序崩溃了
其实,greendao框架呢是不允许多线程查询操作这样使用的
他为我们提供了forCurrentThread方法
我们可以这样:
private void queryThread() {

    final Query query = sonDao.queryBuilder().build();
    new Thread(){
        @Override
        public void run() {
            List list = query.forCurrentThread().list();
            Log.d("queryThread", "run() called" + list);
        }
    }.start();
}

其实,这是greendao为提升效率,为我们提供的一种优化方法
当我们使用子线程查询操作时,这样的方法是更好的
具体原因可以看一下greendao的源代码,仔细研究一下!

到此,我们greendao条件查询的学习就完成了!

好,关于GreenDAO的学习就基本结束了


往期回顾:
GreenDAO之「01.初始GreenDAO」
GreenDAO之「02.Eclipse环境下的基本操作」
GreenDAO之「03.AS环境下的基本操作」
GreenDAO之「04.greendao的各种查询方法」

彩蛋!!

看到这里的朋友你有福利了!我的个人微信公众号上线了!众多热门技术文章,众多有趣好玩的脑洞,请扫描下方二维码关注!

二维码
程序猿干货分享,欢迎您的到来!

作者:qq_34358104 发表于2017/4/9 16:49:42 原文链接
阅读:56 评论:0 查看评论

HTTPS-老司机手把手教你SSL证书申购-TrustAsia证书

$
0
0

前言

Apple从2016年逐步要求HTTPS,SSL相关证书等,上月的JSPatch封杀更是引起广大开发者的注意,整体来说多是为了安全考虑,那么SSL证书是硬需,考虑到上一篇:HTTPS时代已来,老司机手把手指导申请免费SSL证书 介绍了阿里云的相关证书,为了不仅仅依赖一家证书,特此又研究了一下又拍云的SSL-TrustAsia证书申购申购地址,希望能帮助到你!

第一步: 绑定域名并解析域名

创建服务-添加域名-解析域名

提示:记得选择全网加速服务,不要问为什么,因为它免费,也方便绑定域名与解析

提示1:解析的域名最好是备案好的,不然后期证书可能会导致失败,现在所有的国内域名都严格要求起来了,包括阿里云,万网,新网,主机屋等等等等,总之名不正言不顺,备案一下一劳永逸,这里我测试的是我已备案好的

提示2:添加过域名记得及时解析CNAME操作,不然可能影响后期证书,最近收到又拍云升级通知:

第二步: 证书申购(免费)

申购的是免费版的TrustAsia证书(苹果认证可放心使用)

第三步: 证书配置

选择TrustAsia证书,其他不用操作,接着下一步

第四步: 补全资料

填入第一步绑定的域名,由于第一步已解析域名证明了域名所有权,直接点击提交即可!

第五部:审核结果查看

审核通过的如下:

温馨提示:又拍云免费SSL证书申购是通过第三方提交审核的,所以官网不受控制,甚至由于解析错误未能审核通过状态也不显示和邮件提醒,所以特此提醒开发者,正常审核周期是一个工作日,若你的申购一直处于待审核,原因可能有两点:1.解析域名时,顶级域名和带www的域名解析是要和又拍云保持一致的;2.未绑定解析域名,或者节假日延后,更多问题可以咨询官方客服。

第六步: 证书的管理

上一篇:HTTPS时代已来,老司机手把手指导申请免费SSL证书

更多:每周更新关注新浪微博! 手机加iOS开发者交流QQ群: 446310206

作者:qq_31810357 发表于2017/4/9 20:45:43 原文链接
阅读:45 评论:0 查看评论

Android设计模式(二十二)-外观模式

$
0
0

外观模式猛一听有点蒙逼,但是在开发中我们应该都用过,只是没这个概念罢了。

比如在开发时通常会把图片加载框架和网络框架进行封装,封装到最后只暴露出来一个最上级的类供外部调用,外部调用这一个类提供的方法,然后这个类内部具体调用了什么,用的什么逻辑等等外部都不用管。这样也方便后期随便更换图片加载框架和网络框架,而业务代码不用做任何改动。

这其实就是外观模式的一种实现。

定义

要求子系统的外部与其内部的通讯必须通过一个统一的对象进行。提供一个高层次接口,使得子系统更易于使用,

使用场景

  • 为一个复杂的子系统 提供一个简单的接口。子系统可能因为不断演化而变得越来越复杂,甚至可能被替换,就像上面提到的封装的框架。这种模式能让子系统有更高的独立性,对外隔离了子系统的变化。
  • 当需要构建一个层次结构的子系统是,可以用这个模式定义每一层的接口,使各个层次之间的耦合度降低。

UML

  • Facade: 系统对外的统一接口,系统内部系统地工作。
  • SubSystemA,B,C,D: 子系统。

比如客户端要用子系统A和B一起来完成一个操作,又要用B和C和D完成一个操作,那么就需要同事依赖着四个类。子系统有了一个门面之后,客户端就可以只依赖这个门面,调用他的方法。这个门面内部会调度各个子系统来完成协调工作。

简单实现

这里以一个手机为例。然后再更精简一点。手机可以看成是一个系统的facade,他继承了电话,上网,摄像头功能。当我们需要视频通话时,只需要调用手机的视频通话功能就行,通话结束后直接调用挂机就行。因为手机已经集成了这些功能,手机内部会调用各个系统来完成这个操作。

想象一下如果没有手机的封装,我们视频通话的操作可能就是:打开摄像头–上网–通话。然后挂断就要手动断掉通话,然后手动关掉摄像头。

先看电话功能模块:

public interface Phone {
     void dail();
     void hangup();
}

public class PhoneImpl implements Phone {
    @Override
    public void dail() {
        System.out.println("打电话");
    }

    @Override
    public void hangup() {
        System.out.println("挂断");
    }
}

摄像头模块:

public interface Camera {
    void open();
    void takePicture();
    void close();
}

public class CameraImpl implements Camera {
    @Override
    public void open() {
        System.out.println("打开相机");
    }

    @Override
    public void takePicture() {
        System.out.println("开始视频");
    }

    @Override
    public void close() {
        System.out.println("关闭相机");
    }
}

然后封装一个门面:

public class MobilePhone {
    private Phone phone = new PhoneImpl();
    private Camera camera = new CameraImpl();
    public void videoChat(){
        camera.open();
        phone.dail();
    }
    public void hangup(){
        phone.hangup();
        camera.close();
    }
}

客户端直接调动统一的接口就行了:

public class Client {
    public static void main(String[] args) {
        MobilePhone mobilePhone = new MobilePhone();
        mobilePhone.videoChat();
        System.out.println("----");
        mobilePhone.hangup();
    }
}

输出:

总结

外观模式的精髓就在于封装。通过封装出一个高层的统一调动接口,为系统提供统一的API,让用户通过一个API就能控制整个系统,减少 用户的使用成本,也提高了系统的灵活性。

优点

  • 对客户屏蔽了子系统减少了客户端要调用的系统个数,减少了客户对子系统的耦合。
  • 客户也可以直接使用子系统,是系统更加灵活。
  • 修改子系统不用修改客户端的调用。但是外观类可能要进行对应的修改。

缺点

  • 所有子系统的功能都通过一个接口来提供,这个接口可能会变得很复杂。
  • 修改子系统可能要修改外观类,不太符合开闭原则。
作者:qq_25806863 发表于2017/4/11 0:01:31 原文链接
阅读:287 评论:0 查看评论

Android设计模式(二十三)-桥接模式

$
0
0

桥接模式也叫桥梁模式,和生活中一样,桥梁就是用来连接河道两岸的主要建筑。桥接模式也是起着连接两边的作用,连接的两边就是抽象部分和实现部分,这就需要在程序设计的时候划分好抽象部分和实现部分了。

定义

将抽象部分与实现部分分离,使他们都可以独立地进行变化。

使用场景

  • 一个类存在两个独立维度的变化,且两个维度都需要进行拓展。
  • 一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免两个层次之间建立静态的继承联系,可以用桥接模式使他们在抽象层建立一个关联关系。
  • 不想使用继承或使用继承会导致生成一大堆类的时候。

UML

  • Abstraction: 抽象部分,抽象部分保持对实现部分的引用,抽象部分的方法要调用实现部分的对象来实现。一般为抽象类。
  • RefinedAbstraction: 优化的抽象部分,一般是对抽象部分的方法进行完善和拓展,是抽象部分的具体实现
  • Implementor:实现部分,可以是接口和抽象类,方法不一定要和抽象部分保持一致。一般情况下由实现部分提供基本的操作,而抽象部分定义基于这些操作的业务方法。
  • ConcreteImplementorA,B: 具体的实现部分。

模板代码:

实现部分的接口:

public interface Implementor{
    void operationImpl();
}

具体的实现部分:

public class ConcreteImplementorA implements  Implementor{

    @Override
    public void operationImpl() {
        //具体的实现
    }
}
public class ConcreteImplementor implements  Implementor{

    @Override
    public void operationImpl() {
        //具体的实现
    }
}

抽象部分:

public abstract class Abstraction{
    private Implementor mImplementor;

    public Abstraction(Implementor mImplementor) {
        this.mImplementor = mImplementor;
    }
    public void operation(){
        //调用实现部分的具体方法
        mImplementor.operationImpl();
    }
}

优化的抽象部分:

public class RefinedAbstraction extends Abstraction{

    public RefinedAbstraction(Implementor mImplementor) {
        super(mImplementor);
    }
    //可以增加拓展其他方法,也可以重写父类的方法,也能调用父类的方法

    @Override
    public void refinedperation() {
        //对抽象的父类的方法进行拓展
    }
}

简单实现

拿书中举得例子。这里用桥接模式来建立两个维度之间的联系。对咖啡来说,可以分为两个维度,杯子大小是一个维度,加不加糖又是一个维度。这两个没有谁是抽象部分谁是具体部分。就拿杯子大小作为抽象部分来做一个简单实现:

抽象的糖,相当于实现部分的接口:

public abstract class CoffeeSugar {
    public abstract void makeSugar();
}

两种实现部分的实现,加不加糖:

public class AddSugar extends CoffeeSugar {
    @Override
    public void makeSugar() {
        System.out.println("加糖的");
    }
}
public class NoSugar extends CoffeeSugar {
    @Override
    public void makeSugar() {
        System.out.println("不加糖的");
    }
}

杯子的抽象,相当于抽象部分,持有一个糖的引用:

public abstract class CoffeeCup {
    protected CoffeeSugar coffeeSugar;

    public CoffeeCup(CoffeeSugar coffeeSugar) {
        this.coffeeSugar = coffeeSugar;
    }

    public  void makeCup(){
        coffeeSugar.makeSugar();
    }
}

优化的抽象部分,有大小两种杯子:

public class LargeCup extends CoffeeCup {
    public LargeCup(CoffeeSugar coffeeSugar) {
        super(coffeeSugar);
    }

    @Override
    public void makeCup() {
        System.out.println("大杯的");
        super.makeCup();
    }
}
public class SmallCup extends CoffeeCup {
    public SmallCup(CoffeeSugar coffeeSugar) {
        super(coffeeSugar);
    }

    @Override
    public void makeCup() {
        System.out.println("小杯的");
        super.makeCup();
    }
}

客户端调用:

public class Client {
    public static void main(String[] args) {
        CoffeeSugar addSugar = new AddSugar();
        CoffeeSugar noSugar = new NoSugar();
        CoffeeCup coffee = new LargeCup(addSugar);
        coffee.makeCup();
        System.out.println("---");
        coffee = new LargeCup(noSugar);
        coffee.makeCup();
        System.out.println("---");
        coffee = new SmallCup(noSugar);
        coffee.makeCup();
    }
}

输出:

这样就把两个维度连接到一起了。而且两个维度是可以独立拓展的。比如如果想加上个中杯,或者来个多糖少糖等分类,只需要多实现几个类就行了,然后由客户端去调用。这样就能在两个维度上独立的拓展。

再想加上第三个维度也是很简单的,现在要加上年龄分类,有老年人喝的喝年轻人喝的,者又是一个维度。

这时可以把前面两个已经连接在一起的看做是一个维度,让新的去桥接者个已有的。

抽象的people,持有一个之前的桥接,把people和CoffeeCup连接起来:

ublic abstract class People {
    protected CoffeeCup coffeeCup;

    public People(CoffeeCup coffeeCup) {
        this.coffeeCup = coffeeCup;
    }

    public abstract void age();
}

具体的年龄分类:

public class YoungPeople extends People {
    public YoungPeople(CoffeeCup coffeeCup) {
        super(coffeeCup);
    }

    @Override
    public void age() {
        System.out.println("年轻人喝的");
        coffeeCup.makeCup();
    }
}
public class OldPeople extends People {
    public OldPeople(CoffeeCup coffeeCup) {
        super(coffeeCup);
    }

    @Override
    public void age() {
        System.out.println("老年人喝的");
        coffeeCup.makeCup();
    }
}

然后客户端只需要这样

public class Client {
    public static void main(String[] args) {
        CoffeeSugar addSugar = new AddSugar();
        CoffeeSugar noSugar = new NoSugar();
        CoffeeCup coffee = new LargeCup(addSugar);
        coffee.makeCup();
        System.out.println("---");
        coffee = new LargeCup(noSugar);
        coffee.makeCup();
        System.out.println("---");
        coffee = new SmallCup(noSugar);
        coffee.makeCup();
        System.out.println("-----");
        //再次桥接
        People people = new OldPeople(coffee);
        people.age();
    }
}

输出:

总结

桥接模式就是把系统分为抽象部分和实现部分,而建立桥接的方式也很简单。就是让抽象部分持有实现部分的引用,可以通过这个引用调用实现部分的具体方法。

使用这个系统最重要的是把握系统的分离,分不好就失去了灵活的拓展性,因此不容易设计。

优点

  • 分离成抽象部分和实现部分,并且两部分都可以独立的拓展,一个部分变化不会引起另一部分的变化,提高了系统的拓展性。
  • 复用性强,避免了使用继承产生大量继承类的问题。

缺点

  • 将系统分离为抽象部分和实现部分,会增加系统的复杂度和设计难度。如果系统不能分离出两个独立的维度的话,就不适合使用这个模式。
作者:qq_25806863 发表于2017/4/11 10:53:50 原文链接
阅读:207 评论:0 查看评论

Android开发Diffutils打造不一样的recyclerview

$
0
0

简述

DiffUtil是recyclerview support library v7 24.2.0版本中新增的类,根据Google官方文档的介绍,DiffUtil的作用是比较两个数据列表并能计算出一系列将旧数据表转换成新数据表的操作。这个概念比较抽象,换一种方式理解,DiffUtil是一个工具类,当你的RecyclerView需要更新数据时,将新旧数据集传给它,它就能快速告知adapter有哪些数据需要更新。就相当于如果改变了就对某个item刷新,没改变就没刷新,可以简称为局部刷新。

无脑刷新VS局部刷新

首先我们需要知道DiffUtil使用Eugene W. Myers的Difference算法来计算出将一个数据集转换为另一个的最小更新量,也就是用最简单的方式将一个数据集转换为另一个。DiffUtil还可以识别一项数据在数据集中的移动。但该算法不能检测移动的item,所以Google在其基础上改进支持检测移动项目,但是检测移动项目,会更耗性能。 下面是谷歌官网给出的在Nexus 5X M系统上进行运算的时长:

  • 100项数据,10处改动:平均值0.39ms,中位数:0.35ms。
  • 100项数据,100处改动:
    • 打开了移位识别时:平均值:3.82ms,中位数:3.75ms。
    • 关闭了移位识别时:平均值:2.09ms,中位数:2.06ms。
  • 1000项数据,50处改动:
    • 打开了移位识别时:平均值:4.67ms,中位数:4.59ms。
    • 关闭了移位识别时:平均值:3.59ms,中位数:3.50ms。
  • 1000项数据,200处改动:
    • 打开了移位识别时:平均值:27.07ms,中位数:26.92ms。
    • 关闭了移位识别时:平均值:13.54ms,中位数:13.36ms。

使用姿势

首先,我们得学会如何使用它,第二,我们需要知道用什么姿势来使用它,姿势不对,全都白费。

Diffutils.Callback

我们先看下Diffutils的callback的源码:

 /**
     * A Callback class used by DiffUtil while calculating the diff between two lists.
     * 当使用Diffutils的时候,这是一个计算2list不同的回调函数
     */
    public abstract static class Callback {

        /**
         * Returns the size of the old list.
         * 得到老的数据源大小
         */
        public abstract int getOldListSize();

        /**
         * Returns the size of the new list.
         * 得到新的数据源大小
         */
        public abstract int getNewListSize();

        /**
         * Called by the DiffUtil to decide whether two object represent the same Item.
         * For example, if your items have unique ids, this method should check their id equality.
         * <p>
         * 被DiffUtil调用,用来判断 两个对象是否是相同的Item。
         * 例如,如果你的Item有唯一的id字段,这个方法就判断id是否相等。
         * @param oldItemPosition The position of the item in the old list
         *                        旧数据的item
         * @param newItemPosition The position of the item in the new list
         *                        新数据的item
         * @return True if the two items represent the same object or false if they are different.
         * true代表着2item内容相同,否则,不同
         */
        public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

        /**
         * Called by the DiffUtil when it wants to check whether two items have the same data.
         * DiffUtil uses this information to detect if the contents of an item has changed.
         * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
         * so that you can change its behavior depending on your UI.
         * For example, if you are using DiffUtil with a
         * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
         * return whether the items' visual representations are the same.
         * This method is called only if {@link #areItemsTheSame(int, int)} returns
         * {@code true} for these items.
         * 被DiffUtil调用,用来检查 两个item是否含有相同的数据
         * DiffUtil用返回的信息(true/false)来检测当前item的内容是否发生了变化
         * 所以你可以根据你的UI去改变它的返回值
         * DiffUtil 用这个方法替代equals方法去检查是否相等。
         * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的视觉表现是否相同。
         * 这个方法仅仅在areItemsTheSame()返回true时,才调用。
         * @param oldItemPosition The position of the item in the old list
         *                        旧数据的item
         * @param newItemPosition The position of the item in the new list which replaces the
         *                        oldItem
         *                        新数据某个替换了旧数据的item
         * @return True if the contents of the items are the same or false if they are different.
         * true代表着2item内容相同,否则,不同
         */
        public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
        /**
         * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and
         * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil
         * calls this method to get a payload about the change.
         * <p>
         * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the
         * particular field that changed in the item and your
         * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that
         * information to run the correct animation.
         * <p>
         * Default implementation returns {@code null}.
         *
         * 当areItemsTheSame(int, int)返回true,且areContentsTheSame(int, int)返回false时,DiffUtils会回调此方法,
         * 去得到这个Item(有哪些)改变的payload。
         * 例如,如果你用RecyclerView配合DiffUtils,你可以返回  这个Item改变的那些字段,可以用那些信息去执行正确的动画
         * 默认的实现是返回null
         * @param oldItemPosition The position of the item in the old list
         * 在老数据源的postion
         * @param newItemPosition The position of the item in the new list
         * 在新数据源的position
         * @return A payload object that represents the change between the two items.
         * 返回 一个 代表着新老item的改变内容的 payload对象,
         */
        @Nullable
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return null;
        }
    }

上面是整个Callback的说明,我已标注中文,可以先理解下,我们接下来在看。

以正确姿势使用

上面我简单介绍了Callback的注解。现在我们通过继承来实现自己的。


public class SWDiffCallBack extends DiffUtil.Callback {

    private List<String> olddatas;
    private List<String> newDatas;

    public SWDiffCallBack(List<String> olddatas, List<String> newDatas) {
        this.olddatas = olddatas;
        this.newDatas = newDatas;
    }

    public int getOldListSize() {
        return olddatas.size();
    }

    public int getNewListSize() {
        return newDatas.size();
    }

    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return olddatas.get(oldItemPosition).equals(newDatas.get(newItemPosition));
    }
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return olddatas.get(oldItemPosition).equals(newDatas.get(newItemPosition));
    }
}

简单的自定义Callback我们已经实现了,下面我们来看看,是如何使用的呢。

  newlist = new ArrayList<>();
                for (int i = 1; i < list.size(); i++) {
                    newlist.add(list.get(i) + "");
                }
                newlist.add(5,list.size() + j + "");
                j++;
                //普通刷新
                // list=newlist;
//                adapter.setList(newlist);
//                adapter.notifyDataSetChanged();
                //强大的局部刷新
                DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new SWDiffCallBack(list, newlist), true);
                //利用DiffUtil.DiffResult对象的dispatchUpdatesTo()方法,传入RecyclerView的Adapter
                //别忘了将新数据给Adapter
                list = newlist;
                adapter.setList(list);
                diffResult.dispatchUpdatesTo(adapter);

看起来比全局刷新代码多好多?不要紧,我们来看看它的效果图在说。

效果图

这里写图片描述

一波666,还有自带的动画。我们在看看正常的全局刷新
这里写图片描述

看起来比Diffutils简单很多,不过当你真正在网络请求使用的时候,会发现完全不一样,整个屏幕会闪一下~没错,就是闪一下。之前测试让我改这种bug,我也是有点蒙,没法改啊- - 除非重写。。所以,Diffutils还是很强大的,demo会在文末放出,你们可以自己下载跑跑看。

姿势进阶使用

我们看了之前的使用,是不是发现,我之前解决Callback的时候,和我写demo的时候,少了一个方法,没错,就是getChangePayload。这个是做什么的呢?就是我整个列表的某个item只有一个数据改变的时候,我只要去替换那一个数据,而不需要替换整个item。
好了,我们直接写个测试的demo。代码很简单:

public class SWLoadDiffCallBack extends SWDiffCallBack{
//    private List<Bean> oldList;
//    private List<Bean> newList;
    public SWLoadDiffCallBack(List<String> olddatas, List<String> newDatas) {
        super(olddatas, newDatas);
    }

    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
//        Bean oldItem = oldList.get(oldItemPosition);
//        Bean newItem = newList.get(newItemPosition);
//        Bundle diffBundle = new Bundle();
//        if (!newItem.header.equals(oldItem.header)) {
//            diffBundle.putString(KEY_HEEDER, newItem.header);
//        }
//        if (!newItem.footer.equals(oldItem.footer)) {
//            diffBundle.putString(KEY_FOOTER, newItem.footer);
//        }
//        if (diffBundle.size() == 0)
            return null;
//        return diffBundle;
    }

这个我们是写个Bean,里面我们放入header和footer,然后进行逐个对比,这边我就不写demo了。相信这种写法你们应该能看懂。代码我已上传到csdn:点击下载demo

总结

1.Diffutils很适合各种刷新操作,我已经把他整合到我的开源中,你们可以自己去看。
2.Diffutils实现的局部刷新内部含有他自带的动画效果,所以我们无需去处理,而且看起来也比较美观~
3.DiffUtil可用于高效进行RecyclerView的数据更新,但DiffUtil本身的作用是计算数据集的最小更新。DiffUtil有强大的算法支撑,可以利用DiffUtil完成许多其他功能。

作者:sw950729 发表于2017/4/11 14:08:38 原文链接
阅读:1356 评论:5 查看评论

Android Things:外设I/O接口-I2C

$
0
0

一、接口简介

内部集成电路(IIC或者I2C)总线使用小数据负载连接简单的外部设备。传感器和执行器是常见的I2C使用案例,例如包含加速度计,温度计,LCD显示器,和电机驱动。

  1. I2C总线是一种同步的串行接口:这意味着它依赖于共享的时钟信号来同步设备之间的数据传输。控制时钟信号的设备被称为master,其它所有连接的外设被认为是Slaves,每个设备连接到同一组数据信号以形成总线。
  2. I2C设备连接使用3线接口

    • 共享时间信号(SCL);
    • 共享数据线(SDA);
    • 共同的接地参考(GND);

    这里写图片描述

  3. I2C仅支持半双工通信:因为所有的数据都是通过一根线连接。 所有的通信都是由master设备发起的,一旦主master传输完成slave必须响应
  4. I2C支持在同一条总线上连接多个slave设备:不像SPI,slave设备使用I2C软件协议寻址。每个设备编程有一个唯一的地址,并且仅仅响应master发送给地址的信息。每个slave设备必须有一个地址,即时总线仅仅包含一个单一的信号slave。

二、接口使用

1.管理Slave设备连接

public class HomeActivity extends Activity {
    // I2C Device Name
    private static final String I2C_DEVICE_NAME = ...;
    // I2C Slave Address
    private static final int I2C_ADDRESS = ...;
    private I2cDevice mDevice;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Attempt to access the I2C device
        try {
            PeripheralManagerService manager = new PeripheralManagerService();
            mDevice = manager.openI2cDevice(I2C_DEVICE_NAME, I2C_ADDRESS);
        } catch (IOException e) {
            Log.w(TAG, "Unable to access I2C device", e);
        }
    }
}

2.与寄存器通信

I2C Slave设备组织内容给可读或者可写的寄存器(单个字节数据由一个地址值引用):

  • 可读寄存器:包含slave想要向master报告的数据,例如传感器值或者状态标识;
  • 可写寄存器:包含master可以控制的配置数据;

一个常见的协议实现被称为System Management Bus(SMBus)存在于I2C顶部,以标准的方式和寄存器通信。SMBus命令由下面的两个I2C事务组成:
这里写图片描述

  • 第一个事务标识代表了要访问的寄存器的地址,第二个是在该地址读或者写的数据。
  • Slave设备的逻辑数据可能经常占用多个字节,从而包含多个寄存器地址。提供给API的地址始终是第一个寄存器的引用;

外设I/O提供了三种类型的SMBus命令来访问寄存器:

  • 字节数据:readRegByte()和writeRegByte()来读或者写一个单独的8位寄存器数据。
  • 字数据:readRegWord()和writeRegWord()来读或者写两个连续寄存器的值以一个16位litten-endian字。第一个寄存器的地址被翻译为字中的最小有效字节(LSB),其次是最重要的字节(MSB)。
  • 块数据:readRegBuffer()和writeRegBuffer()读或者写最多32个连续寄存器的值作为一个数组。
// Modify the contents of a single register
public void setRegisterFlag(I2cDevice device, int address) throws IOException {
    // Read one register from slave
    byte value = device.readRegByte(address);
    // Set bit 6
    value |= 0x40;
    // Write the updated value back to slave
    device.writeRegByte(address, value);
}
// Read a register block
public byte[] readCalibration(I2cDevice device, int startAddress) throws IOException {
    // Read three consecutive register values
    byte[] data = new byte[3];
    device.readRegBuffer(startAddress, data, data.length);
    return data;
}

3.传输原始数据

当和一个I2C外设交互时,定义不同的SMBus寄存器,或许根本不使用寄存器,使用原始的raw()和write()方法对通过导线传递的字节数据完全控制。这些方法将会执行一个如下单独的I2C传输:
这里写图片描述

  • 使用原始传输,设备将会在传输之前发送一个启动条件,然后一个停止条件。
  • 联合多个传输到一个“重复启动”条件是不可能的;
public void writeBuffer(I2cDevice device, byte[] buffer) throws IOException {
    int count = device.write(buffer, buffer.length);
    Log.d(TAG, "Wrote " + count + " bytes over I2C.");
}

4.关闭连接

当你完成I2C端口通信,关闭这个连接,并释放资源。此外,在现有端口关闭之前,你不能打开一个新的连接。要想关闭连接,使用端口的close()方法;

public class HomeActivity extends Activity {
    ... ... 
    private I2cDevice mDevice;

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDevice != null) {
            try {
                mDevice.close();
                mDevice = null;
            } catch (IOException e) {
                Log.w(TAG, "Unable to close I2C device", e);
            }
        }
    }
}

三、案例演示

下面我们就通过使用i2c接口,获取bmp280温度传感器的温度数据来演示该接口的使用。

1.硬件准备

  • 树莓派开发板 1块
  • 面包板 1块
  • bmp280传感器
  • 杜邦线(公对母)若干

广告时间咯:如果你还没有自己的开发板和元器件,到我们的“1024工场微店”来逛逛一逛吧(文章底部二维码),这里能一次性有买到你想要的!

2.电路搭建

这里写图片描述

3.代码编写

I2CDemo\app\src\main\java\com\chengxiang\i2cdemo\MainActivity.java

public class MainActivity extends AppCompatActivity {
    private static final String I2C_ADDRESS = "I2C1";
    private static final int TEMPERATURE_SENSOR_SLAVE = 0x77;
    private static final int REGISTER_TEMPERATURE_CALIBRATION_1 = 0x88;
    private static final int REGISTER_TEMPERATURE_CALIBRATION_2 = 0x8A;
    private static final int REGISTER_TEMPERATURE_CALIBRATION_3 = 0x8C;

    private static final int REGISTER_TEMPERATURE_RAW_VALUE_START = 0xFA;
    private static final int REGISTER_TEMPERATURE_RAW_VALUE_SIZE = 3;

    private TextView temperatureTextView;

    private I2cDevice i2cDevice;

    private final short[] calibrationData = new short[3];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        temperatureTextView = (TextView) findViewById(R.id.temperature);

        PeripheralManagerService peripheralManagerService = new PeripheralManagerService();
        try {
            i2cDevice = peripheralManagerService.openI2cDevice(I2C_ADDRESS, TEMPERATURE_SENSOR_SLAVE);
            calibrationData[0] = i2cDevice.readRegWord(REGISTER_TEMPERATURE_CALIBRATION_1);
            calibrationData[1] = i2cDevice.readRegWord(REGISTER_TEMPERATURE_CALIBRATION_2);
            calibrationData[2] = i2cDevice.readRegWord(REGISTER_TEMPERATURE_CALIBRATION_3);

            byte[] data = new byte[REGISTER_TEMPERATURE_RAW_VALUE_SIZE];
            i2cDevice.readRegBuffer(REGISTER_TEMPERATURE_RAW_VALUE_START, data, REGISTER_TEMPERATURE_RAW_VALUE_SIZE);
            if (data.length != 0) {
                float temperature = compensateTemperature(readSample(data));
                temperatureTextView.setText("temperature:" + temperature);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (i2cDevice != null) {
            try {
                i2cDevice.close();
                i2cDevice = null;
            } catch (IOException e) {
                Log.w(TAG, "Unable to close I2C device", e);
            }
        }

    }

    private int readSample(byte[] data) {
        // msb[7:0] lsb[7:0] xlsb[7:4]
        int msb = data[0] & 0xff;
        int lsb = data[1] & 0xff;
        int xlsb = data[2] & 0xf0;
        // Convert to 20bit integer
        return (msb << 16 | lsb << 8 | xlsb) >> 4;
    }

    private float compensateTemperature(int rawTemp) {
        float digT1 = calibrationData[0];
        float digT2 = calibrationData[1];
        float digT3 = calibrationData[2];
        float adcT = (float) rawTemp;

        float varX1 = adcT / 16384f - digT1 / 1024f;
        float varX2 = varX1 * digT2;

        float varY1 = adcT / 131072f - digT1 / 8192f;
        float varY2 = varY1 * varY1;
        float varY3 = varY2 * digT3;

        return (varX2 + varY3) / 5120f;
    }
}

4.运行结果

按照上面的电路图,搭建电路如下:
这里写图片描述
运行程序,通过传感器检测的温度显示在屏幕上:
这里写图片描述


1.新技术,新未来!欢迎大家关注“1024工场”微信服务号,时刻关注我们的最新的技术讯息。(甭客气!尽情的扫描或者长按!)
服务号
2.抛弃各种找元器件的烦恼,来“1024工场”微店,一次性买到你所想要的。(甭客气!尽情的扫描或者长按!)
微店
3.加入“Android Things开发”QQ讨论群,一起学习一起Hi。(甭客气!尽情的扫描或者长按!)
qq群

作者:p106786860 发表于2017/4/11 15:06:58 原文链接
阅读:222 评论:2 查看评论
Viewing all 5930 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>