自己经常会忘记一些密码什么的,想把这些密码保存下来,但是别人做的软件总有一点不安全的感觉,所以自己动手做了一个带有指纹加密的笔记本。
以下是本工程用到的一些第三方包
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(); } }
这样基本上算是实现了一个笔记本的基本功能,然后指纹加密部分,以及实现功能的时候遇到的一些小问题写在下半部分。附上本项目代码:一个指纹加密的笔记本
原文地址:https://www.cnblogs.com/leaveMeAlone/p/8867568.html