Android -- (12) ,数据的存储

解决Android应用发布模式下的加密存储问题 摘要:本文针对Android应用在发布模式下的加密存储崩溃问题,特别是NoSuchFieldError异常,提供了详细的解决方案。关键步骤包括创建ProGuard配置文件、添加保留规则确保加密类不被混淆、配置选择性备份及调整Android 12+的数据提取规则。通过正确配置ProGuard和适配不同Android版本特性,能有效解决加密存储功能在发布模式下的兼容性问题,保障应用稳定运行和用户数据安全。(150字) 阅读详情

2013/1/31


一,数据的存储

数据的存储是很重要的,由于某些数据要在未来reuse。所以有必要将数据保存起来。
安卓里面有三种基本保存数据的办法:
(1)shared preferences机制,用来保存一小撮数据。
(2)传统的File System
(3)通过sqlite的支持用DB来存储数据

1.1,存储和载入用户的配置

一般来说,以上三种方法都可以用来存储用户配置,不过后两者明显有点大题小做了。我们可以简单的用SharedPreferences对象来存储一对对的name/value的值,这些值会自动的存到一个xml文件中去。

1.1.1 存储用户配置

首先我们要创建一个xml文件,里面是你想要存储的配置的类型等等等:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="Category 1">
        <CheckBoxPreference
            android:title="CheckBox"
            android:defaultValue="false"
            android:summary="True Or False"
            android:key="checkboxPref"
            />
    </PreferenceCategory>
    <PreferenceCategory android:title="Category 2">
        <EditTextPreference
            android:summary="ENter a text"
            android:defaultValue="[enter a string here]"
            android:title="Edit text"
            android:key="editTextPref"
            />
       	<RingtonePreference
       	    android:summary="select a ringtone"
       	    android:title="RingTones"
       	    android:key="ringtonepref"
       	    />
       	<PreferenceScreen
       	    android:title="Second Prefercence Screen"
       	    android:summary="Click here to go to second PreScreen"
       	    android:key="Second Pref Sceen"
       	    >
       	<EditTextPreference
       	    android:summary="enter a string"
       	    android:title="Edit text sp"
       	    android:key="secondEditTextPref"
       	    />
       	</PreferenceScreen>
    </PreferenceCategory>
</PreferenceScreen>

通过上面的xml文件,可以看出:
(1)有两个preference categories来为配置分组
(2)两个checkbox--checkboxpref和secondEdittextpref
(3)一个选择ringtone的选项,ringtonepref
(4)另外一个preferenceScreen包含另外一个配置eidtTExt

注意上面每个控件都有一个key的属性,这个属性是用来保存或取出对应的配置信息的关键字。会自动的存储用户选择的配置。

为了让os去展示出配置的页面,我们新建了一个活动,继承于PreferenceActivity:

public class AppPreferenceActivity extends PreferenceActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//---load the preferences from an XML file---
addPreferencesFromResource(R.xml.myapppreferences);
}
}

最后用之前讲过的intent方法来开始新活动:

Intent i = new Intent(“net.learn2develop.AppPreferenceActivity”);
startActivity(i);

注意也要在androidmainfest文件中加入这个活动的相关信息(之前的文章有讲)。这样就可以打开配置页面了。


OK,那么当你打开并且更改了一个配置之后,修改后的信息会自动保存在/data/data/net.learn2develop.UsingPreferences/shared_prefs里面


1.1.2取出用户的配置


--存储了之后读取用户的配置。

假设我们在主活动里面添加几个按钮来进入配置页面和保存读取存储的信息:

 <Button
        android:id="@+id/btnPreferences"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClickLoad"
        android:text="Load Preferences Screen" />

<Button
android:id="@+id/btnDisplayValues"
android:text="Display Preferences Values"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="onClickDisplay"/>
<EditText
android:id="@+id/txtString"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btnModifyValues"
android:text="Modify Preferences Values"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="onClickModify"/>

