之前全部应用都不会涉及到数据储存,这次终于开始了.最简单就是SharedPreferences,当然他储存起来相当简单,还是回到之前的登录框App里.

我做一个密码验证的类,通过SharedPreferences保存简单数据,这里保存的数据是明文的,也就是玩玩.
public class PasswordManager {
private static final String PREF_NAME = "AppPasswordPrefs";
private static final String PASSWORD_KEY = "user_password";
private static final int PASSWORD_LENGTH = 4;
private final SharedPreferences sharedPreferences;
private final Context context;
public PasswordManager(Context context) {
this.context = context;
this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
// 检查是否是首次启动
public boolean isFirstLaunch() {
return !sharedPreferences.contains(PASSWORD_KEY);
}
// 生成随机4位数字密码
private String generateRandomPassword() {
// 自行实现
}
// 保存密码到SharedPreferences
private void savePassword(String password) {
sharedPreferences.edit()
.putString(PASSWORD_KEY, password)
.apply();
}
// 获取保存的密码
public String getSavedPassword() {
return sharedPreferences.getString(PASSWORD_KEY, null);
}
// 显示密码对话框
public void showPasswordDialog() {
String password = generateRandomPassword();
savePassword(password);
// 自行实现
}
// 验证密码
public boolean verifyPassword(String inputPassword) {
String savedPassword = getSavedPassword();
return savedPassword != null && savedPassword.equals(inputPassword);
}
}
然后在主程序中调用.
protected void onCreate(Bundle savedInstanceState) {
btn_login = findViewById(R.id.btn_login);
btn_login.setOnClickListener(this);
passwordManager = new PasswordManager(this);
if(passwordManager.isFirstLaunch()){
passwordManager.showPasswordDialog();
}
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.btn_login) {
EditText et_password = findViewById(R.id.et_password);
if(passwordManager.verifyPassword(et_password.getText().toString())) {
Toast.makeText(this,"密码正确",Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this,"正确密码应该是" + passwordManager.getSavedPassword(),Toast.LENGTH_LONG).show();
}
}
}
这里提一个之前的知识点,我们一般是验证密码OK后会跳转到另一个Activity,之后会记录用户曾经输入的密码到数据库里,只有下一次打开App就不要再验证密码了,这时候启动另一个Activity要清空活动堆栈.不然用户按返回又回到登录界面,岂不是感觉很奇怪.
清空堆栈的方法
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
另外SharedPreferences只能存简单小量数据,因为他是加载到内存中进行的,如果要储存更多数据,可以考虑用DataStore代替,因为他用起来也是差不多的,对于更多的数据,就应该存数据库了,大多数安卓App都会运行着SQLite数据库.因为我是速通Android,我假设你看我这个笔记时候也是有SQL基础的了,我不会再细分讲这个,直接说一下如何使用数据库.在Android中,通常用SQLiteOpenHelper来方便管理数据库.
在说数据库储存前,先要说一下文件储存,因为我打算做的应用和这个有关,文件储存是需要权限的.
要先在AndroidManifest写入需要的权限.
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
然后动态申请权限
public static boolean requestFilePermission(Activity activity, String permission) {
// 检查是否已经拥有权限
if (ContextCompat.checkSelfPermission(activity, permission)
== PackageManager.PERMISSION_GRANTED) {
return true;
}
// 如果没有权限,则动态请求
ActivityCompat.requestPermissions(activity, new String[]{permission},
getRequestCodeForPermission(permission));
return false;
}
发起一个意图来选择图片.
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickImageLauncher.launch(intent);
private final ActivityResultLauncher<Intent> pickImageLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
handleImageSelection(result.getData());
}
});
图片如何处理我这里暂且不说,因为到这一步有了图片的路径和权限,和普通的Java读写文件就没有区别了,说一下数据库需要注意的.数据库最好使用单例模式.重写onCreate和onUpgrade,这两个会在App升级和数据库首次创建时候发生,之后就可以使用增删查改,我这里每次操作都会打开关闭数据库,如果高频访问,当然可以只打开一次,操作完成再慢慢关闭.
public class ImageDbHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "ImageDatabase.db";
private static final int DATABASE_VERSION = 1;
// 表名和列名
public static final String TABLE_IMAGES = "images";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_MD5 = "md5";
public static final String COLUMN_PATH = "path";
public static final String COLUMN_SIZE = "size";
public static final String COLUMN_WIDTH = "width";
public static final String COLUMN_HEIGHT = "height";
public static final String COLUMN_DATE_ADDED = "date_added";
// 单例实例
private static ImageDbHelper instance;
// 创建表的SQL语句
private static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_IMAGES + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_MD5 + " TEXT UNIQUE, " +
COLUMN_PATH + " TEXT, " +
COLUMN_SIZE + " INTEGER, " +
COLUMN_WIDTH + " INTEGER, " +
COLUMN_HEIGHT + " INTEGER, " +
COLUMN_DATE_ADDED + " INTEGER" +
")";
// 私有构造函数
private ImageDbHelper(Context context) {
super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION);
}
// 获取单例
public static synchronized ImageDbHelper getInstance(Context context) {
if (instance == null) {
instance = new ImageDbHelper(context);
}
return instance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_IMAGES);
onCreate(db);
}
// 插入图片信息
public long insertImage(ImageInfo imageInfo) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_MD5, imageInfo.getMd5());
values.put(COLUMN_PATH, imageInfo.getPath());
values.put(COLUMN_SIZE, imageInfo.getSize());
values.put(COLUMN_WIDTH, imageInfo.getWidth());
values.put(COLUMN_HEIGHT, imageInfo.getHeight());
values.put(COLUMN_DATE_ADDED, System.currentTimeMillis());
long id = db.insertWithOnConflict(TABLE_IMAGES, null, values,
SQLiteDatabase.CONFLICT_IGNORE);
db.close();
return id;
}
// 根据MD5查询图片是否存在
public boolean isImageExists(String md5) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_IMAGES,
new String[]{COLUMN_ID},
COLUMN_MD5 + "=?",
new String[]{md5},
null, null, null);
boolean exists = cursor.getCount() > 0;
cursor.close();
db.close();
return exists;
}
// 获取所有图片信息
@SuppressLint("Range")
public List<ImageInfo> getAllImages() {
List<ImageInfo> imageList = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.query(TABLE_IMAGES,
null, null, null, null, null, COLUMN_DATE_ADDED + " DESC");
if (cursor.moveToFirst()) {
do {
ImageInfo image = new ImageInfo();
image.setId(cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
image.setMd5(cursor.getString(cursor.getColumnIndex(COLUMN_MD5)));
image.setPath(cursor.getString(cursor.getColumnIndex(COLUMN_PATH)));
image.setSize(cursor.getLong(cursor.getColumnIndex(COLUMN_SIZE)));
image.setWidth(cursor.getInt(cursor.getColumnIndex(COLUMN_WIDTH)));
image.setHeight(cursor.getInt(cursor.getColumnIndex(COLUMN_HEIGHT)));
image.setDateAdded(cursor.getLong(cursor.getColumnIndex(COLUMN_DATE_ADDED)));
imageList.add(image);
} while (cursor.moveToNext());
}
cursor.close();
db.close();
return imageList;
}
// 根据ID删除图片
public void deleteImage(long id) {
SQLiteDatabase db = this.getWritableDatabase();
db.delete(TABLE_IMAGES, COLUMN_ID + "=?", new String[]{String.valueOf(id)});
db.close();
}
// 更新图片信息
public int updateImage(ImageInfo imageInfo) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_PATH, imageInfo.getPath());
values.put(COLUMN_SIZE, imageInfo.getSize());
values.put(COLUMN_WIDTH, imageInfo.getWidth());
values.put(COLUMN_HEIGHT, imageInfo.getHeight());
return db.update(TABLE_IMAGES, values,
COLUMN_ID + "=?",
new String[]{String.valueOf(imageInfo.getId())});
}
}
存了几个图后读出来.
另外数据可以使用ORM库继续简化.
具体代码可以参考 https://gist.github.com/nickfox-taterli/6ab1c3c04b6b5e8204485f88159cc09a
我看到网上有人推荐一个方法,重写应用组件的一些方法,对数据库统一打开,对高频全局关键数据进行储存,这也是一个办法.Activity是会随着不断访问生成和销毁,但是Application只要程序还在,他就还在.具体在AndroidMainfest.xml的Application增加上name节点.
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication"
android:name=".MainApplication"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
写一个类来重写特定方法.
public class MainApplication extends Application {
@Override
public void onCreate(){
super.onCreate();
}
@Override
public void onTerminate(){
super.onTerminate();
}
}
再按照单例的方式,之后不同的Activity都可以获取Application实例,使用他的方法.
public class MainApplication extends Application {
private static MainApplication app;
public static MainApplication getInstance(){
return app;
}
@Override
public void onCreate(){
super.onCreate();
app = this;
}
}
如果引入包过多,可能需要修改其继承为MultiDexApplication.
上一节说到广播时候,可以跨越App进行通信,但是如果要传递大量数据,还是使用ContentProvider更合适一些.其完整组件其实是包含ContentProvider,ContentResolver,ContentObserver三个部分,其中ContentProvider是抽象类,所以需要自行实现.
在菜单New->Other->Content Provider可以快速新建一个,比如我打算创建一个ImgInfoProvider.
他创建了几个需要我们进行实现的方法,这里和数据库上是一一对应的,稍微实现一下也不是很麻烦,这里给出调用的例子.
// 插入图片信息
ContentValues values = new ContentValues();
values.put(ImageDbHelper.COLUMN_MD5, "****************");
values.put(ImageDbHelper.COLUMN_PATH, "/*****/*****/*****/*****.png");
values.put(ImageDbHelper.COLUMN_SIZE, 1024);
values.put(ImageDbHelper.COLUMN_WIDTH, 1920);
values.put(ImageDbHelper.COLUMN_HEIGHT, 1080);
getContentResolver().insert(ImgInfoProvider.CONTENT_URI, values);
// 查询所有图片
Cursor c = getContentResolver().query(
ImgInfoProvider.CONTENT_URI,
null, null, null,
ImageDbHelper.COLUMN_DATE_ADDED + " DESC");
不过实际开发中,很少App开放接口给别人读取,所以我们做更实际的例子,读取短信,监听短信,另外在我实现的过程中,发现小米通知类短信的权限是额外需要打开的,不然可能读不到验证码之类的,并且App必须在前台.下面是一个简单例子.
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 100;
private TextView tvTrafficInfo;
private Button btnQuery;
private SmsObserver smsObserver;
private boolean checkPermissions() {
return ActivityCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.SEND_SMS, Manifest.permission.READ_SMS},
PERMISSION_REQUEST_CODE
);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
sendTrafficQuerySms();
} else {
Toast.makeText(this, "需要短信权限才能查询流量", Toast.LENGTH_SHORT).show();
}
}
}
private void sendTrafficQuerySms() {
try {
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage("10010", null, "10010", null, null); // 电信查询流量指令
Toast.makeText(this, "已发送查询短信", Toast.LENGTH_SHORT).show();
// 注册短信观察者
registerSmsObserver();
} catch (Exception e) {
Toast.makeText(this, "发送短信失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void registerSmsObserver() {
if (smsObserver == null) {
ContentResolver resolver = getContentResolver();
smsObserver = new SmsObserver(new Handler());
resolver.registerContentObserver(
Uri.parse("content://sms/inbox"),
true,
smsObserver
);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
tvTrafficInfo = findViewById(R.id.tvTrafficInfo);
btnQuery = findViewById(R.id.btnQuery);
btnQuery.setOnClickListener(v -> {
if (checkPermissions()) {
sendTrafficQuerySms();
} else {
requestPermissions();
}
});
}
private class SmsObserver extends ContentObserver {
public SmsObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
checkForTrafficSms();
}
private void checkForTrafficSms() {
Cursor cursor = null;
try {
ContentResolver resolver = getContentResolver();
cursor = resolver.query(
Uri.parse("content://sms/inbox"),
new String[]{"body", "address"},
"address = ?",
new String[]{"10010"}, // 电信回复号码
"date DESC LIMIT 5"
);
if (cursor != null && cursor.moveToFirst()) {
String smsBody = cursor.getString(0);
if (smsBody.contains("剩余流量")) { // 根据实际短信内容调整
runOnUiThread(() -> tvTrafficInfo.setText("流量信息: " + smsBody));
// 已经获取到自己的信息了,取消监听.
getContentResolver().unregisterContentObserver(smsObserver);
smsObserver = null;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null) {
cursor.close();
}
}
}
}
}