文章 8 - 处理 Android 上的输入和存储






4.72/5 (12投票s)
本文简要介绍了Android平台为开发者提供的不同数据类型存储方法,如共享偏好设置、内部存储、外部存储、SQLite数据库和Web服务。
引言
在本文中,我们将简要了解如何处理不同输入类型的存储,以及您可以根据需求和应用类型在Android应用程序中应用的不同存储选项,以及通过Web服务访问数据,这是目前最常用的方法。
我们将涵盖以下选项
背景
如果您正在寻找完整的教程,请参考以下链接
文章2 - Peter Leow的设置Android开发环境
文章3 - Varavut的创建简单的Hello World Android项目
文章4 - Peter Leow的Android UI布局和控件
文章5 - Ranjan.D的Android用户交互和传感器
文章6 - Grasshopper.iics的Android资源组织/访问初学者指南
文章7 - 很快更新
文章8 - 很快更新
文章9 - 很快更新
文章10 - 很快更新
文章11 - 很快更新
文章12 - 很快更新
下载次数
- 下载SharedPreferences.zip - 1.4 MB
- 下载InternalStorage.zip - 1.4 MB
- 下载SQLite.zip - 1.4 MB
- 下载kSOAP.zip - 1.7 MB
- 下载JSONParser.zip - 1.4 MB
存储选项
在99%的任何Android应用程序中,您都需要以某种方式保存用户数据,这就是为什么Android提供了不同的存储选项和数据管理,开发者可以使用这些选项来保存数据,选择哪种选项取决于开发者的需求和您的应用程序类型。
1.1 - 它是什么?
共享偏好设置是Android的一个API,用于存储和检索持久性的小型应用程序数据,这意味着您在共享偏好设置中保存的数据即使关闭应用程序也会存在,并且只有通过从设备卸载应用程序才能删除数据。此外,保存在共享偏好设置中的数据仅对该应用程序私有,设备上的任何其他应用程序都无法访问。
您必须注意,数据以键/值对的形式保存。
键/值对:这是一种已知的数据保存方法,将数据保存为唯一标识符和标识符的数据值或数据位置的链接,例如您的代码看起来像 **<键, 值>** => **<"网站", "CodeProject">**,当您需要查找特定值时,您将通过键进行搜索,例如:find(website),它将显示此键的值。
1.2 - 我应该何时使用共享偏好设置?
如果您需要为您的应用程序保存简单数据,最简单直接的方法是共享偏好设置。它通常用于保存整数、双精度浮点数、布尔值和短文本等简单数据。例如,它用于保存应用程序设置或用户登录信息。
1.3 - 如何存储数据?
首先,您必须初始化一个共享偏好设置实例并使用它来存储您的数据,如下所示
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
        
   SharedPreferences _SharedPref = getApplicationContext().getSharedPreferences("MyAppData", 0);
   Editor _SharedPrefEditor = _SharedPref.edit();
}
我创建了一个名为 `_SharedPref` 的共享偏好设置实例,文件名为 `MyAppData`(可以是任何名称),第二个参数设置为 `0`,表示这些数据仅对本应用程序私有(私有模式)。然后我们创建了一个 `Editor`,我们将使用它来存储新值,这将在接下来的几行中看到。
现在,您可以使用创建的 `Editor` 保存数据,如下所示
_SharedPrefEditor.putBoolean(key, value);
_SharedPrefEditor.putFloat(key, value);
_SharedPrefEditor.putInt(key, value);
_SharedPrefEditor.putLong(key, value);
_SharedPrefEditor.putString(key, value);
_SharedPrefEditor.putStringSet(arg0, arg1);
        
_SharedPrefEditor.commit(); //you have to commit to save your data
1.4 - 如何检索数据?
与存储数据相同,您需要初始化一个共享偏好设置实例并使用它来检索数据
SharedPreferences _SharedPref = getApplicationContext().getSharedPreferences("MyAppData", 0);
_SharedPref.getAll(); // to retrieve all data related to MyAppData SharedPreference File
_SharedPref.getBoolean(key, defValue); 
_SharedPref.getFloat(key, defValue);  
_SharedPref.getInt(key, defValue);    
_SharedPref.getLong(key, defValue);   
_SharedPref.getString(key, defValue);
_SharedPref.getStringSet(arg0, arg1);
1.5 - 如何删除共享偏好设置数据?
您有两种选择,可以删除特定数据或删除共享偏好设置中的所有数据
1. 删除特定项目
SharedPreferences _SharedPref = getApplicationContext().getSharedPreferences("MyAppData", 0);
Editor _SharedPrefEditor = _SharedPref.edit();
        
_SharedPrefEditor.remove(key); //Set the key of the data you want to delete
_SharedPrefEditor.commit();
2. 删除所有数据
SharedPreferences _SharedPref = getApplicationContext().getSharedPreferences("MyAppData", 0);
Editor _SharedPrefEditor = _SharedPref.edit();
        