第一个button用来载入配置页面,第二个button用来展示存储的信息,然后是一个edittext,用来输入信息,最后button是用来存储信息,给第二个button展示用。

这几个button对应的方法如下:

 public void onClickLoad(View view) {
    	Intent i = new Intent("net.learn2develop.AppPreferenceActivity");
    	startActivity(i);
    	}
    public void onClickDisplay(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("net.learn2develop.UsingPreferences_preferences",
    	MODE_PRIVATE);
    	DisplayText(appPrefs.getString("editTextPref", ""));
    	}
    public void onClickModify(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("net.learn2develop.UsingPreferences_preferences",
    	MODE_PRIVATE);
    	SharedPreferences.Editor prefsEditor = appPrefs.edit();
    	prefsEditor.putString("editTextPref",
    	((EditText) findViewById(R.id.txtString)).getText().toString());
    	prefsEditor.commit();
    	}
    private void DisplayText(String str) {
    	Toast.makeText(getBaseContext(), str, Toast.LENGTH_LONG).show();
    	}

这几个方法分别用来载入配置页面(上一个小节中设置的配置页面),展示存储的配置信息,确定保存输入的配置信息。

在onClcikDIsplay方法中,我们用getSharedPreferences方法来获取之前存储的配置(这里是“net.learn2develop.UsingPreferences_preferences,”,用<PackageName>_preferences来指定),这里的MODE_private指定这个配置文件只能由创建他的活动打开。然后在用SharedPreferences对象的getString方法,传入想要获得的对象的key,再获得对应的对象。

在onCLickModify方法中,首先还是要获得一个SharedPreferences对象,然后获得它的SharedPreferences.Editor对象,用这个对象来存储对应的数据,该类的putString方法用的就是一对name/value的方法来存储值。

1.1.3自定义存储文件名字


--在上一个例子中,存储配置的文件是net.learn2develop.UsingPreferences_preferences,有点长而且不好记住。我们可以在配置选择的活动中(也就是上面的AppPreferenceActivity类中)的onCreate方法中添加如下代码:

	PreferenceManager prefMgr = getPreferenceManager();
    prefMgr.setSharedPreferencesName("YourName");

这样就把存储的xml文件改名为了YourName了。

在读取的时候只要把YourName当做参数提供到相应的方法中即可,如下:
public void onClickDisplay(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("YourName",
    	MODE_PRIVATE);
    	DisplayText(appPrefs.getString("editTextPref", ""));
    	}
    public void onClickModify(View view) {
    	SharedPreferences appPrefs =
    	getSharedPreferences("YourName",
    	MODE_PRIVATE);
    	SharedPreferences.Editor prefsEditor = appPrefs.edit();
    	prefsEditor.putString("editTextPref",
    	((EditText) findViewById(R.id.txtString)).getText().toString());
    	prefsEditor.commit();
    	}

就可以往自己定义的配置xml文件中读取配置了。


1.2 将数据存储到文件上面


--在上一种用SharedPreferences 对象来存储,我们可以用name/value这种数据对的形式来存储数据,下面介绍传统的文件系统存储。

1.2.1存到内部储存器中(internal Storage)

要把内容写入文件中,要用FileOutputStream类,这个类打开一个新文件,将输入的内容写入其中:
FileOutputStream fOut =
openFileOutput(“textfile.txt”,
MODE_WORLD_READABLE);
这样就把文件写入到了一个叫做textfile.txt的文件中了。这里除了MODE_WORLD_READABLe之外,还可以选择:MODE_PRIVATE(只能有创建文件的应用访问),MODE_APPEND(把内容追加到已有的文件中去),MODE_WORLD_WRITEABLE(所有的应用都可以访问)。

把文件创建好了之后,就要用OutputStreamWriter类把字符流转换为比特流:

OutputStreamWriter osw = new
OutputStreamWriter(fOut);

将这个OutputStreamWriter类跟一个FileOutputStream绑定起来之后,用他的write(String str)方法将字符串写入文件中去:

