Android 网络请求+数据库操作实现数据的读取及增删改查

本文介绍了一个结合ListView和SQLite的书籍管理应用程序,它能够在线获取JSON数据,存储到本地数据库,并在每次启动时优先展示本地数据,实现书籍信息的高效管理和展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

综合ListView、SQLite、网络操作来完成一下需求:

  • 程序第一次启动使用GET方式获取JSON数据并展示到ListView控件中,链接:http://www.imooc.com/api/teacher?type=2&page=1
  • 程序第一次启动除了获取展示数据外,还需要创建数据表book,建立列id、learner、name,并将数据插入到book表中
  • 程序每次启动,先从本地数据表book中获取数据,如果有数据则展示数据表book中的数据,如果没有则从网络获取数据展示并更新本地数据表book
    要求:
  1. 使用SQLiteOpenHelper实现book表的创建、增、删、查操作
  2. 使用AsyncTask从网络获取json数据,构造书籍集合并更新数据表book
  3. 在MainActivity类的onCreate()方法中,查询book表中有无数据,如果有数据则绑定ListView并显示,如果没有则从网络获取并显示

运行效果

分析

需要涉及到网络请求,先完成网络权限的配置。功能分为以下几块来实现:

Book: 书籍类,实现序列化接口
BookDao: 书籍数据表,实现书籍数据表的创建和存储,同时提供对外的增删改查功能的接口
Book_Async: 异步类,从网络获取JSON数据,并设置相应适配器


两个Activity
MainActivity: 程序主界面,实现基本UI布局、数据加载、控件点击事件
BookActivity: 添加和修改数据的界面,实现书籍数据表的更新

代码实现

JSON

在这里插入图片描述
要获取的是data里的JSON数组,通过遍历该数组来获取每一项的数据,数组长度是10,同时只需要id、name、learner三个属性

Book

书籍表类实现了序列化接口,同时声明了三个属性:_id,name,learner


这里一定要有_id!这里一定要有_id!这里一定要有_id!
id前面不能没有 _ !不能没有 _ !不能没有 _!


数据库操作需要使用到游标对象,游标对象的查询是基于_id来查询的,如果没有会报错,所以创建对象的时候要么带有_id属性,要么在后续的数据库操作中选择一个属性来代替_id作为主键
可以在创建数据库的时候将_id设置为自动增长,这样在添加新的Book数据的时候就不用管这个_id属性了

public class Book implements Serializable {
    private String _id, name,learner;

    public Book() {
    }

    public Book(String _id, String name, String learner) {
        this._id = _id;
        this.learner = learner;
        this.name = name;
    }

    public String get_id() {
        return _id;
    }

    public void set_id(String _id) {
        this._id = _id;
    }

    public String getLearner() {
        return learner;
    }

    public void setLearner(String learner) {
        this.learner = learner;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

BookDao

将数据库存放在内部存储中,并实例化一个SQLiteOpenHelper辅助来来创建书籍数据库,并创建共有的增删改查方法
查询方法返回的就是一个游标对象,通过游标对象来定位表中对应的数据行

    private SQLiteDatabase db;

    public BookDao(Context context) {
        // 数据库放置路径,这里放置在内部存储中
        String path = context.getFilesDir().getAbsolutePath() + "/book.db";
        // 实例化辅助类
        // 参数1:上下文  参数2:数据库路径   参数3:游标工厂,null 参数4:版本,是否调用升级方法
        SQLiteOpenHelper helper = new SQLiteOpenHelper(context, path, null, 1) {
            // 创建
            @Override
            public void onCreate(SQLiteDatabase db) {
                // 创建book表
                String sql = "create table book (" +
                        "_id integer primary key autoincrement," +
                        "name varchar(50)," +
                        "learner varchar(10))";
                db.execSQL(sql);
            }

            // 升级
            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

            }
        };
        
        // 获取数据库对象
        /// 如果数据库不存在,则先创建,再打开(第一次运行)
        /// 如果数据库存在,但是版本没变化,则直接打开
        /// 如果数据库存在,但是版本号升高了,则调用升级方法,再打开
        db = helper.getReadableDatabase();
    }