_SharedPrefEditor.clear(); //This will clear all data inside MyAppData Shared Preference File
_SharedPrefEditor.commit();
1.6 - 示例
我创建了一个示例,结合了我们讨论过的所有先前观点。该示例是一个简单的登录表单,用户必须登录才能进入下一个活动,然后他可以选择注销或保持登录状态。
1. 我创建了我的XML布局,其中包含标题、电子邮件输入文本、密码输入文本和登录按钮。

图 1.1.6.1 - XML 布局
2. 现在是编写代码的时候了,我创建了一个静态用户并尝试登录。如果数据有效,则将用户ID保存到Shared Preferences中并跳转到个人资料页面;如果用户已登录,则直接跳转到个人资料页面。
SharedPreferences _SharedPref = getApplicationContext()
                .getSharedPreferences("MyAppData", 0);
final Editor _SharedPrefEditor = _SharedPref.edit();
// Check if user already logged in
int UserKey = _SharedPref.getInt("UserKey", -1);
if (UserKey != -1) {
      Intent i = new Intent(MainActivity.this, Profile.class);
      startActivity(i);
}
// Linking XML to Code
final EditText txt_Email = (EditText) findViewById(R.id.Email);
final EditText txt_Password = (EditText) findViewById(R.id.Password);
Button btn_Login = (Button) findViewById(R.id.btnLogin);
btn_Login.setOnClickListener(new OnClickListener() {
      public void onClick(View arg0) {
          // TODO Auto-generated method stub
          int UserID = IsLogin(txt_Email.getText().toString(),
                        txt_Password.getText().toString());
          if (UserID != -1) {
               _SharedPrefEditor.putInt("UserKey", UserID);
               _SharedPrefEditor.commit();
               Intent i = new Intent(MainActivity.this, Profile.class);
               startActivity(i);
          } else {
               Toast msg = Toast.makeText(getApplicationContext(),
                            "Login Failed", Toast.LENGTH_LONG);
               msg.show();
          }
    }
});
3. 尝试关闭并重新打开应用程序,它应该会直接将您重定向到个人资料页面,而无需再次输入您的电子邮件和密码,除非您已注销。使用Shared Preferences中保存的用户ID来加载用户的相关信息,例如我们示例中的用户名。
// Load Shared Preference
SharedPreferences _SharedPref = getApplicationContext()
                .getSharedPreferences("MyAppData", 0);
final Editor _SharedPrefEditor = _SharedPref.edit();
int UserKey = _SharedPref.getInt("UserKey", -1);
        
//If User is logged In and Display User Data
if (UserKey != -1) {
      if (User._CurrentUser.UserID == UserKey) {
            TextView txt_Name = (TextView) findViewById(R.id.txtUsername);
            txt_Name.setText(User._CurrentUser.Name);
      }
} else {
     // Go back to MainActivity Activity
     Intent i = new Intent(Profile.this, MainActivity.class);
     startActivity(i);
}

图 1.1.6.2 - 已登录屏幕
4. 尝试注销并再次打开应用程序
//Logout Button
Button btn_Logout = (Button)findViewById(R.id.btnLogout);
btn_Logout.setOnClickListener(new OnClickListener() {
            
     @Override
     public void onClick(View arg0) {
          // TODO Auto-generated method stub
          _SharedPrefEditor.remove("UserKey");
          _SharedPrefEditor.commit();
                
         // Go back to MainActivity Activity
         Intent i = new Intent(Profile.this, MainActivity.class);
         startActivity(i);
     }
});
您可以从以下位置下载完整代码:下载SharedPreferences.zip
1.7 - 提示
您可以使用 Eclipse IDE - DDMS 查看、编辑和创建新的共享偏好设置文件
1. 在模拟器上运行您的应用程序
2. 从菜单中选择DDMS,然后选择您的模拟器并选择文件管理器

图 1.1.7.1 - DDMS 屏幕
3. 您应该看到类似的图片,然后选择 data->data

图 1.1.7.2 - 文件浏览器
4. 选择 com.example.sharedpreferences (您的项目)
5. 在这里您可以看到您的共享偏好设置文件,您可以使用提供的工具编辑/添加/删除/导入/导出任何文件