//---write the string to the file---
osw.write(str);
osw.flush();
osw.close();

为了确保字符都写入到文件中去了,可以用flush方法。最后用close方法来关闭文件。

对应的,读取文件要用FileInputStream类:

FileInputStream fIn =
openFileInput(“textfile.txt”);
其中openFileInput方法提供的是文件的名字(由于是内部存储空间,不用提供绝对路径)。
再用InputStreamReader跟对应的FileInputStream绑定起来:

InputStreamReader isr = new
InputStreamReader(fIn);

因为并不知道输入的字符串的长度,定义最大长度为100.可以用循环来读取字符串内容:

char[] inputBuffer = new char[READ_BLOCK_SIZE];
String s = “”;
int charRead;
while ((charRead = isr.read(inputBuffer))>0)
{
//---convert the chars to a String---
String readString =
String.copyValueOf(inputBuffer, 0,
charRead);
s += readString;
inputBuffer = new char[READ_BLOCK_SIZE];
}

其中read()方法读取一个字符,如果到末尾了就返回-1,
当存储了文件之后,可以到ddms去看看文件的内容。绝对路径是/data/data/net.learn2develop.Files/files/textFile.txt


1.2.2存储到sd卡上

--外部存储,大多数就是SDcard啦。

File sdCard = Environment.getExternalStorageDirectory();
File directory = new File (sdCard.getAbsolutePath() +
“/MyFiles”);
directory.mkdirs();
File file = new File(directory, “textfile.txt”);
FileOutputStream fOut = new FileOutputStream(file);
OutputStreamWriter osw = new
OutputStreamWriter(fOut);
//---write the string to the file---
osw.write(str);
osw.flush();
osw.close();

这里用import android.os.Environment;的Environment类中的getExternalStorageDirectory方法来获取外部存储的路径,这里是/mnt/sdcard,然后再在这个目录下面创建目录(MyFile)和文件(textFile.txt).然后就是用FileOutputStream类来写入文件。

读取方式也类似:
try
    	{
    		File sdCard=Environment.getExternalStorageDirectory();
    		File directory=new File(sdCard.getAbsolutePath()+"/MyFile");
    		File file=new File(directory,"textFile.txt");
    		FileInputStream fin=new FileInputStream(file);
    		InputStreamReader isr=new InputStreamReader(fin);
    		char[] inputBuffer = new char[READ_BLOCK_SIZE];
    		String s = "";
    		int charRead;
    		while((charRead=isr.read(inputBuffer))>0)
    		{
    			String readString =String.copyValueOf(inputBuffer,0,charRead);
    			s+=readString;
    			inputBuffer = new char[READ_BLOCK_SIZE];
    		}
    		textBox.setText(s);
    		Toast.makeText(getBaseContext(), "FIle load succ", Toast.LENGTH_SHORT).show();
    		
    	}catch (IOException ioe) {
    		ioe.printStackTrace();
    	}

也是用Environment类来获取外部路径的信息。再用InputStreamReader读入文件内容。

注意:要读取外部文件时候要再AndroidManifest中添加:<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />

1.2.3使用静态的文件。


--除了可以在程序运行的时候动态的添加文件之外,还可以在设计阶段往Package里面添加文件。 -- 例如添加某些帮助文件。

例如:在Package里面的/res中添加文件夹raw,然后再往下面添加textfile.txt.就可以在程序里面将该文件的内容读取了:(当然要敲对应的代码)
InputStream is = this.getResources().openRawResource(R.raw.textfile);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String str = null;
try {
while ((str = br.readLine()) != null) {
Toast.makeText(getBaseContext(),
str, Toast.LENGTH_SHORT).show();
}
is.close();
br.close();
} catch (IOException e) {
e.printStackTrace();
}

这里用getResources()(Activity类的)返回一个Resources的对象,然后用openRawResource方法打开raw目录下面的文件。


1.3创建和使用数据库

--存储大量相关的数据,还是数据库好。

--Android使用的是SQLite数据库,应用创建的数据库只能由它自己访问,别的应用都不能访问。