    // 增
    public void add(Book book) {
        String sql = "insert into book (name ,learner) values(?,?)";
        Object[] args = {book.getName(), book.getLearner()};
        db.execSQL(sql, args);// 无返回值
    }

    // 删
    public void delete(String learner) {
        String sql = "delete from book where learner = " + learner;
        db.execSQL(sql);
    }

    // 改
    public void update(Book book) {
        String sql = "update book set learner = ?," +
                "name = ? where _id = ?";
        Object[] args = {book.getLearner(), book.getName(), book.get_id()};
        db.execSQL(sql, args);
    }

    // 查
    public Cursor getAllBooks() {
        String sql = "select * from book";
        Cursor c = db.rawQuery(sql, null);// 游标对象
        return c;
    }

Book_Async

该类继承了AsyncTask父类,因为不涉及结果和过程的传参,后两个参数设置成Void类型
这里设置的是从外部传入网络链接,所以第一个参数设置成String类型,也可以在doInBackground方法里面写链接,这样三个参数都可以设置成Void类型
doInBackground()方法中完成从网络获取数据,将所需数据放入Book对象中,并加入数据表

    private Book book;
    private String[] from = {"learner", "name"};
    private int[] to = {R.id.learner, R.id.name};

    @Override
    protected Void doInBackground(String... strings) {
        try {
            // 从网络获取JSON数据
            URL url = new URL(strings[0]);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(6000);
            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream is = conn.getInputStream();
                byte[] b = new byte[1024];
                int len = 0;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                while ((len = is.read(b)) > -1) {
                    baos.write(b, 0, len);
                }
                String str = new String(baos.toByteArray());

                // 通过JSONObject来解析JSON数据
                // 参数:满足JSON格式要求的字符串
                JSONObject jo = new JSONObject(str);
                // 获取JSON对象数组
                JSONArray ary = jo.getJSONArray("data");

                for (int i = 0; i < ary.length(); i++) {
                    // 获取单个JSON对象
                    JSONObject obj = ary.getJSONObject(i);
                    String id = obj.getString("id");
                    String name = obj.getString("name");
                    String learner = obj.getString("learner");

                    // 向book表添加数据
                    book = new Book(id, name, learner);
                    // 创建book表,添加数据
                    MainActivity.dao.add(book);
                }
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

onPostExecute()方法中创建简单游标适配器,更新主界面中的ListView的布局

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        // 设置游标适配器
        MainActivity.c = MainActivity.dao.getAllBooks();
        MainActivity.adapter = new SimpleCursorAdapter(MainActivity.context, R.layout.item, MainActivity.c, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        MainActivity.books.setAdapter(MainActivity.adapter);
    }

MainActivity

为了代码整洁,在onCreate()方法中分别实现了初始化数据和初始化事件两个方法:

    public static ListView books;
    public static BookDao dao;
    public static Map<String, Book> map;
    public static Context context;
    public static Cursor c;
    public static SimpleCursorAdapter adapter;
    private String learner;
    private Book_Async ba;
    private Book book;
    private int lastPostion = -1;
    private String[] from = {"learner", "name"};
    private int[] to = {R.id.learner, R.id.name};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initData();
        initEvent();
    }

initData()完成基本的初始化,同时完成数据库的判断,如果book.db不存在则调用异步类从网络获取数据并创建数据库,如果book.db存在,则直接加载本地数据

    private void initData() {
        books = findViewById(R.id.books);
        context = this;
        map = new HashMap<>();

        // 判断数据库是否存在,如果存在则不再通过异步类获取网络数据
        File f = new File(getFilesDir(),"book.db");
        if (!f.exists()) {
            dao = new BookDao(this);
            ba = new Book_Async();
            ba.execute("http://www.imooc.com/api/teacher?type=2&page=1");
        }else{
            dao = new BookDao(this);
        }
    }

initEvent()设置ListView控件的点击事件,用户选中子项时,该子项背景色变为灰色,并保存子项信息,提供给其它方法使用

    private void initEvent() {
        // 为ListView子项添加点击事件
        books.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // 将游标移动到指定的book数据处
                c.moveToPosition(position);
                learner = c.getString(2);

                // 获得选中对象
                book = new Book(c.getString(0), c.getString(1), c.getString(2));

                // 设置选中子项背景色为灰色
                view.setBackgroundColor(Color.GRAY);
                // 将上一次选择的子项颜色恢复为白色
                if (lastPostion != -1 && lastPostion != position) {
                    books.getChildAt(lastPostion).setBackgroundColor(Color.WHITE);
                }
                lastPostion = position;
            }
        });
    }