图 1.1.7.3 - 控制器
2. 内部存储
2.1 - 它是什么?
内部存储是存储选项之一。您可以将应用程序数据直接保存到设备的内部存储中,这些数据会持久存储,这意味着即使您关闭应用程序,数据仍然存在,并且只有通过从设备卸载应用程序才能删除数据。此外,保存在设备内部存储中的数据仅对该应用程序私有,设备上的任何其他应用程序都无法访问。
2.2 - 我应该何时使用内部存储?
内部存储的数据量取决于设备,因此不要尝试保存大型持久文件,因为如果设备空间不足,它可能会使您的应用程序崩溃。最好将任何数据保持在1M以下,例如文本文件或xml文件。
2.3 - 如何存储数据?
首先,您必须创建一个 **FileOutputStream** 对象,我们将用它以 **字节** 的形式将数据写入文件,您必须打开/创建一个文件并使用 **.write()** 函数以字节的形式写入数据,并且 **切勿忘记关闭 FileOutputStream**,如以下代码所示
FileOutputStream _FileOutput;
String FileName = "Note";
String FileInput = txt_EditNote.getText().toString();
try {
   //by default mode is private
   _FileOutput = openFileOutput(FileName, 0);
   _FileOutput.write(FileInput.getBytes());
   _FileOutput.close();
   Toast.makeText(getApplicationContext(),
             FileName + " saved", Toast.LENGTH_LONG).show();
} catch (FileNotFoundException e) {
     e.printStackTrace();
} catch (IOException e) {
     e.printStackTrace();
}
2.4 - 如何检索数据?
您需要创建一个 **StringBuffer** 来保存您从数据中读取的每一行,并创建一个 **BufferReader** 对象来打开文件并逐行读取,并且 **切勿忘记关闭 BufferReader**,如以下代码所示
StringBuffer stringBuffer = new StringBuffer();    
try {    
   BufferedReader inputReader = new BufferedReader(new InputStreamReader(  
        openFileInput(FileName)));  
   String inputString;                
   while ((inputString = inputReader.readLine()) != null) {  
            stringBuffer.append(inputString + "\n");  
   }  
                          
   inputReader.close();
} catch (IOException e) {  
      e.printStackTrace();  
}  
2.5 - 如何删除内部存储数据?
这是一个非常简单的步骤,您只需要要删除的文件名和文件目录。
File dir = getFilesDir(); //Use this as FileDir for Internal Storage File file = new File(dir, "filename"); file.delete();
2.6 - 示例
我将提供一个示例,结合以上所有要点以更好地解释。在这个示例中,我们将创建一个笔记并将其保存到设备的内部存储中,然后显示所有笔记并显示其内容。
1. 我创建了我的 XML 布局,其中包含标题、笔记输入文本、保存按钮和加载所有按钮。

图 1.2.6.1 - XML 布局
2. 如所示,我们需要添加一个输入并将其保存到内部存储中的文件中。我初始化了一个文件名字符串和一个FileOutputStream,如上所述,用于将我们的数据以字节形式保存到名为Note的文件中。
final String FileName = "Note";
// On Save Click
btn_Save.setOnClickListener(new OnClickListener() {
     @Override
     public void onClick(View arg0) {
         // TODO Auto-generated method stub
         FileOutputStream _FileOutput;
         String FileInput = txt_EditNote.getText().toString();
         try {
             //by default mode is private
             _FileOutput = openFileOutput(FileName, 0);
             _FileOutput.write(FileInput.getBytes());
             _FileOutput.close();
             Toast.makeText(getApplicationContext(),
                            FileName + " saved", Toast.LENGTH_LONG).show();
          } catch (FileNotFoundException e) {
                    e.printStackTrace();
          } catch (IOException e) {
                    e.printStackTrace();
          }
    }
});

图 1.2.6.2 - 测试应用程序 #1
3. 我创建了另一个活动来加载所有 **此应用程序的文件**,**并且只有此应用程序才能访问这些文件,因为它们是私有的,无法从其他应用程序访问。** 在创建此活动时,我加载了所有文件,如下所示
SavedDataInInternalStorage = getApplicationContext().fileList();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
          android.R.layout.simple_list_item_1, SavedDataInInternalStorage);
_List.setAdapter(adapter);

图 1.2.6.3 - 显示所有笔记
4. 点击列表视图中的任何项目,它将显示该文件的内容,如下所述
_List.setOnItemClickListener(new OnItemClickListener() {
     @Override
     public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
                    long arg3) {
         // TODO Auto-generated method stub
         String FileName = ((TextView) arg1).getText().toString();
         StringBuffer stringBuffer = new StringBuffer();
         try {
            BufferedReader inputReader = new BufferedReader(
                            new InputStreamReader(openFileInput(FileName)));
            String inputString;
            while ((inputString = inputReader.readLine()) != null) {
                stringBuffer.append(inputString + "\n");
            }
            inputReader.close();
          } catch (IOException e) {
             e.printStackTrace();
          }
           Toast.makeText(getApplicationContext(),
                        stringBuffer.toString(), Toast.LENGTH_LONG).show();
     }
});

图 1.2.6.4 - 显示单个笔记信息
您可以从以下位置下载完整代码:下载InternalStorage.zip
2.7 - 提示
1. 检测内部存储空间
也许,您可能需要知道内部存储空间并决定将数据保存在内部存储还是外部存储。因此,我认为在保存数据之前了解设备上的可用空间非常重要。
您可以按照以下代码找到(鸣谢:链接)
public static String getAvailableInternalMemorySize() {
        File path = Environment.getDataDirectory();
        StatFs stat = new StatFs(path.getPath());
        long blockSize = stat.getBlockSize();
        long availableBlocks = stat.getAvailableBlocks();
        return formatSize(availableBlocks * blockSize);
}
    
