自己经常会忘记一些密码什么的,想把这些密码保存下来,但是别人做的软件总有一点不安全的感觉,所以自己动手做了一个带有指纹加密的笔记本。

以下是本工程用到的一些第三方包

compile 'org.greenrobot:greendao:3.2.0'
compile 'net.zetetic:android-database-sqlcipher:3.5.1'
compile 'com.getbase:floatingactionbutton:1.10.1'

其中greendao是一款比较好用的开源数据库,具体和其他开源数据库相比好在哪里我就不介绍了,百度上面会有很多分析什么的,我就简单说一下用法就好。

需要在build.gradle中添加一下内容

#位置在build.gradle的第一行
apply plugin: 'org.greenrobot.greendao' #位置在android{}中,主要用于管理本地数据库版本,不同本地数据库版本之
#间升级时候需要对版本进行判断
greendao {
schemaVersion 2
targetGenDir 'src/main/java'
} #这个是在工程的build.gradle中进行配置的
#bulidscript{
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.0'
}
}

以上就完成greendao的配置,可以使用greendao了。

net.zetetic:android-database-sqlcipher:3.5.1

这个是用于数据库加密的一个第三方包,可以考虑使用,也可以不使用,毕竟只要你不自己主动将本地数据库提供出去,别人就不能直接从数据库中获得相应的信息

最后一个第三方库是一个floatingActionButton,提供了floatingActionMenu,是那种可以弹出悬浮按钮的效果

下面看一下工程的结构

当然之后可能还会变化,这个是我目前的进度。

下面开始介绍项目各个部分的实现,首先是主页面,主页面是一个比较简单,除了toolbar之外只有一个RecyclerView,用来以列表的方式展示便签的简要信息。资源文件内容如下

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.leaveme.notebook.MainActivity"> <android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"> <android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" /> </android.support.design.widget.AppBarLayout> <include layout="@layout/content_main" /> <com.getbase.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/fab"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:fab_icon="@drawable/ic_add_white_24dp"
/> </android.support.design.widget.CoordinatorLayout>

下面的内容就比较重要了,因为现在android版本都已经在5.0以上了,所以需要进行权限申请,需要将项目中用到的权限,比如读写存储卡的权限,指纹识别的权限,这就需要除了在Manifest中列清楚之外还要动态申请。

    private void Permissinit(){
//需要请求的权限请求字符串列表
List<String> permissionsNeeded = new ArrayList<String>();
//权限请求列表
final List<String> permissionsList = new ArrayList<String>();
//添加读写存储空间、读取手机状态、拨打电话、读取位置信息、读取精确位置信息这些权限到权限请求列表中
if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE))
permissionsNeeded.add("\n\r读存储空间");
if (!addPermission(permissionsList, Manifest.permission.READ_EXTERNAL_STORAGE))
permissionsNeeded.add("\n\r写存储空间");
if (!addPermission(permissionsList, Manifest.permission.INTERNET))
permissionsNeeded.add("\n\r联网");
if (!addPermission(permissionsList, Manifest.permission.USE_FINGERPRINT))
permissionsNeeded.add("\n\r使用指纹识别");
//如果权限请求列表中的内容大于0个,则开始请求权限
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) {
//获取到第一个需要添加请求列表的权限
String message = "你需要获取已下权限:" + permissionsNeeded.get(0);
//循环将剩余需要请求的权限加入到请求列表
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showMessageOKCancel(message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this,permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
});
return;
}
//开始向系统请求权限
ActivityCompat.requestPermissions(this,permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
}

先将需要请求的权限打包放在权限求情列表中,当然首先判断是否需要请求该权限,毕竟有的权限不需要动态请求也是可以获得的,这个时候就需要

    private boolean addPermission(List<String> permissionsList, String permission) {
//判断该应用是否具备要请求的权限
if (ContextCompat.checkSelfPermission(this,permission) != PackageManager.PERMISSION_GRANTED) {
//没有该权限,则加入到请求列表
permissionsList.add(permission);
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,permission))
return false;
}
return true;
}

checkSelfPermission就是判断该APP是否可以使用某种权限。当所有需要的权限都加入到权限请求列表后,并且该列表的大小不是0个,就可以向用户请求者写权限了。

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}

这个UI对话框的UI界面可以简单地向用户描述为什么需要这些权限。

    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
//判断是否为该软件系统请求的权限信息
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
//判断是否请求成功
{
//请求成功
if(grantResults.length>0&&grantResults[0] == PackageManager.PERMISSION_GRANTED){
}
else{
// //请求失败,提示用户“请求权限失败
Log.e("TAG","请求权限失败");
// Toast.makeText(this,"请求权限失败,请手动设置",Toast.LENGTH_LONG).show();
// this.finish();
}
}break;
}
}

