Android学习之路3——数据库SQLite的使用以及Android申请权限

Android学习之路3——数据库SQLite的使用以及Android申请权限

一、实验题目

  • 数据存储
    • 学习SQLite数据库的使用
    • 学习ContentProvider的使用。
    • 复习Android的界面编程。

二、实现内容

  • 进入程序,可以选择单选按钮决定注册还是登陆
    • Register注册页面
      • 可以选择图片作为头像,如果不选程序则自动用默认图片作为头像
      • Username,Password,Confirm Password不能为空,否则点击“OK”按钮会提示错误,Password输入框和Confirm Password 输入框的内容要一致,否则点击“OK”按钮也会提示错误”Password Mismatch“。
      • 如果Username已经存在,也会提示错误username + “already exists”
      • 注册成功地话会把用户的头像,用户名,密码存入数据库
    • Login登陆界面
      • Username,Password,Confirm Password不能为空,否则点击“OK”按钮会提示错误
      • Username和Password如果不存在数据库里也没法登陆成功,提示错误“Username not existed”
      • 如果成功则跳转到评论页面。
  • 评论页面:
    • 界面是一个已发表评论的列表,一个发评论的输入框,一个按钮用于发送评论
    • 只要评论内容不为空,都能发送,如果为空,点击“Send”按钮时会提示错误。
    • 点击评论列表里的条目,短按会弹出框,里面显示发表该评论的用户名和该用户在手机通讯录里存的手机号码,如果不存在该联系人就写明“Phone number not exist”。
    • 长按评论列表里的条目,如果该评论是由现登陆的用户发出的,弹出框内容是“Delete or not”,点击“YES"会把条目删除,点击”NO“就返回页面;如果该评论是由别的用户发出的,弹出框内容是“Report or not”,点击”YES“则弹出Toast提示”Report Success…"。

三、实验结果

实验结果gif图(以下3个图是连着的)

  • Android学习之路3——数据库SQLite的使用以及Android申请权限
  • Android学习之路3——数据库SQLite的使用以及Android申请权限
  • Android学习之路3——数据库SQLite的使用以及Android申请权限

实验截图

  • 登录界面:
    Android学习之路3——数据库SQLite的使用以及Android申请权限

  • 注册界面:
    Android学习之路3——数据库SQLite的使用以及Android申请权限

  • 注册一个新用户,头像,用户名,密码存储到数据库里
    Android学习之路3——数据库SQLite的使用以及Android申请权限

  • 如果再次输入同样的用户名的话,点击注册会报错:
    Android学习之路3——数据库SQLite的使用以及Android申请权限

  • 登录进到Comment页面
    Android学习之路3——数据库SQLite的使用以及Android申请权限

  • 如果Comment输入框为空就点击“Send”按钮会提示错误:
    Android学习之路3——数据库SQLite的使用以及Android申请权限

  • 点击评论的“点赞图标”,对应的点赞数会增加,点赞图标会变色,如上图所示。