public static String formatSize(long size) {
  String suffix = null;
  if (size >= 1024) {
     suffix = "KB";
     size /= 1024;
     if (size >= 1024) {
           suffix = "MB";
           size /= 1024;
     }
  }
  StringBuilder resultBuffer = new StringBuilder(Long.toString(size));
  int commaOffset = resultBuffer.length() - 3;
        while (commaOffset > 0) {
            resultBuffer.insert(commaOffset, ',');
            commaOffset -= 3;
        }
   if (suffix != null)
       resultBuffer.append(suffix);
   
   return resultBuffer.toString();
}
2. 使用 Eclipse IDE - DDMS 编辑/删除内部存储文件
1. 在模拟器上运行您的应用程序
2. 从菜单中选择DDMS,然后选择您的模拟器并选择文件管理器

图 1.2.7.1 - DDMS
3. 您应该看到类似的图片,然后选择 data->data

图 1.2.7.2 - 文件浏览器
4. 选择 com.example.internalstorage (您的项目)
5. 在这里您可以看到您的内部存储文件,您可以使用提供的工具编辑/添加/删除/导入/导出任何文件

图 1.2.7.3 - 通过 DDMS 显示所有文件
3. 外部存储
3.1 - 它是什么?
外部存储是存储选项之一。您可以将应用程序数据直接保存到设备的外部存储中,这些数据是全局可读的,并且可以在计算机上修改/传输。此外,保存在设备外部存储中的数据在用户挂载或移除外部媒体时可能不可用。
3.2 - 我应该何时使用外部存储?
每当您需要保存大文件(例如音频文件或任何不会导致应用程序崩溃且用户可以多次检索的大文件)时,或者当您希望通过不同应用程序共享文件(例如统计文件)时,都应使用外部存储。
3.3 - 如何存储数据?
在将数据存储到外部媒体之前,您必须通过在清单文件中允许写入外部媒体来授予应用程序执行此操作的权限。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.externalstorage"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
现在,您的应用程序已准备好将数据存储到外部媒体的以下目录之一中
- 音乐
- 图片
- 铃声
- 电影
- 闹钟
- Notifications
- 下载次数
如何访问这些文件之一?
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC, FileName);
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES, FileName);
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES, FileName);
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES, FileName);
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS, FileName);
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS, FileName);
- Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS, FileName);
您需要创建文件并将其保存到上述任何文件位置,如下例所示
FileOutputStream _FileOutput;
String FileInput = txt_EditNote.getText().toString();
try {
  // by default mode is private
  File myFile = new File(Environment.getExternalStoragePublicDirectory(
                                Environment.DIRECTORY_DOWNLOADS), FileName);
  myFile.createNewFile();
                     
  _FileOutput = new FileOutputStream(myFile); 
  _FileOutput.write(FileInput.getBytes());
  _FileOutput.close();
  Toast.makeText(getApplicationContext(),
                            FileName + " saved", Toast.LENGTH_LONG).show();
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}
3.4 - 如何检索数据?
要从外部存储中检索任何数据或文件,您必须访问该文件并读取其数据。例如,我将尝试加载保存在 `DIRECTORY_PICTURES` 中的图像并将其解码为位图图像
File file = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES, FileName); FileInputStream InputFileStream = new FileInputStream(file); Bitmap bitmap = BitmapFactory.decodeStream(InputFileStream); InputFileStream.close();
3.5 - 如何删除外部存储数据?
要删除外部存储数据,您必须引用特定文件,然后只需对此文件应用删除功能,如所述
File file = new File(Environment.getExternalStorageDirectory()
            .getAbsolutePath(), FileName);
file.delete();
4. SQLite 数据库
4.1 - 它是什么?
SQLite是一个开源数据库。它支持标准的SQL语法和视图等关系数据库特性。SQLite是一个非常轻量级的数据库,可以在任何移动平台上使用。
有关SQLite的更多信息:http://www.sqlite.org
下载SQLite Studio:http://sqlitestudio.pl/
4.2 - 我应该何时使用SQLite数据库?
它没有特定的用例,您可以在需要保存用户数据时使用它,例如从外部API获取数据,但是如果用户没有连接到互联网,您可以在离线模式下从数据库中获取相同的数据,或者保存用户搜索条件以便在用户尝试搜索时将其用作默认值。
4.3 - 示例
我认为对SQLite进行简要描述的最佳方式是直接进入一个示例。
我们的示例如下:我们有一组汽车及其描述以及其型号,如下面的数据库图所示

图 1.4.3.1 - 数据库模型
1. 创建模型类
1. 我们需要构建我们的模型类 - 创建您的第一个类模型 **CarModel**。始终创建3个构造函数并在您的类中设置私有变量
- 空构造函数
- 带所有字段作为参数的构造函数 **带ID**
- 带所有字段作为参数的构造函数 **不带ID**
public class CarModel {
   //Set Private Variables
    int _CarID;
    int _CarModelID;
    String _CarName;
    String _Description;
    String _Color;
    double _Price;
    
    // Empty constructor
    public CarModel(){
         
    }
     // constructor
     public CarModel(int CarID, int CarModelID, String CarName, String Description, String Color, double Price){
        this._CarID = CarID;
        this._CarModelID = CarModelID;
        this._CarName = CarName;
        this._Description = Description;
        this._Color = Color;
        this._Price = Price;
    }
     