1.3.1创建DBAdapter helper类


--贴出代码如下:
package com.example.database;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DBAdapter {
	static final String KEY_ROWID="_id";
	static final String KEY_NAME="name";
	static final String KEY_EMAIL="email";
	static final String TAG="DBAdapter";
	static final String DATABASE_NAME = "MyDB";
	static final String DATABASE_TABLE = "contacts";
	static final int DATABASE_VERSION = 1;
	static final String DATABASE_CREATE =
			"create table contacts (_id integer primary key autoincrement, "
			+ "name text not null, email text not null);";
	final Context context;
	DatabaseHelper DBHelper;
	SQLiteDatabase db;
	public DBAdapter(Context ctx)
	{
		this.context=ctx;
		DBHelper=new DatabaseHelper(context);
		
	}
	private static class DatabaseHelper extends SQLiteOpenHelper
	{
		DatabaseHelper(Context context)
		{
			super(context,DATABASE_NAME,null,DATABASE_VERSION);
		}
		public void onCreate(SQLiteDatabase db)
		{
			try
			{
				db.execSQL(DATABASE_CREATE);
				
			}catch(SQLException e)
			{
				e.printStackTrace();
			}
		}
		public void onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)
		{
			Log.w(TAG, "Upgrading database from "+oldVersion+" to "+newVersion+" which will destory all data.");
			db.execSQL("DROP TABLE IF EXISTS contacts");
			onCreate(db);
		}
		
	}
	public DBAdapter open() throws SQLException
	{
		db = DBHelper.getWritableDatabase();
		return this;
	}
	public void close()
	{
		DBHelper.close();
	}
	public long insertContact(String name,String email)
	{
		ContentValues initialValues=new ContentValues();
		initialValues.put(KEY_NAME, name);
		initialValues.put(KEY_EMAIL, email);
		return db.insert(DATABASE_TABLE, null, initialValues);
		
	}
	public boolean deleteContact(long rowId)
	{
		return db.delete(DATABASE_TABLE,KEY_ROWID+"="+rowId,null) >0;
	}
	public Cursor getAllContacts()
	{
		return db.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME,
				KEY_EMAIL}, null, null, null, null, null);
	}
	public Cursor getContact(long rowId) throws SQLException
	{
		Cursor mCursor =
				db.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
				KEY_NAME, KEY_EMAIL}, KEY_ROWID + "=" + rowId, null,
				null, null, null, null);
				if (mCursor != null) {
				mCursor.moveToFirst();
				}
				return mCursor;
	}
	public boolean updateContact(long rowId, String name, String email)
	{
	ContentValues args = new ContentValues();
	args.put(KEY_NAME, name);
	args.put(KEY_EMAIL, email);
	return db.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;
	}
	
}

首先建议数据库表的一系列域(属性),然后再将创建数据库需要的sql语句写出来:

static final String KEY_ROWID = “_id”;
static final String KEY_NAME = “name”;
static final String KEY_EMAIL = “email”;
static final String TAG = “DBAdapter”;
static final String DATABASE_NAME = “MyDB”;
static final String DATABASE_TABLE = “contacts”;
static final int DATABASE_VERSION = 1;
static final String DATABASE_CREATE =
“create table contacts (_id integer primary key autoincrement, “
+ “name text not null, email text not null);”;

在DBAdapter类里面,我们创建了一个私有类--DatabaseHelper extends SQLiteOpenHelper,而SQLiteOpenHelper则是Android里面用于创建和管理数据库的类。我们重写了onCreate方法和onUpgrade方法:

private static class DatabaseHelper extends SQLiteOpenHelper
{
DatabaseHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db)
{
try {
db.execSQL(DATABASE_CREATE);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
Log.w(TAG, “Upgrading database from version “ + oldVersion + “ to “
+ newVersion + “, which will destroy all old data”);
db.execSQL(“DROP TABLE IF EXISTS contacts”);
onCreate(db);
}
}
onCreate方法创建一个数据库(如果原来数据库不存在的话),onUpgrade当数据库要被更新的时候被调用。这里只是简单的删除和重新创建数据库。

