Below is the working method to Import and Export SQLite database. Its Working just fine in all android versions excluding Android Pie. When I am trying to import in Android pie, it shows successful toast but database is not being restored. Can anybody help me workaround in Android Pie(API 28).
private void importDB() {
try {
File sd = Environment.getExternalStorageDirectory();
File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());
if (sd.canWrite()) {
String backupDBPath = bac_dir_nam +"/" + DATABASE_NAME;
File currentDB = new File(sd, backupDBPath);
FileChannel src = new FileInputStream(currentDB).getChannel();
FileChannel dst = new FileOutputStream(cur_db_pat).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
Toast.makeText(getBaseContext(), cur_db_pat.toString(),
Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
.show();
}
}
private void exportDB() {
try {
File sd = Environment.getExternalStorageDirectory();
File cur_db_pat = new File(this.getDatabasePath(DATABASE_NAME).getAbsolutePath());
if (sd.canWrite()) {
String backupDBPath = bac_dir_nam+"/" + DATABASE_NAME;
File backupDB = new File(sd, backupDBPath);
FileChannel src = new FileInputStream(cur_db_pat).getChannel();
FileChannel dst = new FileOutputStream(backupDB).getChannel();
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
Toast.makeText(getBaseContext(), backupDB.toString(),
Toast.LENGTH_LONG).show();
}
} catch (Exception e) {
Toast.makeText(getBaseContext(), e.toString(), Toast.LENGTH_LONG)
.show();
}
}
I don't have much experience with file system. So an example would help a lot.
In Android Pie+ SQLite has changed to default to using the generally more efficient Write Ahead Logging (WAL) instead of Journal mode.
As such there will be two files with the same name as the database but suffixed with -shm (shared memory file) and -wal (write ahead log) and their presence is what I believe causes the issue(s).
Temporary Files Used By SQLite (see 2.2 and 2.3)
One fix would be to disable Write Ahead Logging using use the SQliteDatabase disableWriteAheadLogging method and the previous method would work as before but with the less efficient journal mode.
(if using a subclass of SQliteOpenHelper then override the onConfigure method to invoke this method. ) disableWriteAheadLogging.
Another fix is to delete these two files when restoring. To avoid the potential for corruption you have to ensure that the database was adequately checkpointed before making the backup. see PRAGMA checkpoint;
The following is a snippet that deletes these two files when restoring (noting that the backup is assumed to have been taken with adequate checkpointing):-
// Added for Android 9+ to delete shm and wal file if they exist
File dbshm = new File(dbfile.getPath() + "-shm");
File dbwal = new File(dbfile.getPath()+ "-wal");
if (dbshm.exists()) {
dbshm.delete();
}
if (dbwal.exists()) {
dbwal.delete();
}
Another fix would be to additionally backup and subsequently restore the -shm and -wal files.
You may also wish considering the potential benefits of renaming the original files when importing/restoring, checking the new files after they have been copied (e.g. using PRAGMA integrity_check;) if the results indicat no issues then delete the renamed original files, otherwise delete the imported files and rename the original files to their original name, indicating that the import failed.
In your class for Db WorkHelper ovverride onOpen() method and set disableWriteAheadLogging then call onOpen() standard, if version of android sdk 28 , sure then old version remain old modality.
#Override
public void onOpen(SQLiteDatabase database) {
super.onOpen(database);
if(Build.VERSION.SDK_INT >= 28)
{
database.disableWriteAheadLogging();
}
}
In my case WORK perfect.
Unlike what the other commenters have suggested, you can't rely on the database consisting of a single file after write-ahead logging is disabled, and you can't assume that the -shl and -wal filenames remain correct. This is all an implementation detail of sqlite3 / Android and therefore subject to change at any time (just like the old code broke).
One way of doing this that I expect to continue working is to use sqlite3's .dump command to convert the database into SQL that can later be executed in order to recreate the database.
I haven't tested the following, but expect that something similar to it should work:
// Copyright 2021 Google LLC.
// SPDX-License-Identifier: Apache-2.0
// Untested:
private byte[] exportDatabase(SQLiteDatabase database) throws IOException {
Process process = new ProcessBuilder()
.command("/system/bin/sqlite3", database.getPath(), ".dump")
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.start();
try (InputStream inputStream = process.getInputStream()) {
// [read the full contents of inputStream and save them somewhere]
return ByteStreams.toByteArray(inputStream);
} finally {
waitForProcess(process);
}
}
private void importDatabase(String databasePath, InputStream backedUpData) throws IOException {
// restore the database:
Process process = new ProcessBuilder()
.command("/system/bin/sqlite3", databasePath)
.redirectInput(ProcessBuilder.Redirect.PIPE)
.start();
try (OutputStream outputStream = process.getOutputStream()) {
// now write the backed-up contents back to outputStream
ByteStreams.copy(backedUpData, outputStream);
}
waitForProcess(process);
}
private static void waitForProcess(Process process) {
try {
process.waitFor();
} catch (InterruptedException e) {
// ignore interruption, restore interrupt flag
Thread.currentThread().interrupt();
}
}
Obviously, you'll have to ensure that:
The database that you're backing up isn't currently open.
The database that you're restoring doesn't already exist.
Related
I'm new to Android Studio 3.0, emulating on a Nexus 4, Marshmallow. I'm trying to build simple "Save File" and "Load File" parts of my app. Here's the "Save File" part:
String filename = "myFile01"; // Then "myFile02", "myFile03", etc...
String userData = "Some useful data here...";
try {
// Adapted from: https://www.youtube.com/watch?v=_15mKw--RG0
FileOutputStream fileOutputStream = openFileOutput(filename, MODE_PRIVATE); // creates a file with given filename
fileOutputStream.write(userData.getBytes()); // puts userData into the file
fileOutputStream.close();
Toast.makeText(getApplicationContext(), "File saved!", Toast.LENGTH_LONG).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
The above code will be called again and again as the user creates and saves additional files. Later, the user may want to view all the saved files and load one. I'll have a ListView displaying all the files... but I need help reading the current directory to get that list.
I thought I read somewhere that in Android, there's one flat directory for your app to save and retrieve files. So I was hoping if I saved a bunch of files and then called a read() method, all my saved files would simply be in the default directory, no need to search. That seems to be a bad assumption; here's why:
Here's my code looking in the default directory and listing all the files found within there. First, I need the path of said default directory:
// Get current directory adapted from: https://stackoverflow.com/questions/5527764/get-application-directory
String packName, currDir;
PackageManager m = getPackageManager();
packName = getPackageName();
PackageInfo p = null;
try {
p = m.getPackageInfo(packName, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
currDir = p.applicationInfo.dataDir;
And then I open "currDir," and store the names of all the local files in an array:
// get list of files adapted from: https://stackoverflow.com/questions/9317483/showing-a-list-of-files-in-a-listview#9317583
File dir = new File(currDir);
File[] filelist = dir.listFiles();
String[] fileArr = new String[filelist.length];
for (int i = 0; i < fileArr.length; i++) {
fileArr[i] = filelist[i].getName();
}
The plan from here is to load the "fileArr" into a ListView and go from there. But when I step through the debugger, I see this as the contents of "fileArr":
"cache"
"code_cache"
"files"
This is true no matter how many files I've saved previously.
BTW, in the debugger, the assignments for packName and currDir look 100% correct:
packName = com.mydomain.myapp
currDir = /data/user/0/com.mydomain.myapp
So... I'm kinda assuming that my saved files are actually here:
/data/user/0/com.mydomain.myapp/files
And therefore, I should append this to my "get current directory" code:
// Get current directory adapted from: https://stackoverflow.com/questions/5527764/get-application-directory
String packName, currDir;
...everything from before...
currDir = p.applicationInfo.dataDir+"/files"; // <---- appending "+"/files"
Or am I way off? Any advice will be appreciated, thanks!
First of all, if you want to save your files in the app's directory, then you should call create a directory,
File directoryDefault = new File(Environment.DIRECTORY_DOCUMENTS, "YOUR_FOLDER_NAME");
if (!directoryDefault.exists()) {
directoryDefault.mkdir();
}
Then you have to save whatever files you have to save in the above mentioned default directory. Afterwards, when you want to list all the files available in that directory, you should call,
private ArrayList<String> fileNames() {
ArrayList<String> namesArray = new ArrayList<>();
File[] arrayFiles = directoryDefault.listFiles();
for (File file : arrayFiles) {
namesArray.add(file.getName());
}
return namesArray;
}
This is my function for converting blob to mp3:
private void convertByyeToMP3(byte[] bytearray,String trackName) {
try {
ContextWrapper c = new ContextWrapper(getApplicationContext());
File directory = new File(c.getFilesDir().getAbsolutePath()
+ "/Music");
if (!directory.exists()){
directory.mkdir();
}
File tempMp3 = File.createTempFile(trackName, ".mp3",
directory);
FileOutputStream fos = new FileOutputStream(tempMp3);
fos.write(bytearray);
fos.close();
Log.d("Byte array to mp3 conversion: ", "successfull");
} catch (Exception ex) {
Log.d("In convertToByteToMp3 Function:", ex.toString());
}
}
When I execute this function ,I can see the created mp3 files in my app folder but when I try to play them Using my own code or using ES File Explorer, they both can't play it.
This is the function I use play my music:
private MediaPlayer mp = new MediaPlayer();
private void playSong(String songPath) {
try {
mp.reset();
mp.setDataSource(songPath);
mp.prepare();
mp.start();
} catch (IOException e) {
Log.v(getString(R.string.app_name), e.getMessage());
}
}
And I use this sample code to play the track:
ContextWrapper c = new ContextWrapper(getApplicationContext());
File directory = new File(c.getFilesDir().getAbsolutePath() + "/Music");
playSong(directory.getPath() + File.separator + "kurchina");
This is where I read database and send the blob:
cursor = mDbHelper.GetTables();
byte[] blob = null;
DATAS data = new DATAS();
while (cursor.moveToNext()) {
blob = cursor.getBlob(cursor.getColumnIndex("data"));
if (blob != null) {convertByyeToMP3(blob,data_MusicName);}
db.addDATAS(data);
}
FYIs:
-Read and Write permissions added to manifest.
-Path and filename are check and they exist
-blob byte is not corrupted
There are all sorts of things that might have gone wrong, either in the code that you have shown us or elsewhere. So you need to do your own troubleshooting. Methodically.
Figure out if the problem is with the song file you have extracted or the way you are playing it. For example, try to play the extracted file using a free-standing mp3 player utility.
Assuming that the problem is the extracted file, the next thing is to figure out if the file is the same as the one that you originally inserted into the database. Compare the file sizes and the checksums using the relevant external applications.
and so on.
Found the problem.
It didn't play because the music files were stored in my app folder which is only accessible using a rooted device.
When I copied the music to my sdcard they played well, but in my app folder using rooted nexus 7 I couldn't play it even with an mp3-player app.
I'll post my code first:
private void validateXml(String xml) throws BadSyntaxException{
File xmlFile = new File(xml);
try {
JaxbCommon.unmarshalFile(xml, Gen.class);
} catch (JAXBException jxe) {
logger.error("JAXBException loading " + xml);
String xmlPath = xmlFile.getAbsolutePath();
System.out.println(xmlFile.delete()); // prints false, meaning cannot be deleted
xmlFile.delete();
throw new BadSyntaxException(xmlPath + "/package.xml");
} catch (FileNotFoundException fne) {
logger.error("FileNotFoundException loading " + xml + " not found");
fne.printStackTrace();
}
}
You can see in my comment where I print that the file cannot be deleted. Files can't be deleted from a try/catch? So, if there is a file with bad xml syntax, I want to delete the file in the catch.
EDIT: I can delete the file when I use delete() from outside of this function. I am on Windows.
Make sure that this method invocation JaxbCommon.unmarshalFile(xml, Gen.class); closes any stream when the exception occurs. If the stream that was reading the file is left opened then you cannot delete it.
The problem is unrelated to the try/catch. Do you have permissions to delete the file?
If you are using Java 7 you can use the Files.delete(Path) which I think will actually throw an IOException with the reason why you can't delete the file.
There is no general restriction regarding the use of java.io.File.delete() on try/catch blocks.
The behavior of many java.io.File methods can depend on platform/enviroment that the application is running. It is because they can need to access file system resources.
For example, the following code returns false on Windows 7 and true on Ubuntu 12.04:
public static void main(String[] args) throws Exception {
File fileToBeDeleted = new File("test.txt");
// just creates a simple file on the file system
PrintWriter fout = new PrintWriter(fileToBeDeleted);
fout.println("Hello");
fout.close();
// opens the created file and does not close it
BufferedReader fin = new BufferedReader(new FileReader(fileToBeDeleted));
fin.read();
// try to delete the file
System.out.println(fileToBeDeleted.delete());
fin.close();
}
So, the real problem can depend on several factors. However, it is not related to the code residing on a try/catch block.
Maybe, the resource that you is trying to delete was opened and not closed or locked by another process.
CODE EDITED:
I am developing a dictionary app for Android. I have been succeeding in making the app pronounce each word being looked up. Here is the code:
btnPronounce.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
//Log.i(MAIN_TAG,"Start pronounciation ...");
btnPronounce.setEnabled(false);
String currentWord = edWord.getText().toString().toLowerCase();
try {
ZipFile zip = new ZipFile("/sdcard/app_folder/sound/zip_test.zip");
ZipEntry entry = zip.getEntry(currentWord);
if (entry != null) {
InputStream in = zip.getInputStream(entry);
// see Note #3.
File tempFile = File.createTempFile("_AUDIO_", ".wav");
FileOutputStream out = new FileOutputStream(tempFile);
IOUtils.copy(in, out);
// do something with tempFile (like play it)
File f = tempFile;
try {
if (f.exists())
{
Log.i(MAIN_TAG,"Audio file found!");
MediaPlayer mp = new MediaPlayer();
mp.prepare();
mp.setLooping(false);
mp.start();
while (mp.getCurrentPosition() < mp.getDuration());
mp.stop();
mp.release();
Log.i(MAIN_TAG,"Pronounciation finished!");
}
else
{
Log.i(MAIN_TAG,"File doesn't exist!!");
}
}
catch (IOException e)
{
Log.i(MAIN_TAG,e.toString());
}
btnPronounce.setEnabled(true); }
else {
// no such entry in the zip
}
} catch (IOException e) {
// handle your exception cases...
e.printStackTrace();
}
}
});
But the problem is that there are too many WAV files in one folder, and all these files are treated as music files by Android devices. As a result, it takes ages to index such files. In addition, indexing and user browsing sometimes force the app to crash.
Therefore, I just wonder if the following could be programmatically done:
Can Android MediaPlayer play WAV/MP3 files zipped or wrapped in a single file? I mean I want to zip or wrap the audio files (or do something alike) so that they appear as one single file to Android devices but MediaPlayer can still play each individual WAV file inside.
If the above is impossible, can you guys suggest a solution to the problem?
EDIT:
Are there any other ways/solutions that allow audio files to be simply put into one big file (an image, zip or the like...) and then let MediaPlayer read individual files in it?
Thank you very much.
You can use a combination of ZipFile or ZipInputStream and java.io file operations to read the necessary data from the zip, create temp files and play those using MediaPlayer.
Alternatively, you could just use a TTS engine and not pass out a 50-bagillion-byte APK.
Edit - Example by request:
try {
ZipFile zip = new ZipFile("someZipFile.zip");
ZipEntry entry = zip.getEntry(fileName);
if (entry != null) {
InputStream in = zip.getInputStream(entry);
// see Note #3.
File tempFile = File.createTempFile("_AUDIO_", ".wav");
FileOutputStream out = new FileOutputStream(tempFile);
IOUtils.copy(in, out);
// do something with tempFile (like play it)
} else {
// no such entry in the zip
}
} catch (IOException e) {
// handle your exception cases...
e.printStackTrace();
}
Notes:
I didn't include any safe file handling practices here. That's up to you.
This isn't the way to do it, only a way to do it. There are probably 100 other ways, some of which may be better suited to what you need. I didn't use ZipInputStream simply because there's a little more logic involved and I was going for brevity. You have to check every entry to see if it's what you're looking for with ZipInputStream, whereas ZipFile allows you to just ask for what you want by name. I'm not sure what (if any) performance implications using either over the other would have.
By no means are you required to use temp files (or files at all, really), but Android's MediaPlayer doesn't really like streams, so this is probably the easiest solution.
An alternative you should consider is to download the individual sound files when the user want to listen to a pronunciation. This should reduce the file size although it does mean that you can't listen to a pronunciation when there is no Internet.
in my android app I'm using an own java library that extracts a .db-file from jar. In Java desktop it works well, but when I try to do it on android, the inputstream blocks forever. The copy method looks like this:
InputStream in = classloader.getResourceAsStream(...);
OutputStream out = new FileOutputStream(new File(...));
try {
while ((read = in.read()) != -1) {
out.write(read);
}
} finally {
try {
in.close();
} catch (final Exception e) {
LOGGER.debug("Error", e);
}
try {
out.close();
} catch (final Exception e) {
LOGGER.debug("Error", e);
}
}
I want to copy this file to the external files dir and android.permission.WRITE_EXTERNAL_STORAGE is granted.
Is there a way to access the file in /data/app/...apk? If not, how can I detect that it can not be accessed without blocking forever?
You should put the database-file in the /assets-folder and copy it to the /databases-folder on first run. A tutorial on this can be found here.
However, if you only want to create the tables and some sample-entry's, you might want to use the onCreate()-method from the SQLiteOpenHelper to do so.
Maybe you should store your data in the raw folder, you access it then :)
http://developer.android.com/guide/topics/resources/index.html
Answering to the question : we can only extract the class files from the apk that we have got, we will not get the layout/xml files or any other files.
There are ways by which we can extract the class files from the apk.