   // constructor
   public CarModel(int CarModelID, String CarName, String Description, String Color, double Price){
        this._CarModelID = CarModelID;
        this._CarName = CarName;
        this._Description = Description;
        this._Color = Color;
        this._Price = Price;
    }
}
2. 我们需要为所有数据库列创建getter/setter,如下所示
// getting CarID
public int getCarID(){
   return this._CarID;
}
     
// setting CarID
public void setCarID(int Carid){
   this._CarID = Carid;
}
     
// getting CarModelID
public int getCarModelID(){
   return this._CarModelID;
}
     
// setting CarModelID
public void setCarModelID(int CarModelID){
   this._CarModelID = CarModelID;
}
    
// getting Car Name
public String getCarName(){
   return this._CarName;
}
     
// setting Car Name
public void setCarName(String CarName){
   this._CarName = CarName;
}
     
// getting Description
public String getDescription(){
   return this._Description;
}
     
// setting Description
public void setDescription(String Description){
   this._Description = Description;
}
    
// getting Color
public String getColor(){
   return this._Color;
}
     
// setting Color
public void setColor(String Color){
   this._Color = Color;
}
    
// getting Price
public double getPrice(){
   return this._Price;
}
     
// setting Price
public void setPrice(double Price){
   this._Price = Price;
}
3. 对模型类 **CarModelModel** 执行上述 2 个步骤
public class CarModelModel {
    //Set Private Variables
    int _ModelID;
    String _ModelName;
    
 // Empty constructor
    public CarModelModel(){
         
    }
    // constructor
    public CarModelModel(int ModelID, String ModelName){
        this._ModelID = ModelID;
        this._ModelName = ModelName;
    }
     
    // constructor
    public CarModelModel(String ModelName){
        this._ModelName = ModelName;
    }
    
 // getting ModelID
    public int getModelID(){
        return this._ModelID;
    }
     
    // setting ModelID
    public void setModelID(int ModelID){
        this._ModelID = ModelID;
    }
     
    // getting ModelName
    public String getModelName(){
        return this._ModelName;
    }
     
    // setting ModelName
    public void setModelName(String ModelName){
        this._ModelName = ModelName;
    }
}
2. 创建DB Handler类
1. 创建DB Handler类 **DBHandler**,然后继承 **SQLiteOpenHelper** 并添加未实现的方法
public class DBHandler extends SQLiteOpenHelper {
    public DBHandler(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub
        
    }
}
2. 除非我们添加一些静态数据,如 DB_NAME、DB_VERSION、表、列,否则您会收到错误。
//DB Inst
public static final int DB_VERSION = 1;
public static final String DB_NAME = "CarDB";
    
//DB Tables
public static final String TableCar = "car";
public static final String TableCarModel = "model";
    
//Columns as their Name in DB
public static final String Car_ID = "CarID";
public static final String Car_ModelID = "CarModelID";
public static final String Car_Name = "CarName";
public static final String Car_Description = "Description";
public static final String Car_Color = "Color";
public static final String Car_Price = "Price";
    
public static final String Model_ID = "ModelID";
public static final String Model_Name = "ModelName";
3. 我们必须实现 OnCreate 函数,它将在第一次为我们创建表,创建一个将以 SQL 语法创建表的字符串
String CREATE_CAR_TABLE = "CREATE TABLE " + TableCar + "("
         + Car_ID + " INTEGER PRIMARY KEY," 
         + Car_ModelID + " INTEGER," + Car_Name + " TEXT,"
         + Car_Description + " TEXT," + Car_Color + " TEXT,"
         + Car_Price + " double" + " )";
db.execSQL(CREATE_CAR_TABLE);
4. 对模型表做同样的操作
String CREATE_MODEL_TABLE = "CREATE TABLE " + TableCarModel + "("
         + Model_ID + " INTEGER PRIMARY KEY," 
         + Model_Name + " TEXT" + ")";
db.execSQL(CREATE_MODEL_TABLE);
5. OnUpgrade 的作用是什么?简单来说,它允许您在列名或类型更改或添加约束等任何更改的情况下重新创建数据库表。
//Drop Tables
db.execSQL("DROP TABLE IF EXISTS " + TableCar);
db.execSQL("DROP TABLE IF EXISTS " + TableCarModel);
        
// Add tables again
onCreate(db);
3. 创建表ViewModel以保存其CRUD(创建/读取/更新/删除)操作
1. 创建一个新类 **CarViewModel**,它将保存此表的CRUD操作并添加这些方法
public class CarViewModel {
   