实验步骤

  • Step1 先布局

    • 主页面的布局并不麻烦,外面是官方推荐的约束性布局ConstraintLayout,中间套一个LinearLayout使得全部控件整体居中,然后就一个一个排好控件的位置即可,至于点击Login或Register界面的不同再通过代码动态使得某些控件不可见即可。

    • 评论页面外层是用LinearLayout,上方是一个ListView,下方是评论框EditText和按钮Button,这个不难,下面是ListView里每个条目Item的布局:

      <?xml version="1.0" encoding="utf-8"?>
      <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          xmlns:app="http://schemas.android.com/apk/res-auto"
          android:layout_margin="10dp">
      
          <ImageView
              android:id="@+id/item_portrait"
              android:src="@drawable/me"
              android:layout_width="40dp"
              android:layout_height="40dp" />
      
          <TextView
              android:id="@+id/item_username"
              android:text="username"
              android:textSize="18sp"
              android:textColor="@color/textColor"
              app:layout_constraintLeft_toRightOf="@id/item_portrait"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
      
          <TextView
              android:id="@+id/item_date"
              android:text="20118-11-11 14:15:15"
              android:textSize="10sp"
              android:textColor="@color/textColor"
              app:layout_constraintLeft_toRightOf="@id/item_portrait"
              app:layout_constraintTop_toBottomOf="@id/item_username"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
      
          <TextView
              android:id="@+id/item_comment"
              android:text="comment"
              android:textSize="20sp"
              android:textColor="@color/dividerColor"
              app:layout_constraintLeft_toRightOf="@id/item_portrait"
              app:layout_constraintTop_toBottomOf="@id/item_date"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
      
          <TextView
              android:id="@+id/item_like_num"
              android:text="0"
              android:textSize="15sp"
              app:layout_constraintRight_toLeftOf="@id/item_like_pic"
              app:layout_constraintBottom_toBottomOf="@id/item_like_pic"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content" />
      
          <ImageView
              android:id="@+id/item_like_pic"
              android:src="@drawable/white"
              app:layout_constraintRight_toRightOf="parent"
              app:layout_constraintBottom_toBottomOf="@id/item_username"
              android:layout_width="20dp"
              android:layout_height="20dp" />
      </android.support.constraint.ConstraintLayout>
      
  • Step2:数据库的建立(MyDB.java)

    创建三个表格,用户信息User,评论信息Comment,用户与点赞数的关联表UC下面是MyDB.java的关键代码:

       	public static final String CREATE_USER="create table User ("
              + "username text primary key,"
              + "password text,"
              + "portrait blob)";
      
          public static  final String CREATE_COMMENT = "create table Comment ("
                  + "id integer primary key autoincrement,"
                  + "username text,"
                  + "date text,"
                  + "comment text,"
                  + "likeNum integer)";
      
          public static final String CREATE_UC = "create table UC ("
                  + "id integer primary key autoincrement,"
                  + "commentId integer,"
                  + "username text)";
      	//...
      	@Override
          public void onCreate(SQLiteDatabase db){
              db.execSQL(CREATE_USER);
              db.execSQL(CREATE_COMMENT);
              db.execSQL(CREATE_UC);
              Toast.makeText(mContext,"Create Succeeded",Toast.LENGTH_SHORT).show();
          }
    
  • Step3:为评论的条目信息创建一个类来进行操作(CommentInfo.java),头像可以根据用户名从数据库里读出来。

    public class CommentInfo implements Serializable{
        private int id;
        private String username;
        private String date;
        private String comment;
        private int likeNum;
    
        public CommentInfo(int id,String username,String date,String comment,int likeNum){
            this.id = id;
            this.username = username;
            this.date = date;
            this.comment = comment;
            this.likeNum = likeNum;
        }
    
        public int GetId(){return id;}
        public String GetUsername(){
            return username;
        }
        public String GetDate(){
            return date;
        }
        public String GetComment(){
            return comment;
        }
        public int GetLikeNum(){
            return likeNum;
        }
        public void SetLikeNum(int num){this.likeNum = num;}
    }
    
  • Step4:为ListView设置一个适配器,这个稍微麻烦一点,根据传过来的CommentInfo对象将信息实例到ListView的每一个条目里,头像就根据CommentInfo对象的用户名从User表格里查找出对应的头像;还要更具UC关联表判断对应的条目里的点赞按钮的状态,下面是MyListViewAdapter.java中的getView函数和对应的ViewHolder类。

        @Override
        public View getView(int position, View convertView, ViewGroup parent){
            final CommentInfo item = getItem(position);
            final View view;
            final ViewHolder viewHolder;
            if(convertView == null){
                view = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
                viewHolder = new ViewHolder();
                viewHolder.portrait = (ImageView)view.findViewById(R.id.item_portrait);
                viewHolder.username = (TextView)view.findViewById(R.id.item_username);
                viewHolder.date = (TextView)view.findViewById(R.id.item_date);
                viewHolder.comment = (TextView)view.findViewById(R.id.item_comment);
                viewHolder.likeNumber = (TextView)view.findViewById(R.id.item_like_num);
                viewHolder.likePic = (ImageView) view.findViewById(R.id.item_like_pic);
                view.setTag(viewHolder);
            }else{
                view = convertView;
                viewHolder = (ViewHolder)view.getTag();
            }
            viewHolder.username.setText(item.GetUsername());
            viewHolder.date.setText(item.GetDate());
            viewHolder.comment.setText(item.GetComment());
            viewHolder.likeNumber.setText(String.valueOf(item.GetLikeNum()));
    
            MyDB dbHelper = new MyDB(view.getContext(),"User.db",null,1);
            final SQLiteDatabase db = dbHelper.getWritableDatabase();
            Cursor cursor = db.rawQuery("select * from User where username = ?",new String[]{item.GetUsername()},null);
            if(cursor.getCount() > 0){
                cursor.moveToFirst();
                byte[] in = cursor.getBlob(cursor.getColumnIndex("portrait"));
                Bitmap bitmap = BitmapFactory.decodeByteArray(in,0,in.length);
                viewHolder.portrait.setImageBitmap(bitmap);
            }
            cursor.close();
            
            cursor = db.rawQuery("select * from UC where commentId = ? and username = ?",new String[]{String.valueOf(item.GetId()),CommentActivity.username});
            if(cursor.getCount() > 0 ){
                viewHolder.likePic.setImageResource(R.drawable.red);
                viewHolder.likePic.setTag(R.drawable.red);
            }else{
                viewHolder.likePic.setImageResource(R.drawable.white);
                viewHolder.likePic.setTag(R.drawable.white);
            }
    
            viewHolder.likePic.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if( R.drawable.white == (Integer)viewHolder.likePic.getTag() ){
                        viewHolder.likePic.setImageResource(R.drawable.red);
                        viewHolder.likePic.setTag(R.drawable.red);
                        item.SetLikeNum(item.GetLikeNum()+1);
                        viewHolder.likeNumber.setText(String.valueOf(item.GetLikeNum()));
                        ContentValues values = new ContentValues();
                        values.put("likeNum",item.GetLikeNum());
                        db.update("Comment",values,"id = ?",new String[]{String.valueOf(item.GetId())});
    
                        values.clear();
                        values.put("commentId",item.GetId());
                        values.put("username",CommentActivity.username);
                        db.insert("UC",null,values);
                    }else{
                        viewHolder.likePic.setImageResource(R.drawable.white);
                        viewHolder.likePic.setTag(R.drawable.white);
                        item.SetLikeNum(item.GetLikeNum()-1);
                        viewHolder.likeNumber.setText(String.valueOf(item.GetLikeNum()));
                        ContentValues values = new ContentValues();
                        values.put("likeNum",item.GetLikeNum());
                        db.update("Comment",values,"id = ?",new String[]{String.valueOf(item.GetId())});
                        values.clear();
    
                        db.delete("UC","commentId = ? and username = ?",new String[]{String.valueOf(item.GetId()),CommentActivity.username});
                    }
                }
            });
            return view;
        }
    
        class ViewHolder{
            ImageView portrait;
            TextView username,date,comment,likeNumber;
            ImageView likePic;
        }
    
  • Step5:MainActivity.java就处理注册与登录的问题,用一个变量记录当前是登陆页面还是注册页面,然后根据状态处理点击“OK”按钮时对应的事件:

    • 如果是登陆界面就检查Username和password是否已存在数据库里 才能正确登陆。

    • 如果是注册界面就检查Username和password的合法性以及是否已经存在该用户。

       okBtn.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      if(TextUtils.isEmpty(usernameEdit.getText().toString())){
                          Toast.makeText(MainActivity.this,usernameEdit.getHint().toString()+" cannot be empty",Toast.LENGTH_SHORT).show();
                          return;
                      }else if(TextUtils.isEmpty(passwordEdit.getText().toString())){
                          Toast.makeText(MainActivity.this,"Password cannot be empty",Toast.LENGTH_SHORT).show();
                          return;
                      }
                      if(isInLoginPage){
                          SQLiteDatabase db = dbHelper.getWritableDatabase();
                          Cursor cursor = db.rawQuery("select * from User where username = ? and password = ?",new String[]{usernameEdit.getText().toString(),passwordEdit.getText().toString()});
                          if(cursor.getCount() <= 0){
                              Toast.makeText(MainActivity.this,"uername or password invalid",Toast.LENGTH_SHORT).show();
                              return;
                          }
                          passwordEdit.setText("");
                          Toast.makeText(MainActivity.this,"Correct Password",Toast.LENGTH_SHORT).show();
                          //跳转到评论页面
                          Intent intent = new Intent(MainActivity.this,CommentActivity.class);
                          intent.putExtra("username",usernameEdit.getText().toString());
                          startActivity(intent);
                          cursor.close();
      
                      }else{
                          if(!passwordEdit.getText().toString().equals(confirmPasswordEdit.getText().toString())){
                              Toast.makeText(MainActivity.this,"Password Mismatch",Toast.LENGTH_SHORT).show();
                          }else{
                              SQLiteDatabase db = dbHelper.getWritableDatabase();
                              Cursor cursor = db.rawQuery("select * from User where username = ?",new String[]{usernameEdit.getText().toString()});
                              if(cursor.getCount() > 0){
                                  Toast.makeText(MainActivity.this,usernameEdit.getText().toString()+" already existed",Toast.LENGTH_SHORT).show();
                                  return;
                              }
                              cursor.close();
      
                              Bitmap bitmap = null;
                              if(imageView.getTag().toString().equals("selecting")){
                                  Resources res = getResources();
                                  bitmap = BitmapFactory.decodeResource(res,R.drawable.me);
                              }else{
                                  bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
                              }
                              ByteArrayOutputStream os = new ByteArrayOutputStream();
                              bitmap.compress(Bitmap.CompressFormat.PNG,100,os);
      
                              ContentValues values = new ContentValues();
                              values.put("username",usernameEdit.getText().toString());
                              values.put("password",passwordEdit.getText().toString());
                              values.put("portrait",os.toByteArray());
                              db.insert("User",null,values);
                              Toast.makeText(MainActivity.this,"Register success..",Toast.LENGTH_SHORT).show();
                          }
      
                      }
                  }
              });
      
    • 难点在手机图片库的读取,Android 6.0以后对程序的权限要求更严格了,除了要在AndroidManifest.xml加入权限声明,还要在代码上动态申请权限:

       imageView.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      //申请权限
                      if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
                          ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},RESULT_LOAD_IMAGE);
                      }
                      Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
                      startActivityForResult(i,RESULT_LOAD_IMAGE);
                  }
              });
      
    • 如果成功获得权限进行图片的读取后要重写onActivityResult函数对图片进行处理:

      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
      
       	@Override
          protected void onActivityResult(int requestCode,int resultCode,Intent data){
              super.onActivityResult(requestCode,resultCode,data);
              if(requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && data != null){
                  Uri selectedImage = data.getData();
                  String[] filePathColumn = { MediaStore.Images.Media.DATA};
                  Cursor cursor = getContentResolver().query(selectedImage,filePathColumn,null,null,null);
                  cursor.moveToFirst();
                  int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
                  String picturePath = cursor.getString(columnIndex);
                  cursor.close();
      
                  BitmapFactory.Options options = new BitmapFactory.Options();
                  // options 设为true时,构造出的bitmap没有图片,只有一些长宽等配置信息,但比较快,设为false时,才有图片
                  options.inJustDecodeBounds = true;
                  int scale = (int)(options.outWidth / (float)100);
                  if(scale <= 0){
                      scale = 1;
                  }
                  options.inSampleSize = scale;
                  options.inJustDecodeBounds = false;
                  Bitmap bitmap = BitmapFactory.decodeFile(picturePath,options);
                  imageView.setImageBitmap(bitmap);
                  if(bitmap != null){
                      imageView.setTag("selected");
                  }
                  Log.d("SELECEIMAGE",imageView.getTag().toString());
              }
          }
      
  • Step6:评论页面CommentActivity.java

    • 点击SEND按钮的事件处理,如果评论消息不为空即会发送成功,并写入数据库:

      sendBtn.setOnClickListener(new View.OnClickListener() {
                  @Override
                  public void onClick(View v) {
                      if(TextUtils.isEmpty(commentEdit.getText().toString())){
                          Toast.makeText(CommentActivity.this,"Comment cannot be empty",Toast.LENGTH_SHORT).show();
                          return;
                      }
                      SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                      Date date = new Date(System.currentTimeMillis());
                      String dateString = simpleDateFormat.format(date);
      
                      SQLiteDatabase db = dbHelper.getWritableDatabase();
                      ContentValues values = new ContentValues();
                      values.put("username",username);
                      values.put("date",dateString);
                      values.put("comment",commentEdit.getText().toString());
                      values.put("likeNum",0);
                      db.insert("Comment",null,values);
                      Cursor cursor = db.rawQuery("select * from Comment",null,null);
                      if(cursor.moveToLast()){
                          CommentInfo item = new CommentInfo(cursor.getInt(cursor.getColumnIndex("id")),username,dateString,commentEdit.getText().toString(),0);
                          itemList.add(item);
                          myListViewAdapter.notifyDataSetChanged();
                      }
                      commentEdit.setText("");
                  }
              });
      
    • 进入该页面时先初始化页面的内容,即从数据库读取存好的评论信息然后显示在ListView里:

       	public void InitList(){
              SQLiteDatabase db = dbHelper.getWritableDatabase();
              Cursor cursor = db.rawQuery("select * from Comment",null,null);
              if(cursor.moveToFirst()){
                  do{
                      int id = cursor.getInt(cursor.getColumnIndex("id"));
                      String username = cursor.getString(cursor.getColumnIndex("username"));
                      String date = cursor.getString(cursor.getColumnIndex("date"));
                      String comment = cursor.getString(cursor.getColumnIndex("comment"));
                      int likeNum = cursor.getInt(cursor.getColumnIndex("likeNum"));
                      CommentInfo commentInfo = new CommentInfo(id,username,date,comment,likeNum);
                      itemList.add(commentInfo);
                  }while(cursor.moveToNext());
              }
              cursor.close();
          }
      
    • 对ListView列表的每一项长按是删除或举报(这取决于当前登录的用户与点击的该评论的发起者是不是同一个用户),如果是删除的话,除了要在listview上显示还要同时更新对应的数据库内容:

      listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
                  @Override
                  public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                      final int removePos = position;
                      AlertDialog.Builder dialog = new AlertDialog.Builder(CommentActivity.this);
                      if(username.equals(itemList.get(position).GetUsername())){
                          dialog.setTitle("Delete or not?");
                          dialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
                              @Override
                              public void onClick(DialogInterface dialog, int which) {
                                  SQLiteDatabase db = dbHelper.getWritableDatabase();
                                  Log.d("SELECTITEM",String.valueOf(itemList.get(removePos).GetId()));
                                  db.delete("Comment","id = ?" ,new String[]{String.valueOf(itemList.get(removePos).GetId())});
                                  itemList.remove(removePos);
                                  myListViewAdapter.notifyDataSetChanged();
                              }
                          });
                      }else{
                          dialog.setTitle("Report or not?");
                          dialog.setPositiveButton("YES", new DialogInterface.OnClickListener() {
                              @Override
                              public void onClick(DialogInterface dialog, int which) {
                                  Toast.makeText(CommentActivity.this,"Already reported.",Toast.LENGTH_SHORT).show();
                              }
                          });
                      }
      
                      dialog.setNegativeButton("NO", new DialogInterface.OnClickListener() {
                          @Override
                          public void onClick(DialogInterface dialog, int which) {
                          }
                      });
                      dialog.show();
                      return true;
                  }
              });
      
    • 对ListView列表的短按操作是读取通讯录里与当前登录的用户名相同的联系人电话信息,与读取图片一样,要在AndroidManifest申明权限还要在代码动态申请:

      <uses-permission android:name="android.permission.READ_CONTACTS"/>
      
      listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                  @Override
                  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                      if(ContextCompat.checkSelfPermission(CommentActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
                          ActivityCompat.requestPermissions(CommentActivity.this, new String[]{Manifest.permission.READ_CONTACTS},1);
                      }else{
                          AlertDialog.Builder dialog = new AlertDialog.Builder(CommentActivity.this);
                          dialog.setTitle("Info").setPositiveButton("ok", new DialogInterface.OnClickListener() {
                              @Override
                              public void onClick(DialogInterface dialog, int which) {
                              }
                          });
                          Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME+" = ?",new String[]{itemList.get(position).GetUsername()},null);
                          if(cursor.moveToFirst()){
                              //String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                              String phoneNumber = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                              dialog.setMessage("Username: "+itemList.get(position).GetUsername() + "\nPhone: " + phoneNumber);
                          }else{
                              dialog.setMessage("Username: "+itemList.get(position).GetUsername() + "\nPhone number not exist.");
                          }
                          dialog.show();
                          cursor.close();
                      }
                  }
              });
      

参考资料:

完整代码见我的Github