`Android`标签下的文章

编程

用handler/thread来做定时器

刚开始接触android编程的时候,是大三.那时候在做精益防伪的演示版App,网络协议用的是socket.

使用socket的时候,编译器告诉我,需要在新线程中来使用socket操作.

于是乎,就学习了Thread的使用.

当时觉得Thread好像还是蛮简单的.在thread里面搞一个runnable,在runnable里面跑代码块,用start(),stop()来控制线程的开始和结束.

之后在thread中操控了ui,就闪退了.因为UI只能在主线程中去操作.这iOS就牛逼了,即使不在主线程中去操作,他也不会闪退.他会自动的在主线程中去渲染.

分界线

言归正传,后面上课,老师让我们用thread来做一个定时器,在主线程中显示一个数字,在另一个线程中定时增加数字.

这玩意儿是来学习Thread和Handler的.

之后需要用到定时器的地方我都用Thread来实现了~

然而

Thread不是很安全.而且,对于不同的手机厂商,不同的ROM,对thread的操作也是不一样的.

Thread workingThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (isWorking) {
                byte[] wantSend = sendingData();
                if (wantSend == null || mainCharacteristic == null || mainGatt == null) {
                    dataPointer = 0;
                    isWorking=false;
                    workingThread.interrupt();
                } else {
                    mainCharacteristic.setValue(wantSend);
                    mainGatt.writeCharacteristic(mainCharacteristic);
                    if (dataPointer == 0 && dataStack.size() > 0) {
                        try {
                            Thread.sleep(gifInterval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(baseInterval);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    });

某些手机上,这个thread是停不下来的.在某些手机上,这个thread是无法重启的.在某些手机上,这个thread是无法中断的.

这就意味着,某些手机上,炫轮app只能发送一次图片;在某些手机上,炫轮app无法显示附近的蓝牙.

查了一些资料,我才发现thread,的很多接口都被废弃了,而且非常不安全,很容易出现锁死卡死的情况.

于是我去检查了很早很早很早的炫轮app.发现是用延迟来做定时器功能的.

于是我把Runnable提取出来了:

Runnable sendingRunnable=new Runnable() {
    @Override
    public void run() {
        if (isWorking) {
            byte[] wantSend = sendingData();
            if (wantSend == null || mainCharacteristic == null || mainGatt == null) {
                dataPointer = 0;
                isWorking=false;
            } else {
                mainCharacteristic.setValue(wantSend);
                mainGatt.writeCharacteristic(mainCharacteristic);
                if (dataPointer == 0 && dataStack.size() > 0) {
                        mHandler.postDelayed(sendingRunnable,gifInterval);
                } else {
                        mHandler.postDelayed(sendingRunnable,baseInterval);
                }
            }
        }
    }
};

用Handler的postDelayed来做延迟执行,这竟然就解决了前面所有的问题!

原因,我还要好好查些资料,再来补充.下次更新!

阅读剩下更多

编程

初用Volley

Context

水壶架iOS端终于撸完了

由于我Android的界面写的烂的一逼,所以只能拜托xiaopo来帮我撸界面了.

首要目标是干掉蓝牙类,不过蓝牙绑定设备的时候会需要网络请求.

那就顺便把网络也给撸掉吧~

基本用法

1.需要一个请求队列

RequestQueue mQueue = Volley.newRequestQueue(context);

2.好了直接撸请求吧

Form-Data这么搞

StringRequest stringRequest = new StringRequest("一个URL",new Response.Listener<String>() {
        @Override
        public void onResponse(String response) {
               Log.d("Network OK", response);
        }
        }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
               Log.e("Network Fail", error.getMessage(), error);
        }
});

JSON这么搞

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://m.weather.com.cn/data/101010100.html", null,
    new Response.Listener<JSONObject>() {
        @Override
        public void onResponse(JSONObject response) {
            Log.d("Network OK", response.toString());
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.e("Network Fail", error.getMessage(), error);
        }
    });

Q1 请求是form-data 响应是json

不过这里遇到了一个问题.

我们服务器大佬坑我,请求只能解析Form-Data的,返回的响应是Json

于是只能手动转换了

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
            try {
                JSONObject jsonResponse = new JSONObject(response);
                if (jsonResponse.getInt("status") == 10086) {
                    //认证失败
                    failBlock.requestFail(new Exception("SessionExpired"));
                } else {
                    succeedBlock.getResponse(jsonResponse);
                }
            } catch (JSONException e) {
                e.printStackTrace();
                failBlock.requestFail(e);
            }
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            if (error.networkResponse.statusCode == 10000) {
                //认证失败
                failBlock.requestFail(new Exception("SessionExpired"));
            } else
                failBlock.requestFail(error);
        }}){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }
    };
    queue().add(request);
    return request;
}

