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

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

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

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(); } }

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

posted on 2018-04-17 20:05  LeaveMeAlone1995  阅读(289)  评论(0编辑  收藏  举报