当请求获得反馈信息后,会执行onRequestPermissionsResult函数,然后可以根据是否请求成功进行相应的操作。

这样就完成了一整套动态权限获取的流程。

然后是列表页面的实现,采用RecyclerView的主要原因是其在实现了listview功能的基础上,加入了更加规范的viewHolder,而且item的复用工作不需要手动管理。前期的实现代码如下,在创建的时候初始化加载数据,然后通过onbindViewHolder将数据绑定到每一个item上,逻辑实现比较清晰。资源文件比较简单,不单独列出了,在文章的末尾提供了整个项目的github地址,方便下载查看

public class NoteAdapter extends RecyclerView.Adapter<NoteAdapter.ItemViewHolder> implements ItemTouchHelperAdapter{

    private List<Note> notes = new ArrayList<>();
private Context context;
private DaoSession session;
private NoteDao noteDao; private final OnStartDragListener mDragStartListener; public NoteAdapter(Context context, OnStartDragListener dragStartListener){
mDragStartListener = dragStartListener;
this.context = context;
init();
} private void init(){
session = GreenDaoHelper.getDaoSession(context);
noteDao= session.getNoteDao();
notes = noteDao.loadAll();
} @Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_list, parent, false);
ItemViewHolder itemViewHolder = new ItemViewHolder(view);
return itemViewHolder;
} @Override
public void onBindViewHolder(ItemViewHolder holder, final int position) {
RecyclerView.ViewHolder viewHolder = (RecyclerView.ViewHolder)holder;
ViewGroup.LayoutParams layoutParams = viewHolder.itemView.getLayoutParams();
layoutParams.height = LinearLayout.LayoutParams.WRAP_CONTENT; holder.content.setText(notes.get(position).getContent());
holder.time.setText(dateFormatString.transform(notes.get(position).getTimeStamp()));
holder.title.setText(notes.get(position).getTitle()); ((RecyclerView.ViewHolder) holder).itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(context, NoteActivity.class);
intent.setAction(notes.get(position).getTimeStamp()+"");
context.startActivity(intent);
}
});
} @Override
public int getItemCount() {
if(noteDao.count()!=notes.size()){
notes = noteDao.loadAll();
return notes.size();
}
return notes.size();
} @Override
public boolean onItemMove(int fromPosition, int toPosition) {
Collections.swap(notes, fromPosition, toPosition);
notifyItemMoved(fromPosition, toPosition);
return true;
} @Override
public void onItemDismiss(final int position) {
new CommomDialog(context, R.style.dialog, "您确定删除此条记录?", new CommomDialog.OnCloseListener() {
@Override
public void onClick(Dialog dialog, boolean confirm) {
if(confirm){
session.getNoteDao().delete(notes.get(position));
notes.remove(position);
notifyItemRemoved(position);
dialog.dismiss();
} }
}).setTitle("提示").show();
} public static class ItemViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder{ public final TextView title;
public final TextView time;
public final TextView content;
public ItemViewHolder(View itemView) {
super(itemView);
title = (TextView)itemView.findViewById(R.id.tv_item_tile);
time = (TextView)itemView.findViewById(R.id.tv_item_time);
content = (TextView)itemView.findViewById(R.id.tv_item_content);
} @Override
public void onItemSelected() {
itemView.setBackgroundColor(Color.LTGRAY);
} @Override
public void onItemClear() {
itemView.setBackgroundColor(0);
}
}
}

在这里用到了greendao数据库初始化加载数据,具体的greendao的使用,我这里不介绍了,网上有很多教程可以找到,例如:Android GreenDao使用教程。列一下我的数据库格式,包括了以下这些column。其中@entity关键字表示这是一个实体Bean,会被greendao自动编译成一个数据库表,并形成一个notedao来管理数据库的增删查改等操作。

@Entity
public class Note {
@Id(autoincrement = true)
private long id; private String title;//笔记标题
private String content;//笔记内容
private long timeStamp;//笔记时间
private int state;//笔记状态 0:正常状态 1:加密状态 -1:已删除状态
private String pictureId;//笔记中的图片id
private long index;//显示顺序排序
@Generated(hash = 587745031)
public Note(long id, String title, String content, long timeStamp, int state,
String pictureId, long index) {
this.id = id;
this.title = title;
this.content = content;
this.timeStamp = timeStamp;
this.state = state;
this.pictureId = pictureId;
this.index = index;
}
@Generated(hash = 1272611929)
public Note() {
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return this.content;
}
public void setContent(String content) {
this.content = content;
}
public long getTimeStamp() {
return this.timeStamp;
}
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
public int getState() {
return this.state;
}
public void setState(int state) {
this.state = state;
}
public String getPictureId() {
return this.pictureId;
}
public void setPictureId(String pictureId) {
this.pictureId = pictureId;
}
public long getIndex() {
return this.index;
}
public void setIndex(long index) {
this.index = index;
} }