用JSONObject自带的牛逼的初始化方法来解析响应

好不容易,把网络请求跑通了.

才发现,一个劲的403.

原来,Volley本身不处理Cookie的!!

啊席巴

于是就要手动处理

private static StringRequest FormDataRequest(String url, int method, Map parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock) {
    StringRequest request=new StringRequest(method, url, new Response.Listener<String>() {

        @Override
        public void onResponse(String response) {
           //这些你都看过了
        }
    }, new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            //这些你都看过了
            }){
        @Override
        protected Map<String, String> getParams() throws AuthFailureError {
            return parameter;
        }

        @Override
        public Map<String, String> getHeaders() throws AuthFailureError {
            Map<String,String> header=super.getHeaders();
            String cookie=getCookie();
            if (cookie!=null){
                header.put("Set-Cookie",cookie);
                //手动从把Cookie放到Header中
            }
            return header;
        }

        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            refreshCookie(response.headers);
                //手动从Response的Header中获取Cookie
            return super.parseNetworkResponse(response);
        }
    };
    queue().add(request);
    return request;
}

这其中的Cookie持久化什么的,就不说了.它不是重点.

而重点是……

Final 我放弃了

当Response的Header中有多个Set-Cookie字段时,只会被解析出来一个!

如果需要把所有的Cookie都拿出来的话,需要修改Volley源码!

然而,我使用Gradle来添加Volley工程的,所以,你逗我吗!

于是乎,我就放弃了.

姜还是老的辣

大三的时候做精益防伪,使用了Android-Async-Http

这个很牛逼,功能很强大,还自动缓存了Cookie,真是替我省心.

还好前面撸接口的时候,封装的比较好,所以没有改动太多的东西.

Tip

封装了两个基本网络操作函数

public static void getMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    getViaLoopj(url,parameter,succeedBlock,failBlock);
}

public static void postMethodFormData(String url, Map<String,String> parameter, VKNetworkSucceedBlock succeedBlock, VKNetworkFailBlock failBlock){
    postViaLoopj(url,parameter,succeedBlock,failBlock);
}

再来一发Android-Async-Http的实例(封装过了)

