Android实践——密码本SecretBook

看了几天的东西,特别是看完数据库SQLite之后(第一行代码),想着无非还是CRUD之类的,也是该练练手了。做个什么呢,要简单一点的,实用点的,于是想好了——密码本,记录自己用到的各种账号密码。


这几天又看了点《代码大全2》,想着这东西要运用一下啊,于是结合作者软件构建的思维加Android的技术来练练手呗~虽然说系统有大有小,大系统大架构大考虑,小系统不需考虑那么多。


开发过程中遇到了各种问题,看书时觉得挺简单的东西,写起来就会出各种bug,开发工具又不是太会用,解决个问题要费老大的劲啊,真是抓狂!做完了现在记录下下~有的问题解决了,有的问题还是不知道怎么回事……(′д` )…彡…彡


按照软件开发过程,开始前的准备工作三部曲:问题定义、需求分析、软件架构。


 

Android实践——密码本SecretBook_第1张图片

 

一、问题定义

从客户的角度来看问题,用客户的语言来描述问题:开发一个个人密码管理工具


二、需求分析

通过口令(密码)进入密码本;

可以新增账号,保存到数据库;

可以查看全部账号密码信息,全部展示出来,就在一个页面,类似记事本;

可以修改账号信息,CRUD(增删改查);

安全:暂时不做要求


三、系统架构

  1. 程序组织:密码本是用来管理账户密码的(这就是概述)         构造快:界面、数据库操作、业务逻辑……只能想到这三个~(@_@;)             通信规则:Activity调用xml显示界面,调用DB类操作数据库;外部通信:暂时作为本地服务,不与其他应用交互
  2. 主要类:MainActivity:登录界面;ChooseOperationActivity:操作选择;AddActivity:新增;DisplayAllActivity:查看全部信息;MyDataBaseHelper:数据库操作
  3. 数据库设计:一张表:secretbook;字段:id、site、account、password、note
  4. 界面设计:见需求分析,Login——》Select——》Edit——》ViewAll
  5. 安全性:暂时不考虑安全性能,因为对SQLite数据库不了解,对Android系统管理文件的情况也不清楚,也不知道加密规则
  6. 可扩展性:要考虑以后会增加到多张表,增加新功能
  7. 可行性:100%,完全是个人实践之作,项目很小,就不多花时间准备了,第一次做,肯定会遇到问题,那就边做边解决问题吧
  8. ……不多说了,动手吧!<( ̄︶ ̄)>

编程语言:肯定是Java啊,Android的界面是用xml写

下面问题出现了:
首先考虑登录界面activity_main.xml:Android中有四种布局LinearLayout(线性布局)、RelativeLayout(相对布局)、FrameLayout(帧布局)、TableLayout(表格布局),到底用哪种呢?纠结了……一个输入框、一个登录按钮而已,我想把他们一起放在屏幕中间位置,那用RelativeLayout是最方便的呢,因为他可以用 android :layout_alignParentTop= "true" 在子控件里, 这个属性让本控件相对父级控件水平垂直方向上都居中,外面用 < RelativeLayout > 里面用一个 < TableLayout >就可以了,我刚开始把 android :layout_width和 android :layout_height 设置成了 "match_parent", 在手机上显示时密码输入框和按钮就位于最左上角,且占了整个屏幕宽度,不好看,于是改成了 "wrap_content" ,才显示在了屏幕中间位置,可是在我输入时,由于输入法的高度挡住了按钮,于是又将TableLayout往上拖了一点,这就是所谓的用户体验了吧,要时时刻刻考虑使用上的方便性,

登录时的密码问题我写死在了程序里面,由于没有设置对应的数据库来管理,暂时就这样吧……

然后是操作选择界面 < LinearLayout> 里面嵌 < LinearLayout> 吧。暂时还好,没出问题,可是按返回键时,密码还留在上一个Activity的输入框里,这是不对滴~我也不知道怎么做好,只有让它在通过密码验证之后清除数据了: editText .setText( "" );但是这样写效果很不好,会有个明显的清除过程后才会跳转,让人不舒服,可是我目前也不知道怎么办,就先这样吧……

接着新增数据,可以,操作数据库 MyDataBaseHelper 就得了。
package com.thsware.secretbook;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

/**  * Created by 世祥 on 2015/9/4.  */ public class MyDataBaseHelper extends SQLiteOpenHelper {

    private static final String CREATE_SECRETBOOK="create table secretbook("  +"id integer primary key autoincrement,"  +"site text,"  +"account text,"  +"password text,"  +"note text,"  +"deleteFlag integer"  +")";

    private Context mContext;

    public MyDataBaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext=context;
    }

    @Override
 public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_SECRETBOOK);
        Toast.makeText(mContext,"secretbook数据表创建成功~",Toast.LENGTH_SHORT).show();
    }

    @Override
 public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        switch (oldVersion){
            case 1:
                sqLiteDatabase.execSQL("alter table secretbook add column deleteFlag integer");
            default:
        }
    }
}


但是如果这里要退出app我得按三次返回键啊????难受不难受!得考虑在工具栏上面加个退出app的按钮,于是做一个 ActivityCollector类来管理每次新建的Activity,这就涉及到设计模式的问题了,让每个Activity在 onCreate() 方法中将自己交给 ActivityCollector 管理,当点击退出按钮时就调用 ActivityCollector 的方法一次性来结束所有的Activity,
package com.thsware.secretbook;

import android.app.Activity;
import java.util.ArrayList;
import java.util.List;

/**  * Created by 世祥 on 2015/9/4.  */ public class ActivityCollector {

    private static List<Activity> activities=new ArrayList<Activity>();

    public static void addActivity(Activity activity){
        activities.add(activity);
    }

    public static void removeActivity(Activity activity){
        activities.remove(activity);
    }

    public static void finishAll(){
        for (Activity ac : activities){
            if (!ac.isFinishing()){
                ac.finish();
            }
        }
    }
}

再就是查看全部数据了,问题又来了:怎么展示出来,又得考虑UI了,用 < ListView>, 里面就用默认的 android .R.layout. simple_list_item_1 吧,适配器就用String数组好了,将数据库中查到的每一条数据组装成我想要的格式放到数据列表中,这也算是结束了,可是问题就是这时出现的:我声明了一个全局变量 private  List<String> dataList; 可是忘记初始化了,我想着尽量迟一点初始化,等到用时再初始化,可是运行时始终崩溃。这个Android Studio工具不知道怎么调试的,没显示错误的原因啊,像Eclipse中就会明明白白写出是什么问题并显示错误链抛出的栈信息,可是这AS调试就什么都让人看不懂,在这里纠结了好久,,可能是我不会用吧……但我还是觉得AS设计的不好…… (〃` 3′〃)