关于greendao自身的管理,即保证项目中仅有一个数据库,则需要创建一个greendaohelper继承自application,然后将Daomaster和DaoSession设计为单例模式。我参考了这一篇文章,Android GreenDao使用总结(包括模型生成、增删改查、修改存储路径、数据库更新升级和加解密数据库)

最后是NoteActivity的实现,就是写一些笔记的Activity。实现起来非常容易,页面上只有两个edittext,对其进行简单的配置即可。值得注意的是需要对启动来源进行判断,是来自直接新增一个条目,还是要修改一个条目,这里我采用的是传入一个timestamp参数,如果这个参数为空则表示是一个新增条目,则新建一个笔记条目。如果有这个timestamp参数,且不为空,那么就从数据库中获取该条笔记,并加载其中的内容,以备修改和查看。保存则是在推出的时候自动保存,包括点击android系统返回键退出或者点击上面的返回图表退出。

public class NoteActivity extends AppCompatActivity {    
private EditText title;

   private EditText content;
private Note note;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note); DaoSession session = GreenDaoHelper.getDaoSession(this);
NoteDao noteDao= session.getNoteDao(); Intent intent = getIntent();
String time = intent.getAction();
long currentTime = 0;
     //判断启动来源,决定是否需要新建一条笔记
if(null==time||time.equals("")){
       //需要新建一条笔记
currentTime = new Date().getTime();
note = new Note();
note.setId(noteDao.count());
note.setContent("");
note.setTitle("");
note.setPictureId("");
note.setState(0);
note.setTimeStamp(currentTime);
}else {
       //获取数据库中已有笔记
currentTime = Long.parseLong(time.trim());
List<Note> n = noteDao.queryBuilder().where(NoteDao.Properties.TimeStamp.eq(currentTime)).list();
if(n.size()>0) {
note = n.get(0);
}else {
Toast.makeText(this,"something error",Toast.LENGTH_SHORT).show();
}
} initView();
} private void initView(){
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_black_24dp);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
storeNote();
NoteActivity.this.finish();
}
});
title = (EditText) findViewById(R.id.edt_title);
content = (EditText)findViewById(R.id.edt_content); title.setText(note.getTitle());
content.setText(note.getContent());
} private void storeNote(){
DaoSession session = GreenDaoHelper.getDaoSession(this);
NoteDao noteDao= session.getNoteDao();
note.setContent(content.getText().toString());
note.setTitle(title.getText().toString());
     //插入或者替换笔记
noteDao.insertOrReplace(note);
} @Override
protected void onPause() {
super.onPause();
      
     //在这里写保存笔记的目的是为了防止因为activity的生命周期执行到onstop的时候,主界面的列表已经刷新了,进而导致数据同步延迟
storeNote();
}
}

这样基本上算是实现了一个笔记本的基本功能,然后指纹加密部分,以及实现功能的时候遇到的一些小问题写在下半部分。附上本项目代码:一个指纹加密的笔记本