public static void postViaLoopj(String url, Map<String,String> params,VKNetworkSucceedBlock succeedBlock,VKNetworkFailBlock failBlock) {
    client.post(url, new RequestParams(params), new JsonHttpResponseHandler(){
        @Override
        public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
            {
                succeedBlock.getResponse(response);
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {
            super.onFailure(statusCode, headers, throwable, errorResponse);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else{
                failBlock.requestFail(new Exception(errorResponse.toString()));
            }
        }
        @Override
        public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {
            super.onFailure(statusCode, headers, responseString, throwable);
            if (statusCode==403){
                failBlock.requestFail(new Exception("SessionExpired"));
            }else
                failBlock.requestFail(new Exception(responseString));
        }
    });
}

定义了两个interface

public interface VKNetworkSucceedBlock {
    void getResponse(JSONObject responseObject);
}

public interface VKNetworkFailBlock {
    void requestFail(Exception error);
}

于是乎做网络请求,就方便的很了

public static void mLogin(String email, String verify, boolean isChina, NetworkUtils.VKNetworkSucceedBlock succeed, NetworkUtils.VKNetworkFailBlock fail){
    HashMap<String,String> map=new HashMap<String, String>(){{
            put("hhh","123@163.com");
            put("jjj","3434");
            put("kkk",String.valueOf(1));
        }};
      NetworkUtils.postMethodFormData(UrlManager.URL_Login,map,succeed,fail);
}

最后网络的效果,就留到具体调用他的类去实现吧.

所以,封装了一遍就是好瞧把你能耐的,更换网络框架,不需要跑到每个Activity啊,Class里面去改.

response也是一套,

顶层完全不用去关注,底层的网络是如何实现的.(好像段老师还是郝老师这么说过)

当然我相信,来看这篇文章的大佬们,肯定早就知道这个了

阅读剩下更多

编程

安卓初体验?

安卓初体验~

其实这并不算是初体验吧,毕竟大三的时候已经玩过android开发了,而且看起来也蛮像回事儿的.

只不过当时搞了NFC,业务逻辑和网络请求.剩下的最重要的界面和界面跳转,都是在鹅厂的望神帮我写的~

第一节课其实我没去

那一节课,据说老师讲了布局文件

然后好像就布置了一个计算器的作业,然后就没什么了.

反正,我就在界面编辑器里面拖控件.然后findViewById,之后给他们做了一些逻辑~

第二节课说是要做SD卡读取图片,并且显示出来

我一想,觉得有点儿难啊,于是就开始查官方文档去了,

整理了一下,获取文件夹的路径,文件夹内文件这些好像都不难.

选择一个内容显示估计也不难,但是如何做一个漂亮的选择页面,可能比较复杂.

我打算用GridView,做一个和相册一样.

Let’s do this

先搞了一个SDCardHelper的类(写iOS时候留下的习惯,各种Helper)

//外部储存是否可写
static public boolean isExternalStorageWritable()
//外部储存是否可读
static public boolean isExternalStorageReadable()
//储存路径
static public String storagePath()
//为GridView用的文件列表
static public List<GridItem> getFileList()

Android的GridView和iOS的UICollectionView好像差不太多

Android的GridView需要的适配器里面需要重写

public int getCount();
public Object getItem(int position);
public long getItemId(int position);
public View getView(int position, View convertView, ViewGroup parent);

iOS的UICollectionView需要实现DataSource协议的

-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView;
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section;
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath

这样就可以正常显示一个GridView(UICollectionView)

可是,我要自定义控件呀!不然用原始的GridView中每个item的View或者UICollectionView中原始的UICollectionViewCell
,那得多难看…

在iOS中,只要写一个继承于UICollectionViewCell的类,对它进行界面操作,数据操作,并且在Protocol中复用Cell并且返回,就可以完成目的

在Android中,貌似没有类似UICollectionViewCell的东西.所以得自己重写一个类,用来持有Item的xml布局文件中的各个控件

import android.widget.ImageView;
import android.widget.TextView;

/**
 * Created by vikingwarlock on 16-9-23.
 * This is the class for view
 */
public class GridItemView {
    public ImageView imageView;
    public TextView filename;
}

之前的图片列表,也专门写了一个类,用来存放这个数据

import java.io.Serializable;

/**
 * Created by vikingwarlock on 16-9-23.
 * This is a class that represent a grid item
 */
public class GridItem implements Serializable{
    public String filepath;
    public String filename;
}

这里用了一个Serializable,是为了后面Intent传递用的~

最后在Adapter类里面实现View

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    GridItemView item;
    GridItem data = gridItemList.get(position);
    if (convertView == null) {
        convertView = inflater.inflate(R.layout.gridviewitem, null);
        item = new GridItemView();
        item.filename = (TextView) convertView.findViewById(R.id.itemName);
        item.imageView = (ImageView) convertView.findViewById(R.id.itemImage);
        convertView.setTag(item);
    } else {
        item = (GridItemView) convertView.getTag();
    }
    ImageLoader.getInstance().displayImage("file://"+data.filepath,item.imageView);
    item.filename.setText(data.filename);
    return convertView;
}

界面就差不多搞定了~

至于点击效果,iOS依然是实现一个Protocol~就可以了

-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;

Android需要手动添加

GridView gv = (GridView) findViewById(R.id.gridView);
if (gv != null) {
    gv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
            GridItem item = datalist.get(position);
            Intent intent=new Intent();
            intent.setClass(SDView.this,MainActivity.class);
            Bundle bundle=new Bundle();
            bundle.putSerializable("result",item);
            intent.putExtras(bundle);
            setResult(2,intent);
            finish();
        }
    });

这里的gv是GridView的意思,不要想歪了.

做了一堆和Bundle有关的事情,主要是为了Intent中可以传递一个对象,而不仅仅是字符串.

setResult()用来传递一个Intent回上一个需要Result的Activity.

finish()就是结束当前Activity.

这个花了我最多时间的东西就这样做完了~

当然啦,后面还遇到了图片显示的问题.比如卡住啊,比如OOM啊~最后借助了universal-image-loader
都解决了!!

最后坐在旁边的同学跟我说,他们的代码,只有几十行.原来老师给的demo中获取图片是这样写的

@Override
public void onClick(View v) {
    Intent intent = new Intent();
    intent.setType("image/*");
    /* 使用Intent.ACTION_GET_CONTENT这个Action */
    intent.setAction(Intent.ACTION_GET_CONTENT);
    /* 取得相片后返回本画面 */
    startActivityForResult(intent, 1);
}

我好生气!说好的操作SD卡呢!

这样的写法,和iOS中的UIImagePickerViewController一样了~

Anyway,至少我学会了自定义控件的GridView,体验了一把OOM,还学习了SDCard的操作

还望各位大大指导我!

阅读剩下更多

返回顶部