    // Database fields
    private SQLiteDatabase database;
    private DBHandler dbHelper;
    private String[] CarTableColumns = { DBHandler.Car_ID,
            DBHandler.Car_ModelID, DBHandler.Car_Name,
            DBHandler.Car_Description, DBHandler.Car_Color,
            DBHandler.Car_Price};
    public CarViewModel(Context context) {
        dbHelper = new DBHandler(context);
    }
    public void open() throws SQLException {
        database = dbHelper.getWritableDatabase();
    }
    public void close() {
        dbHelper.close();
    }
    // Adding Car
    public void addCar(CarModel car) {}
    // Get Specific Car By ID
    public CarModel getCar(int id) {}
    // Get All Cars
    public List<CarModel> getAllCars() {}
    // Updating Car
    public int updateCar(CarModel car) {}
    // Deleting Car
    public void deleteCar(CarModel car) {}
}
3.1. CRUD - 添加汽车
我们需要创建一个ContentValues,它将保存我们所有的列值并将这个ContentValue保存到数据库中,如下所示
// Adding Car
public void addCar(CarModel car) 
{
   ContentValues values = new ContentValues();
   values.put(DBHandler.Car_ModelID, car._CarModelID);
   values.put(DBHandler.Car_Name, car._CarName);
   values.put(DBHandler.Car_Description, car._Description);
   values.put(DBHandler.Car_Color, car._Color);
   values.put(DBHandler.Car_Price, car._Price);
        
   // Inserting Row
   database.insert(DBHandler.TableCar, null, values);
   database.close(); // Closing database connection
}
3.2. CRUD - 通过ID读取汽车值
要从数据库中读取一条记录,我们需要创建一个名为游标(cursor)的东西,它将处理我们的查询并使用此记录在返回的记录之间导航。在以下代码中,我们尝试读取一辆带有其ID的汽车,并返回由 **DBHandler.TableCar** 确定的表中指定在 `new String[]` 中的一组列,并取决于指定的 **Car_ID**
Cursor cursor = database.query(DBHandler.TableCar, new String[] { DBHandler.Car_ID,
         DBHandler.Car_ModelID, DBHandler.Car_Name, DBHandler.Car_Description,
         DBHandler.Car_Color, DBHandler.Car_Price}, DBHandler.Car_ID + "=?",
         new String[] { String.valueOf(id) }, null, null, null, null);
这些空值代表什么?它们代表一些SQL特性,如group by、having、order by。

图 1.4.3.2 - Android支持的SQL特性
从数据库中检索项目的完整代码如下
// Get Specific Car By ID
public CarModel getCar(int id) {
        Cursor cursor = database.query(DBHandler.TableCar, new String[] { DBHandler.Car_ID,
                DBHandler.Car_ModelID, DBHandler.Car_Name, DBHandler.Car_Description,
                DBHandler.Car_Color, DBHandler.Car_Price}, DBHandler.Car_ID + "=?",
                new String[] { String.valueOf(id) }, null, null, null, null);
        if (cursor != null)
            cursor.moveToFirst();
 
        CarModel Car = new CarModel(Integer.parseInt(cursor.getString(0)),
                Integer.parseInt(cursor.getString(1)), cursor.getString(2),
                cursor.getString(3), cursor.getString(4),
                Double.parseDouble(cursor.getString(5)));
        
        // return Car
        return Car;
}
3.3. CRUD - 读取所有汽车
它与按ID加载相同,但这里我们将选择设置为null,因为我们不寻找列的特定值
// Get All Cars
public List<CarModel> getAllCars() {
        List<CarModel> Cars = new ArrayList<CarModel>();
        Cursor cursor = database.query(DBHandler.TableCar,
            CarTableColumns, null, null, null, null, null);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
          CarModel Car = new CarModel(Integer.parseInt(cursor.getString(0)),
                    Integer.parseInt(cursor.getString(1)), cursor.getString(2),
                    cursor.getString(3), cursor.getString(4),
                    Double.parseDouble(cursor.getString(5)));
          Cars.add(Car);
          cursor.moveToNext();
        }
        // make sure to close the cursor
        cursor.close();
        return Cars;
}
3.4. CRUD - 更新一辆汽车
这里我们需要更新数据库中现有记录的列,通过指定记录的新值,然后使用其ID更新数据库。
// Updating Car
public int updateCar(CarModel car) {
        ContentValues values = new ContentValues();
        values.put(DBHandler.Car_ModelID, car.getCarModelID());
        values.put(DBHandler.Car_Name, car.getCarName());
        values.put(DBHandler.Car_Description, car.getDescription());
        values.put(DBHandler.Car_Color, car.getColor());
        values.put(DBHandler.Car_Price, car.getPrice());
 
        // updating a row
        return database.update(DBHandler.TableCar, values, DBHandler.Car_ID + " = ?",
                new String[] { String.valueOf(car.getCarID()) });
}
3.5. CRUD - 删除一辆汽车
这是您在SQL中可以做的最简单的查询之一。只需使用delete从数据库中删除特定记录。
// Deleting Car
public void deleteCar(CarModel car) {
        long id = car.getCarID();
        database.delete(DBHandler.TableCar, DBHandler.Car_ID
            + " = " + id, null);
}
现在,我们需要为我们的模型类再次实现上述步骤。我将跳过这一部分,因为它相同且代码可用。
4. 创建用户与数据库的交互
我创建了我的第一个屏幕,其中包含一些按钮,这些按钮将应用上述CRUD操作