最后我们创建了一系列的操作数据库的函数:

//---opens the database---
public DBAdapter open() throws SQLException
{
db = DBHelper.getWritableDatabase();
return this;
}
//---closes the database---
public void close()
{
DBHelper.close();
}
//---insert a contact into the database---
public long insertContact(String name, String email)
{
ContentValues initialValues = new ContentValues();
initialValues.put(KEY_NAME, name);
initialValues.put(KEY_EMAIL, email);
return db.insert(DATABASE_TABLE, null, initialValues);
}
//---deletes a particular contact---
public boolean deleteContact(long rowId)
{
return db.delete(DATABASE_TABLE, KEY_ROWID + “=” + rowId, null) > 0;
}
//---retrieves all the contacts---
public Cursor getAllContacts()
{
return db.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_NAME,
KEY_EMAIL}, null, null, null, null, null);
}
//---retrieves a particular contact---
public Cursor getContact(long rowId) throws SQLException
{
Cursor mCursor =
db.query(true, DATABASE_TABLE, new String[] {KEY_ROWID,
KEY_NAME, KEY_EMAIL}, KEY_ROWID + “=” + rowId, null,
null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
}
return mCursor;
}
//---updates a contact---
public boolean updateContact(long rowId, String name, String email)
{
ContentValues args = new ContentValues();
args.put(KEY_NAME, name);
args.put(KEY_EMAIL, email);
return db.update(DATABASE_TABLE, args, KEY_ROWID + “=” + rowId, null) > 0;
}

注意上面的cursor变量返回一个游标,可以让用户遍历全部数据库的内容。用ContentValue类可以存储一对name/value变量,它的put方法可以插入一对不同数据类型的name/value的值。

其实DBAdapter就是为了让DBhelper更加方便的给用户使用而构造的一个类。于是我们可以直接用DBAdapter来创建和管理数据库:
public DBAdapter(Context ctx)
{
this.context = ctx;
DBHelper = new DatabaseHelper(context);
}

创建数据库=创建已给DBAdapter的实例。
其他操作同理。

1.3.2 往DB中添加查询数据


--往DB中添加数据操作。

        DBAdapter db = new DBAdapter(this);
	db.open();
        long id=db.insertContact("Wei-Meng Lee", "testData1");
        id=db.insertContact("Marry Jackson", "testData2");
        db.close();

先创建一个db例子,然后用其insertContact方法来往里插入数据即可。


--从DB中读取数据。

        db.open();
        Cursor c=db.getAllContacts();
        if(c.moveToFirst())
        {
        	do{
        		DisplayContact(c);
        		
        	}while(c.moveToNext());
        }
        

    public void DisplayContact(Cursor c)
    {
    	Toast.makeText(this, "id:"+c.getString(0)+"\n"+
						   "Name:"+c.getString(1)+"\n"+
						  "Email:"+c.getString(2)+"\n"
    			, Toast.LENGTH_SHORT).show();
    }

用cursor对象c可以遍历表中的每个triple,然后用moveToNext来去往下一个。在DisplayContact中用cursor的getString方法,传入列号,就能获得对应的数据。

--获取其中一个数据:

	db.open();
        Cursor c = db.getContact(number);
        if (c.moveToFirst())
        DisplayContact(c);
        else
        Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show();
        db.close();

可以用db的getContact方法来获取其中一行的数据,提供的是行号。然后cursor c里面就有一个对象昂,然后直接调用DisplayContact方法即可。注意行号从0开始。

--余下的还有删除和更新数据,这里只贴出来:

//更新数据
db.open();
if (db.updateContact(1, “Wei-Meng Lee”, “weimenglee@gmail.com”))
Toast.makeText(this, “Update successful.”, Toast.LENGTH_LONG).show();
else
Toast.makeText(this, “Update failed.”, Toast.LENGTH_LONG).show();
db.close();