实现一个带有指纹加密功能的笔记本(Android)第一部分的更多相关文章

  1. 实现一个带有指纹加密功能的笔记本(Android)第二部分

    上文基本完成了整个笔记本的笔记功能的实现,接下来记录实现指纹识别加密以及一些小注意事项. 首先判断该手机是否具备指纹识别的硬件功能和用户是否开启指纹识别. public boolean isFinge ...

  2. 分享一个带有合计行功能的DataGridView扩展

    因为一个Winform的项目中需要用到带有合计行的表格,并且需要满足以下需求: 合计行可自动对需要求和的列进行求和计算; 合计行必须固定(冻结)在表格的最底部,且其位置不受滚动条的滚动而移动; 可以设 ...

  3. 在存放源程序的文件夹中建立一个子文件夹 myPackage。例如,在“D:\java”文件夹之中创建一个与包同名的子文件夹 myPackage(D:\java\myPackage)。在 myPackage 包中创建一个YMD类,该类具有计算今年的年份、可以输出一个带有年月日的字符串的功能。设计程序SY31.java,给定某人姓名和出生日期,计算该人年龄,并输出该人姓名、年龄、出生日期。程序使用YM

    题目补充: 在存放源程序的文件夹中建立一个子文件夹 myPackage.例如,在“D:\java”文件夹之中创建一个与包同名的子文件夹 myPackage(D:\java\myPackage).在 m ...

  4. [转载]TexturePacker 如何使用自带的加密功能及在cocos2dx中的使用

    在cocos2dx中使用纹理图集是非常节省资源的,在这里推荐 TexturePacker,而且 TexturePacker工具的加密接口也非常的好用,下面就来介绍一下... TexturePacker ...

  5. 为SRS流媒体服务器添加HLS加密功能(附源码)

    为SRS流媒体服务器添加HLS加密功能(附源码) 之前测试使用过nginx的HLS加密功能,会使用到一个叫做nginx-rtmp-module的插件,但此插件很久不更新了,网上搜索到一个中国制造的叫做 ...

  6. 痞子衡嵌入式:开启NXP-MCUBootUtility工具的HAB加密功能 - CST(中英双语)

    1 Reason for enabling HAB encryption function 为什么要开启HAB加密功能 NXP-MCUBootUtility is a tool designed fo ...

  7. 给 Qt sqlite 增加加密功能

    整合sqlite代码 开源的sqlite中没有实现加密的功能,所以如果需要加密功能,需要自己实现 sqlite3_keysqlite3_rekey 等相关函数 不过开源的 wxsqlite3中已经实现 ...

  8. 用php实现一个敏感词过滤功能

    周末空余时间撸了一个敏感词过滤功能,下边记录下实现过程. 敏感词,一方面是你懂的,另一方面是我们自己可能也要过滤一些人身攻击或者广告信息等,具体词库可以google下,有很多. 过滤敏感词,使用简单的 ...

  9. SQLSERVER的一个不显眼的功能 备份文件的分割

    SQLSERVER的一个不显眼的功能 备份文件的分割 当完整备份数据库的时候,我们有时候可能会遇到一种极端情况,比如服务器上C,D,E三个盘符都只剩下5G空间了 但是如果要完整备份业务库需要12G的空 ...

随机推荐

  1. February 27 2017 Week 9 Monday

    All the bright precious things fade so fast. 所有的光鲜靓丽都敌不过时间. Try to make some things endurable and et ...

  2. python入门16 递归函数 高阶函数

    递归函数:函数内部调用自身.(要注意跳出条件,否则会死循环) 高阶函数:函数的参数包含函数 递归函数 #coding:utf-8 #/usr/bin/python """ ...

  3. vue错误提示 Cannot read property 'beforeRouteEnter' of undefined,刷新后跳到首页

    vue错误提示 Cannot read property 'beforeRouteEnter' of undefined,刷新后跳到首页 因为vue-router版本太高了,我vue用的是2.3.4, ...

  4. 贪心,Gene Assembly,ZOJ(1076)

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=76 解题报告: 1.类似活动安排问题. 2.输出格式要注意. #inc ...

  5. react中 props,state与render函数的关系

    我们很明显的能够感受到,react是一门数据驱动的框架,当数据发生变化,页面就会自动发生变化,他背后的原理是怎么样子的呢 比如todolist例子里面,inputValue变了,框里面的内容就会自动变 ...

  6. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(一) 整理基础数据

    最近碰巧发现一款比较好的Web即时通讯前端组件,layim,百度关键字即可,我下面要做的就是基于这个前端组件配合后台完成即时聊天等功能.当然用到的技术就是ASP.NET SingalR框架.本人不会c ...

  7. 广义线性模型(GLM, Generalized Linear Model)

    引言:通过高斯模型得到最小二乘法(线性回归),即:      通过伯努利模型得到逻辑回归,即:      这些模型都可以通过广义线性模型得到.广义线性模型是把自变量的线性预测函数当作因变量的估计值.在 ...

  8. Markdown黑色背景代码高亮

    因为我默认的不是Markdown...这里分享给用Markdown的小伙伴吧. /* 使用了Monokai Sublime的黑色主题皮肤,但是还存在样式冲突,需要自己修改 这个样式只适合使用maked ...

  9. 初学bind

    其实项目中还没有用到. 但自己还是想逐步了解一些高级的JS语法,不是为了炫技,也不像找前端的工作. 主要目的是:1.学习设计思想,提升解决问题的能力2.让自己的脑子动起来,别太笨. 简单的几句话总结一 ...

  10. 解决:Visual Studio 启动就报错退出

    Please open an administrative CMD window and navigate to C:\Program Files (x86)\Microsoft Visual Stu ...