图 1.4.3.3 - XML 布局
1. 添加新车
正如我们在以下代码中看到的,我创建了一个保存按钮,点击时我们需要创建一个 CarViewModel 实例来调用 addCar 函数,并创建另一个 Car 对象,该对象将与其数据一起保存。
我创建了一个名为 `_Car` 的汽车对象,其型号为 `1`,名称为 `Aveo`,描述为 `I Love Aveo`,颜色为 `Red`,价格为 `100`。
然后是另一个 `CarViewModel` 对象,并在构造函数中传入我们的上下文作为参数,然后调用 `addCar` 函数。
btn_Save.setOnClickListener(new OnClickListener() {
            
   @Override
   public void onClick(View arg0) {
       // TODO Auto-generated method stub
       CarViewModel = new CarViewModel(getApplicationContext());
       CarViewModel.open();
                
       // save the new comment to the database
       CarModel _Car = new CarModel(1, "Aveo", "I Love Aveo", "Red", 100);
       CarViewModel.addCar(_Car);
                
       Toast.makeText(getApplicationContext(), "Added New Car", Toast.LENGTH_LONG).show();
   }
});

图 1.4.3.4 - 插入
2. 加载所有汽车
我们的下一个按钮是“加载所有汽车”屏幕,它将显示我们数据库中所有已保存的汽车。
btn_LoadACar.setOnClickListener(new OnClickListener() {
            
    @Override
    public void onClick(View v) {
         // TODO Auto-generated method stub
         CarViewModel = new CarViewModel(getApplicationContext());
         CarViewModel.open();
                
         List<CarModel> _List;
         _List = CarViewModel.getAllCars();
                
         Toast.makeText(getApplicationContext(), _List.get(0)._CarName, Toast.LENGTH_LONG).show();
    }
});

图 1.4.3.5 - 检索所有
3. 加载一辆汽车
它与加载所有汽车的步骤相同,但我们必须提供 Car_ID 才能加载特定汽车,但它与前几点应用相同的思想。这里我们使用我们现有的函数 getCar(Id) 来加载具有特定 Car_ID 的汽车,然后显示其名称和价格
btn_LoadACar.setOnClickListener(new OnClickListener() {
            
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        CarViewModel = new CarViewModel(getApplicationContext());
        CarViewModel.open();
                
        CarModel Car = CarViewModel.getCar(1);
                
         Toast.makeText(getApplicationContext(), Car._CarName + " " + Car._Price, Toast.LENGTH_LONG).show();
    }
});

图 1.4.3.6 - 按ID检索
4. 更新一辆汽车
接下来是您将经常需要使用的更新方法。我决定更新我们刚才加载的ID为1的汽车的价格,将其从100更改为200。

图 1.4.3.7 - 更新
5. 删除一辆汽车
我们需要测试的最后一个方法是从我们的应用程序数据库中删除一个对象。首先,我们需要加载要删除的对象,就像使用 `getCar(Id)` 加载汽车一样,然后将 `CarModel` 的这个对象传递给我们的函数 `deleteCar(object Car)`,如下所示
btn_DeletCar.setOnClickListener(new OnClickListener() {
            
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        CarViewModel = new CarViewModel(getApplicationContext());
        CarViewModel.open();
                
        CarModel Car = CarViewModel.getCar(1);           
        CarViewModel.deleteCar(Car);
                
        Toast.makeText(getApplicationContext(), "Deleted Car", Toast.LENGTH_LONG).show();
    }
});
您可以从以下位置下载完整代码:下载SQLite.zip
Web服务
1. kSOAP 2
kSOAP 是在 Android 应用程序中消费 Web 服务的著名库之一。它是一个轻量级且高效的库,并且是开源的,可以节省大量时间来实现您通过 Web 服务所需的功能。此外,它易于在您的应用程序中安装,我们将在接下来的步骤中看到。
1.1. 下载kSOAP库
我们的第一步是下载kSOAP库,将其添加到我们项目中的lib文件夹中。
kSOAP项目页面:kSOAP页面 或您可以直接从以下位置下载我们将使用的库:kSOAP 2 V 3.1.1
1.2. 将kSOAP库包含到我们的项目中
我们创建了一个名为 kSOAP 的新项目,它将包含我们的示例。首先,我们需要在清单文件中为我们的应用程序添加互联网访问权限。
1. 打开您的应用程序清单文件并添加互联网访问权限
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.ksoap"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-permission android:name="android.permission.INTERNET"/>
2. 现在,是时候将 kSOAP 库添加到我们的项目中了。右键单击您的项目 -> 属性

图 2.1.2.1 - 属性窗口
3. 从左侧栏中,选择 **Java Build Path -> Libraries**

图 2.1.2.2 - 库
4. 从右侧面板选择外部Jar包,然后导航到您已下载的kSOAP库

图 2.1.2.3 - 添加 kSOAP 库
5. 点击“确定”,**但我们还没有完成**,经过一些工作并尝试运行您的应用程序后,您将收到一个错误,显示“java.lang.NoClassDefFoundError”,因为还有一个步骤需要完成
6. 我们需要将我们的 .jar 文件添加到我们项目的 Lib 文件夹中