再考虑ListView中每个子项的点击事件吧,根据点击的位置将信息带到新增页面上去(当时就不应该叫AddActivity的,这里明显是修改的功能嘛,所以设计时就要考虑好这个类是干嘛用的,这里就看出设计的重要性了,改动的成本是很大的,幸好我只是在自己练习),可是每当我点击时总崩溃,,又一次出错了不会调试……真难受啊……都不知道从哪里抛出了什么错误……哎╮(╯▽╰)╭慢慢试,,发现点击新增页面时也会崩溃,那么就是AddActivity有问题了,可是找了好久还是不知道怎么回事,google……也没找到怎么回事, 于是没办法了,最傻的方法来吧,一点点的改回去,边改边测试,看看是什么问题,我退啊退,,终于发现了问题所在,我将全局变量初始化时出的问题,如下:
private MyDataBaseHelper dbHelper=new MyDataBaseHelper(AddActivity.this, DATABASE_NAME, null, 2);
private SQLiteDatabase db= dbHelper.getWritableDatabase();
我还是不知道为什么,我想可能是startActivity(intent)出的问题吧,也可能是类加载的机制问题,看来我又忘了Java类加载的机制问题了吧~要找时间将没看完的《Thinking in Java》再好好学学了,,当时还明明白白的,现在好久没弄就忘了,,,,衰( ⊙ o ⊙ )啊!

于是改成了在onCreate()方法中再初始化dbHelper,这样就可以了,但是问题是永远不会结束的,永远解决不完的……接着出现的问题是intent带过来的数据问题,后来弄清楚了若某intent没有携带相应的数据,则获取到的是null,最后onCreate方法如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_add);
    ActivityCollector.addActivity(this);

    siteEdit= (EditText) findViewById(R.id.site);
    accountEdit= (EditText) findViewById(R.id.account);
    passwordEdit= (EditText) findViewById(R.id.password);
    noteEdit= (EditText) findViewById(R.id.note);
    saveButton= (Button) findViewById(R.id.save_button);
    dbHelper=new MyDataBaseHelper(AddActivity.this, DATABASE_NAME, null, 2);
    db = dbHelper.getWritableDatabase();

    Intent intent=getIntent();
    //未检测intent是否为空,此处id无数据时为null  id=intent.getStringExtra("id");
    if (id!=null && !id.equals("")){
        Cursor cursor=db.query(SECRETBOOK_TABLE, null, "id=?", new String[]{id}, null, null, null);
        if (cursor.moveToFirst()){
            String site=cursor.getString(cursor.getColumnIndex("site"));
            String account=cursor.getString(cursor.getColumnIndex("account"));
            String password=cursor.getString(cursor.getColumnIndex("password"));
            String note=cursor.getString(cursor.getColumnIndex("note"));

            siteEdit.setText(site);
            accountEdit.setText(account);
            passwordEdit.setText(password);
            noteEdit.setText(note);
        }
    }

    //保存数据  saveButton.setOnClickListener(new View.OnClickListener() {
        @Override
 public void onClick(View view) {
            String site = siteEdit.getText().toString().trim();
            if (site.equals("")) {
                Toast.makeText(AddActivity.this, R.string.sitename + "必须填写", Toast.LENGTH_SHORT).show();
                return;
            }
            ContentValues values = new ContentValues();
            values.put("site", site);
            values.put("account", accountEdit.getText().toString());
            values.put("password", passwordEdit.getText().toString());
            values.put("note", noteEdit.getText().toString());

            //如果id为空,则是新增数据  if (id==null || id.equals("")){
                values.put("deleteFlag", 0);
                db.insert(SECRETBOOK_TABLE, null, values);
            }else{
                db.update(SECRETBOOK_TABLE,values,"id=?",new String[]{id});
            }

            //未做判断,就提示成功了  Toast.makeText(AddActivity.this, "保存成功!", Toast.LENGTH_SHORT).show();
        }
    });
}
肯定是不好的,但是目前我也只能写成这样了,以后再慢慢优化吧!

数据库也出了问题,创建时没考虑删除的问题,后来想到删除时,就直接将数据给删除了,这是不好的,于是设置了一个deleteFlag删除标记,0就是正常的,1就是删除了的,本来只需要一位做标记就可以了的,可是查资料,SQLite中没有bit或者Boolean类的数据,于是选择了integer,但是数据库改变了,版本升级的问题也出现了,最后照着作者给的方案解决了。


本以为是一个小实践,可是前前后后也出了好多问题。也算是学到了好多吧!这次是将前前后后各章的内容都结合起来用到了一遍!还有很多问题遗留,以待解决

所以说读万卷书不如行万里路啊~实践才是检验真理的唯一标准~纸上得来终觉浅~……

你可能感兴趣的:(android,实践)