要实现当用户在修改添加界面完成操作后,返回主界面时主界面数据的更新,所以要把ListView的布局放在MainActivity的onResume()方法中

    @Override
    protected void onResume() {
        super.onResume();
        // 设置游标适配器
        c = dao.getAllBooks();
        adapter = new SimpleCursorAdapter(this, R.layout.item, c, from, to, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        books.setAdapter(adapter);
    }

最后就是底部添加、删除、修改三个按钮的点击事件
**添加:**点击后直接跳转至添加界面BookActivity
**删除:**获取ListView子项点击事件所记录下的learner属性,通过该属性调用BookDao中的删除方法删除数据,并更新游标对象,显示相应提示
**修改:**当用户选中子项后获取对应的Book对象,通过实例化一个意图对象,将该对象传输至修改界面,并实现界面跳转,显示相应提示

    // 按钮点击事件
    public void myClick(View v) {
        switch (v.getId()) {
            case R.id.add:
                startActivity(new Intent(this, BookActivity.class));
                book = null;
                break;
            case R.id.delete:
                dao.delete(learner);
                // 更新游标对象
                c.requery();
                lastPostion = -1;
                book = null;
                break;
            case R.id.change:
                if (book != null) {
                    Intent it = new Intent(this, BookActivity.class);
                    it.putExtra("book", book);
                    startActivity(it);
                    book = null;
                } else {
                    Toast.makeText(this, "请选择要修改的信息", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

BookActivity

因为控件不多,所以直接在该界面的onCreate()方法中进行初始化,同时接受上一个界面传过来的Book对象,如果该对象为空,则说明用户是通过添加按钮进入,若对象不为空,这说明用户是通过修改按钮进入

    private EditText learnerEdit, nameEdit;
    private Book b;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book);

        learnerEdit = findViewById(R.id.learner_edit);
        nameEdit = findViewById(R.id.name_edit);

        // 获取从上一个界面传过来的对象
        b = (Book) getIntent().getSerializableExtra("book");
        if (b != null) {
            nameEdit.setText(b.getName());
            learnerEdit.setText(b.getLearner());
        }
    }

commit()在该方法中完成提交按钮的单击事件,同样通过Book对象是否为空来判断是添加还是修改操作。添加直接调用添加方法,在原有的数据库的最后直接添加数据,修改需要获取当前传入Book对象的id,再调用修改方法修改对应id的数据。完成后均弹出相应提示

    public void commit(View v) {
        v.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String learner = learnerEdit.getText().toString();
                String name = nameEdit.getText().toString();
                Book book = new Book("0", name, learner);

                if (b == null) {
                    // 添加操作
                    dao.add(book);
                    Toast.makeText(BookActivity.this, "添加成功", Toast.LENGTH_SHORT).show();
                } else {
                    // 修改操作
                    book.set_id(b.get_id());
                    dao.update(book);
                    Toast.makeText(BookActivity.this, "修改成功", Toast.LENGTH_SHORT).show();
                }
                learnerEdit.setText("");
                nameEdit.setText("")}
        });
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值