天盾app项目总结

经过几个星期的努力,天盾app2.0版正式发布。再次记录下自己在开发中遇到的问题:

  1. 拍照图片oom
  2. listview 图片错位
  3. xutils 数据库的使用与升级

1. 拍照显示图片oom

由于该app中有快递单的采集,用RecyclerView 展示,并且每条可能需要拍照录入三张图片,这些图片需要显示并保存到文件便于上传。
目前,各个各个手机拍照后生成的图片比较大,分辨率也很高,直接显示很容易出现oom,使用BitmapFactory创建bitmap显示图片,每次使用都会分配内存,通过设置它的采样率,以避免。通过使用下面的工具类来加载图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class BitmapUtil {
private static final boolean DEBUG = false;
private static final String TAG = BitmapUtil.class.getSimpleName();

private BitmapUtil() {
throw new Error("Do not need instantiate!");
}

/**
* 图片压缩处理(使用Options的方法)
* <p/>
* @param reqWidth 目标宽度
* @param reqHeight 目标高度
*/
public static BitmapFactory.Options calculateInSampleSize(
final BitmapFactory.Options options, final int reqWidth,
final int reqHeight) {
// 源图片的高度和宽度
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > 400 || width > 450) {
if (height > reqHeight || width > reqWidth) {
// 计算出实际宽高和目标宽高的比率
final int heightRatio = Math.round((float) height
/ (float) reqHeight);
final int widthRatio = Math.round((float) width
/ (float) reqWidth);
// 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
// 一定都会大于等于目标的宽和高。
inSampleSize = heightRatio < widthRatio ? heightRatio
: widthRatio;
}
}
// 设置压缩比例
options.inSampleSize = inSampleSize;
options.inJustDecodeBounds = false;
return options;
}



/**
* 获取一个指定大小的bitmap
*
* @param reqWidth 目标宽度
* @param reqHeight 目标高度
*/
public static Bitmap getBitmapFromFile(String pathName, int reqWidth,
int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(pathName, options);
options = calculateInSampleSize(options, reqWidth, reqHeight);
return BitmapFactory.decodeFile(pathName, options);
}
}

这里只列出了计算采样率和从文件中加载显示的方法,如需了解更多该工具,请点击此处查看
具体使用如下:

1
2
3
4
int width = mImageView.getWidth();
int height = mImageView.getHeight();
//picPath 为图片存储路径
mImageView.setImageBitmap(BitmapUtil.getBitmapFromFile(picPath, width, height));

进过测试,连续拍照10多张并显示,内存的消耗物明显变化,大约有2M的多动,测试手机为Nexus 6,至此,oom完美解决,性能也十分好。

2. Listview 加载网络图片错位

在app登陆前,需要选择相应的快递和分部,而快递列表的设计是显示快递图片和快递公司名称,该部分数据是由网络获取的,展示在listView中。当图片地址为空时,无图片的item就会显示其他的图片,而且随着屏幕的滚动而变化,出现错位的现象。这种情况主要是由于ListView适配器 中getView的convertView复用导致的,解决办法是为imageview设置tag标记,这里以图片的url作为标记。如下,getView的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.list_item_express, parent, false);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

String imgUrl = mDatas.get(position).getExpressIco();
holder.imgExpressIcon.setTag(imgUrl);

//这里注意图片地址的判断,被 "" 坑了好久
if (holder.imgExpressIcon.getTag() == null || holder.imgExpressIcon.getTag().equals("")) {
//若无网络图片,显示错误图片
holder.imgExpressIcon.setImageResource(R.drawable.express_error);
} else if (holder.imgExpressIcon.getTag().equals(imgUrl)) {
x.image().bind(holder.imgExpressIcon, Constants.BASE_URL + imgUrl);
}
holder.tvExpressName.setText(mDatas.get(position).getExpressName());

return convertView;
}

3. xutils3 数据库的使用与升级

为了节省流量,将采集的数据保存在本地,便于在wifi情况下同一上传,只有该功能需要数据库,加上项目中使用的有xutils,带有数据库模块,便没有自己写或者使用 GreenDao,Ralem等其他的数据库框架。
此处简单的记录下改数据库框架的使用。

  • 在Application中配置
    在自己的application类(或者使用的activity)中添加配置信息,这里为了方便,在Application类中添加,并通过单利类访问使用。
    如下部分代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class SNApplication extends Application {

    private static DbManager.DaoConfig mDaoConfig = null;

    /**
    * 获取数据库配置对象
    *
    * @return
    */
    public static DbManager.DaoConfig getDaoConfig() {
    if (mDaoConfig == null) {
    mDaoConfig = new DbManager.DaoConfig()
    .setDbName("ygjexpress.db")
    .setDbVersion(2)
    .setDbOpenListener(new DbManager.DbOpenListener() {
    @Override
    public void onDbOpened(DbManager db) {
    // 开启WAL, 提升写入速度
    db.getDatabase().enableWriteAheadLogging();
    }
    })
    .setDbUpgradeListener(new DbManager.DbUpgradeListener() {
    @Override
    public void onUpgrade(DbManager db, int oldVersion, int newVersion) {
    //升级数据库
    try {
    //添加 user_id ,避免同一手机登陆多个账号出现数据混乱
    db.addColumn(PickupDbItem.class,"user_id");
    } catch (DbException e) {
    e.printStackTrace();
    }
    }
    });

    }
    return mDaoConfig;
    }
    }
  • 创建数据表对应的实体类
    通过注解,来指定数据表名(Table)和字段名(Column),isId 指定是否为id,property设置是否唯一。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    @Table(name = "pickup_item")
    public class PickupDbItem {
    @Column(name = "id", isId = true)
    private int id;
    @Column(name = "sender_idcrad_id")
    private String senderIdcradID;//身份证id
    @Column(name = "express_no")
    private String expressNo;//快递编号
    @Column(name = "pic_bale_before")
    private String picBaleBefore;//打包前
    @Column(name = "pic_bale_after")
    private String picBaleAfter;//打包后图片
    @Column(name = "pic_bale_complete")
    private String picBaleComplete;//贴快递单后图片

    @Column(name = "user_id")
    private String userId; //当前登录的用户id

    //此处省略构造方法和getter和setter方法
  • 具体的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //获取数据库配置
    private static DbManager mDbManager = x.getDb(SNApplication.getDaoConfig());
    //插入一条
    mDbManager.save(pickupDbItem);
    //查找-条
    pickupDbItem = mDbManager.selector(PickupDbItem.class)
    .where("express_no", "=", expressNo)
    .findFirst();
    //查找所有
    mDbManager.selector(PickupDbItem.class).findAll();
    //更新三个字段
    mDbManager.update(pickupDbItem, "sender_idcrad_id", "pic_bale_before", "pic_bale_after", "pic_bale_complete");
    //删除
    mDbManager.delete(pickupDbItem);

用法很简单,负责的查询条件可以使用WhereBuilder类来构造。更多的请参见此处

  • 数据库的升级

在配置文件中增加版本号,在 setDbUpgradeListener 中的 onUpgrade 方法中添加或删除列,最后在实体中添加相应的字段即可

项目比较小,遇到的问题也就这些,需要查看该app的,请访问内测平台

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器