图 2.1.2.4 - Libs 文件夹
7. 将您的jar文件拖放到Lib文件夹中并选择“复制文件”
8. 现在我们已经将库添加到我们的项目中。
1.3. 创建一个响应
要使用 kSOAP 发送请求,我们需要使用 **HttpTransportSE** 创建一个 HTTP 请求,但在创建 HTTP 请求之前,我们需要了解一些要点。
我将使用W3schools Web服务来检索数据:W3schools Web服务
1. 对于我们将使用的任何服务,我们需要提取一些关于它的信息,例如 **webservice namespace** 和 **SOAP ActionName**。打开上面的链接 -> 点击服务描述

图 2.1.3.1 - 温度转换器网站
2. 您应该看到类似此图片的内容,滚动到底部后,您可以提取命名空间和 SOAP 动作名称,如图片中带下划线的部分所示

图 2.1.3.2 - 温度转换器服务描述
3. 现在是时候将这些变量添加到项目中了。在 MainActivity 的 onCreate() 函数中,只需添加这些变量
private static final String NAMESPACE = "https://w3schools.org.cn/webservices/"; private static final String URL = "https://w3schools.org.cn/webservices/tempconvert.asmx?wsdl"; private static final String SOAP_ACTION = "https://w3schools.org.cn/webservices/FahrenheitToCelsius"; private static final String METHOD_NAME = "FahrenheitToCelsius";
4. 我们需要使用这些变量创建请求。首先,我们必须创建一个 SOAP 对象,并分配所有要发送的参数,并将 SOAP 对象包含到 OutputSOAPObject 中。
String SoapResult = null;
                    
SoapObject SOAPRequest = new SoapObject(NAMESPACE, METHOD_NAME);
//Add Parameter
SOAPRequest.addProperty("Fahrenheit", "100.0");
                    
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
                            SoapEnvelope.VER11);
envelope.setOutputSoapObject(SOAPRequest);
envelope.dotNet = true;
我怎么知道参数名称?- 只需打开您要调用的Web服务,您就可以确定它需要的所有参数。

图 2.1.3.3 - FahrenheitToCelsius Web 方法信息
5. 让我们使用HTTP请求调用web服务,只需将SoapSerilizationEnvelope分配给HTTP请求,它将支持所有其余部分,然后将结果显示在TextView中
HttpTransportSE ht = new HttpTransportSE(URL);
ht.call(SOAP_ACTION, envelope);
                    
// Get the SoapResult from the envelope body.
if (envelope.bodyIn instanceof SoapFault) {
      SoapResult = ((SoapFault) envelope.bodyIn).faultstring;
} else {
      SoapObject resultsRequestSOAP = (SoapObject) envelope.bodyIn;
      SoapResult = resultsRequestSOAP.getProperty(0).toString();
}
                    
final String str = SoapResult;
runOnUiThread(new Runnable() {
  public void run() {
      txt_Input.setText(str);
  }
});
6. **我们还没完。** 如果我们只是简单地将上面的代码添加到主线程中,应用程序将会崩溃,因为我们让主线程空闲等待web服务的响应,这就是为什么我们需要在从主线程继承的另一个线程中应用代码。为了实现这一点,我们需要创建一个新线程
Thread SOAPThread = new Thread() {
     @Override
     public void run() {
     } 
};
SOAPThread.start();

图 2.1.3.4 - 温度转换器示例测试
您可以从以下位置下载完整代码:下载kSOAP.zip
1.4. JSON 解析器
如果我们使用一个返回多列而不是单个值的Web服务,该怎么办?这就是我们使用JSON解析器的原因,它可以解析并遍历JSON输入,并返回我们需要从JSON中检索的所有列。
因此,**JSON解析器** 意味着从这个字符串中提取我们需要的内容。我们如何实现这一点?- 很容易,我们只需要使用 **JSONObject** 来通过解析json树来检索数据。
考虑以下json输入
{
"User":
   {
      "Name":"Ahmed Alaa ElDin",
      "Title":"Software Engineer",
      "Company":"Enozom Software Company",
      "Copyrights" : "CodeProject"
   }
}
我们需要将这些数据分别提取到不同的变量中
JSONObject DataReader;
try {
   //Hold Input String "Data" inside JSONObject 
   DataReader = new JSONObject(Data);
   //Read an object inside Data that might contain multiple objects , and here is called User
   JSONObject User  = DataReader.getJSONObject("User");
   //Hold Columns inside variables as below
   String Name = User.getString("Name");
   String Title = User.getString("Title");
   String Company = User.getString("Company");
   String Copyright = User.getString("Copyrights");
   
   //Set Data on the screen
   txt_Name.setText("Name : " + Name);
   txt_Name.setText("Name : " + Name);
   txt_Title.setText("Title : " + Title);
   txt_Company.setText("Company : " + Company);
   txt_Copyrights.setText("Copyrights : " + Copyright);
} catch (JSONException e) {
            // TODO Auto-generated catch block
}

您可以从以下位置下载完整代码:下载JSONParser.zip
关注点
我建议更多地研究另一种名为 **Android Restful WebService** 的 Web 服务。它是另一种使用 Web 服务获取/保存数据的方法。
历史
1. 文章发布 - 2014年9月10日
2. 更新 #1:添加 JSON 解析器 - 2014年9月13日