//删除数据
db.open();
if (db.deleteContact(1))
Toast.makeText(this, “Delete successful.”, Toast.LENGTH_LONG).show();
else
Toast.makeText(this, “Delete failed.”, Toast.LENGTH_LONG).show();
db.close();

1.3.3预先创建数据库

--在实际的应用中,预先建立应用需要的数据库是比较高效的。可以用SQLite Database Browser等工具来帮助设计。
假设我们设计好了一个数据库文件mydb,只需要将其放入项目的assets文件夹中。再用如下代码读取即可:

DBAdapter db = newDBAdapter(this); 
try{
String destPath = “/data/data/”+ getPackageName() +
“/databases”;
File f = newFile(destPath);
if(!f.exists()) { 
f.mkdirs();
f.createNewFile();
//---copy the db from the assets folder into 
// the databases folder---
CopyDB(getBaseContext().getAssets().open(“mydb”),
newFileOutputStream(destPath + “/MyDB”));
}
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(IOException e) {
e.printStackTrace();
}
用到的copyDb函数如下:
public voidCopyDB(InputStream inputStream, 
OutputStream outputStream) throwsIOException {
//---copy 1K bytes at a time---
byte[] buffer = new byte[1024];
int length;
while((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
}
注意在这个函数里面直接用InputStream来读取源文件,并用outputStream来输出到指定文件里面。




Android12 解决应用使用Settings异常断电数据丢失的问题 后者通过精确控制需要保护的目录来减少性能损失,但是缺点是要非常清楚哪些目录需要保护,并 且不同文件系统的命令和标志都不一样,需要把你可能用到的所有文件系统的 ioctl 都写一遍,并且有 些文件系统可能不支持这种方式(f2fs 和 ext4 都是两种方式都支持,而 fat 则不支持 ioctl 方式)。在Android12中,Settings写操作在以下路径下,它是采用了AtomicFile的方式,这种方式可以避免断电文件损坏,但是避免不了刚写入的数据丢失。修改在最下面,主要修改底层文件系统。 阅读详情

相关推荐

QT在Android、Linux、windows平台采集声卡原始音频数据编码存储与播放

一、操作系统介绍 Linux系统:ubuntu18.04 64位 Android系统:Android 8.1/9.0 windows系统:win10 QT版本:5.12 声卡: win10电脑自带声卡、罗技USB摄像头声卡、Android手机自带声卡都可以获取声音数据 二、需求介绍 使用QT本身代码在linux平台、Android平台、windows平台实时获取声卡...

2521

Android数据存储的五种方法汇总

Android数据存储的五种方法汇总 本文介绍Android中的5种数据存储方式。 数据存储在开发中是使用最频繁的,在这里主要介绍Android平台中实现数据存储的5种方式,分别是: 1 使用SharedPreferences存储数据 2 文件存储数据 3 SQLite数据库存储数据 4 使用ContentProvider存储数据 5 网络存储数据 第一种: 使用SharedPreferences存储数据 SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比

qq_45146250的博客 2万+

android外部存储

android外部存储案例,有private files public files

Android——数据文件保存

创建工具类FileSaveLogin,在该类中实现Login账号和密码的存储与读取功能,使用文件存储的方式,保存与读取账号与密码信息。运行程序,输入账号和密码信息,点击“登录”按钮,实现登录功能。通过线性布局和相对布局来搭建一个用户登录界面,界面可自主设计,但至少需要包括以下控件:1个ImageView控件、2个TextView控件;□验证性 □演示性 □设计性 R。下面的蓝色和红色的字是从data.txt中读取的信息。保存成功是将账号和密码成功存入data.txt中了。

qq_64628470的博客 2362

代码实现 —— 基于 STM32 的可见光通信系统课程设计

基于STM32F103ZET6,源码如需移植,需要修改定时器捕获通道引脚,及定时器,如果你也有这个课设任务,希望对你有所帮助。

不善挽留 1万+

基于改进Unet的多场景水果图像分割与分类研究

本文针对复杂场景下的水果图像分割问题,提出了一种改进Unet模型。通过构建包含8类水果、3种成熟度及多种干扰场景的数据集,采用ResNet50与MobileNetV3作为骨干网络,并在跳跃连接层集成CBAM注意力机制。实验表明,改进模型在测试集上达到91.6%的mIoU,推理速度提升23%,特别在重叠水果和低光照场景表现突出。研究为农业视觉检测提供了有效解决方案。

AI的内部世界的博客 304

Android 保存图片到本地 兼容Android 10+ ,12

适配Android12

qq_33209777的博客 2146

深入理解 Terraform 中的资源更新与管理

摘要:本文探讨Terraform处理AWS安全组名称变更时的行为。当修改不可变属性(如安全组名称)时,Terraform会创建新资源并删除旧资源。通过状态文件比对所需状态与实际状态,Terraform对不可变属性采用"先创建后删除"策略,对可变属性则直接更新。实例演示了修改安全组名称和关联EC2实例时的处理过程,强调理解资源属性可变性对基础设施管理的重要性。(149字)

2501_90323865的博客 471

Android中实现长按照片弹出右键菜单

场景 效果 注: 博客:https://blog.youkuaiyun.com/badao_liumang_qizhi 关注公众号 霸道的程序猿 获取编程相关电子书、教程推送与免费下载。 实现 将布局改为LinearLayout,并通过android:orientation="vertical">设置为垂直布局。 然后添加一个ImageView,并设置id属性和图片源。 &lt...

BADAO_LIUMANG_QIZHI的博客 751

sam3.1 部署笔记 推理封装

sam3.1 部署笔记

jacke121的专栏 424

ESP32-IDF开发之旅 网络篇⑩ Web配网

本文介绍了基于ESP32S3的Web配网实现方案。作者通过移植开源项目,在ESP32S3上实现了WiFi配置功能,包括AP模式创建、HTTP服务器搭建、NVS存储读写等核心功能。文章详细解析了代码结构,包含WiFi初始化、事件处理、NVS存储管理和HTML页面服务等关键模块。实现方案采用AP+STA双模式,支持通过网页扫描WiFi并保存配置信息,适用于物联网设备的网络配置场景。代码中使用了FreeRTOS任务、ESP-IDF事件组等技术,并提供了完整的错误处理和日志记录功能。

单片机菜鸟哥的博客 351

Android11、12存储变化-不能在scard存文件怎么办-更换存储区域

Android11、12本地化存储变化-不能在scard存文件怎么办-更换存储区域

GUOZIJUN01的博客 2312

FFmpeg入门详解之110:RTSP协议讲解

TCP/IP 协议体系中的一个应用层协议,该协议定义了一对多应用程序如何有效地通过 IP 网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或UDP完成数据传输。HTTP与RTSP相比,HTTP传送HTML,而RTSP传送的是多媒体数据。正在上传…重新上传取消RTSP是基于文本的协议,采用ISO10646字符集,使用UTF-8编码方案。行以CRLF中断(\r\n:10,13:0x0A,0x0D),包括消息类型、消息头、消息体和消息长。

福优学苑@音视频+流媒体 2529

【Android基础笔记12】Android数据存储——SharedPreferences

一、数据存储选项:Data Storage ——Storage Options【重点】 1、Shared PreferencesStore private primitive data in key-value pairs.保存简单的键值对数据。2、Internal StorageStore private data on the device memory.在手机内存中保存不对外共

黎姿从的博客 479

国科大作业考试资料《人工智能原理与算法》2024新编-第三次作业整理

1. 你的目标是让机器人走出迷宫。机器人面朝北,开始位置在迷宫中间。你可以让机器人转向面朝东、南、西或北。你可以让机器人向前走一段距离,在撞墙之前它会停步。 (1)将问题形式化。状态空间有多大? (2)在迷宫中游走,在两条路或更多路交叉的路口可以转弯。重新形式化这个问题。现在状态空间有多大? (3)从迷宫的任一点出发,我们可以朝四个方向中的任一方向前进直到可以转弯的地方,而且我们只需要这样做。重新对这个问题进行形式化。我们需要记录机器人的方向吗? 2. 传教士和野人问题

MatsumotoChrikk 984

黑猫带你学eMMC协议第17篇:超详细的eMMC HS200 & HS400讲解

1 前言 1.1 声明 本文依据eMMC JEDEC5.1及个人工作经验整理而成,如有错误请留言。 本文结合eMMC JEDEC5.1协议手册查看效果更佳。 1.2 内容提要 本文大约6000字,主要内容有: 1.什么是emmc hs200/hs400? 2.什么是tuning?tuning的过程又是什么? 3.如何设置才能让emmc进入hs200 mode? 4.如何设置才能让emmc进入hs400 mode? 5.emmc关于HS200/HS400内部结构框图对比。 6.部分个人相关经验总结。

黑猫奥利奥的博客 1万+

android开发基础12-数据存储

基础数据存储

blind cat 468

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

2017.12.25 Android数据存储方案

1.持久化技术: 数据持久化:将内存中的数据保存到文件,以使得设备在关机或者重启以后也能正常使用的一种技术; 内存态与持久态之间切换的代价:CPU,文件读取,文件写入,内核态的切换 2.Android内置的持久化技术: (1)文件系统; (2)SharedPreferences (3)SQLite 3.文件系统如何使用:文件持久化 如何把数据保存到文件中: ...

a18946983682的博客 92

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

我的android 第12天 - 嵌入式关系型SQLite数据库存储数据

嵌入式关系型SQLite数据库存储数据    除了可以使用文件或SharedPreferences存储数据,还可以选择使用SQLite数据库存储数据。 在Android平台上,集成了一个嵌入式关系型数据库—SQLite,SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlit

u012458883的专栏 795

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Android进阶#(5/12)独特高效的数据存储——SQLite数据库_Android中的数据库开发

(5/12)独特高效的数据存储——SQLite数据库_Android中的数据库开发

王基。 432

iphone同步助手_如何快速的将 Android 数据迁移到 iPhone

iPhone 12 发布在即,普及了买了新 iPhone 怎么最快的迁移数据,那么对于 Android 用户怎么快速的将数据迁移到 iPhone 呢?首先你要想好你是否真的要放弃 Android 阵营哈,在下定决心后,你就可以开始苹果之旅了。苹果公司自然也考虑到了这点,先去应用市场搜索「转移到 iOS」App。准备工作:在安卓设备上,确保 Wi-Fi 已开启。将新 iOS 设备和安卓设备插入电源。...

weixin_39738755的博客 896

【QCM2290】Android12 TA数据默认保存在data,修改保存至persist

【QCM2290】Android12 TA数据默认保存在data,修改保存至persist

sunnywalden 744

Android音视频【六】视频数据存储方式YUV

人间观察 这个社会很浮躁,千万不要考验人性 在视频中的数据保存和传输都是YUV数据格式。主要是为了降低数据大小,如果用argb格式的数据,1px可能需要4个字节,而用YUV可能就需要1.5个字节。 YUV简介 与RGB类似,YUV也是一种颜色编码方法,主要用于视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,比如这样的设计解决了彩色电视机与黑白电视的兼容问题。 YUV,分为三个分量,“Y”表示的是明亮度(Luminance或Luma),也就是灰度值;.

顾修忠的博客 965

Android文件存储常用Api和对应存储位置

注:测试时的Android版本是12,如果实际地址不同可以用下面的测试代码自行重新看一遍。防止经常要用的时候记不住在哪个文件夹,要再去一一百度很麻烦,一次性测完了整理放在这里。

wodongx123的博客 1119
上一篇: Android -- (10),Gallery ImageView
下一篇: Android -- (13),Content Provider
theonlydk
博客等级 码龄14年 0粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值