I am writing an Android App where I need to have two persistent values that have to be read by the App. The App needs to maintain a counter which has to be stored 'somehwhere' and retrieved each time the Activity is launched. The counter is then updated and then stored before the Activity is shut down.
The important point is that I want the storing and retrieval of the counter to be done by the JNI part of my code. It should be practically invisible to the Java code.. Can this counter(memory) be accessed using JNI? If so, can you point me to which API I have to look?
I know one can use the SQLiteDatabase on the Java side. Please advise!
It's perfectly possible, but not without some Java code.
EDIT : the following is offered as an alternative to a database. Being able to read and write persistent data to/from files from native code would be a lot more flexible than a database...
Assuming you want to store and retrieve some data from a file (binary or plain text) residing on the filesystem these would be the steps to take:
JAVA : get the storage location for your app and check if it's available for reading and writing
JAVA : if the above is positive, pass it to the native layer through JNI
NATIVE : use the storage params to read/write your file
Ok, so far the abstract; lets get to the code:
1A) retreiving and checking storage:
private boolean checkExternalStorageState(){
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// We can read and write the media
android.util.Log.i("YourActivity", "External storage is available for read/write...", null);
return true;
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
// We can only read the media : NOT ok
android.util.Log.e("YourActivity", "External storage is read only...", null);
return false;
} else {
// Something else is wrong. It may be one of many other states, but all we need
// to know is we can neither read nor write
android.util.Log.e("YourActivity", "External storage is not mounted or read only...", null);
return false;
}
}
Get the storage location :
private get storageLocation(){
File externalAppDir = getExternalFilesDir(null);
String storagePath = externalAppDir.getAbsolutePath()
}
1B) you also might want to check if a file exists (you can also do this in the native part)
private boolean fileExists(String file) {
String filePath = storagePath + "/" + file;
// see if our file exists
File dataFile = new File(filePath);
if(dataFile.exists() && dataFile.isFile())
{
// file exists
return true;
}
else
{
// file does not exist
return false;
}
}
2) Pass it to the native layer:
JAVA part:
// Wrapper for native library
public class YourNativeLib {
static {
// load required libs here
System.loadLibrary("yournativelib");
}
public static native long initGlobalStorage(String storagePath);
...enter more functions here
}
NATIVE part:
JNIEXPORT jlong JNICALL Java_com_whatever_YourNativeLib_initGlobalStorage(JNIEnv *env, jobject obj, jstring storagePath)
{
jlong data = 0;
// convert strings
const char *myStoragePath = env->GetStringUTFChars(storagepath, 0);
// and now you can use "myStoragePath" to read/write files in c/c++
//release strings
env->ReleaseStringUTFChars(storagePath, myStoragePath);
return data;
}
How to read/write binary or text files in c/c++ is well documented, I'll leave that up to you.
You can use SQLite from NDK side by including SQLite amalgamation ( http://www.sqlite.org/download.html ) in your project.
See SQLite with Android NDK
Related
android.os.FileObserver requires a java.io.File to function.
But with Android 10 Google restricted access to everything but your app's private directory due to the famous "Storage Access Framework". Thus, accessing anything via java.io.File breaks and renders FileObserver useless unless you intend to use it in your app's private directory. However, I want to be notified when something is changed in a certain directory on external storage. I would also like to avoid periodically checking for changes.
I tried using ContentResolver.registerContentObserver(uri,notifyForDescendants,observer) and ran into some problems with that method:
Every Uri I have plugged in so far was accepted
It neither fails nor notifies if the Uri doesn't work
I cannot find any documentation telling me which Uris actually work
The only thing I got working to some extent is the following approach:
// works, but returns all changes to the external storage
contentResolver.registerContentObserver(MediaStore.Files.getContentUri("external"), true, contentObserver)
Unfortunately this includes all of the external storage and only returns Media Uris when changes happen - for example content://media/external/file/67226.
Is there a way to find out whether or not that Uri points to my directory?
Or is there a way to make registerContentObserver() work with a Uri in such a way that I get a notification whenever something in the folder has changed?
I also had no success trying various Uris related to DocumentsFile and external storage Uris.
I kept getting errors when even trying to use the base constructor such as the following -
No direct method <init>(Ljava/util/List;I)V in class Landroid/os/FileObserver; or its super classes (declaration of 'android.os.FileObserver' appears in /system/framework/framework.jar!classes2.dex)
From a comment on Detect file change using FileObserver on Android:
I saw that message (or something like that) when i was trying to use constructor FileObserver(File). Use of deprecated FileObserver(String) solved my problem.... Original FileObserver has bugs.
Full disclosure, I was using the Xamarin.Android API; however, the gist and the commenter I quoted were both working with Java. At any rate, indeed - tried again using the counterpart String constructor and I was finally able to make and use the observer. Grinds my gears to use a deprecated API, but apparently they're hanging onto it at least up to and including Android 12.0.0_r3... still, would much prefer the supported constructors actually work. Maybe there's some warrant here for filing an issue.
I found a way to implement FileObserver on Android 10 with ContentObserver, but it might only work with media files since it works with media content uris.
The uri for ContentResolver.registerContentObserver() should be the file's corresponding media uri (e.g. content://media/external/file/49) which is queried by file path.
fun getMediaUri(context: Context, file: File): Uri? {
val externalUri = MediaStore.Files.getContentUri("external")
context.contentResolver.query(
externalUri,
null,
"${MediaStore.Files.FileColumns.DATA} = ?",
arrayOf(file.path),
null
)?.use { cursor ->
if (cursor.moveToFirst()) {
val idIndex = cursor.getColumnIndex("_id")
val id = cursor.getLong(idIndex)
return Uri.withAppendedPath(externalUri, "$id")
}
}
return null
}
Then ContentObserver.onChange() will be triggered for every file change with uri: content://media/external/file/{id}; uri in ContentObserver.onChange(boolean selfChange, Uri uri) will always be content://media/external/file; only registered file will be with id (e.g. content://media/external/file/49?deletedata=false).
Does what FileObserver used to do when input uri's path matches registered uri.
I have a temporary solution for this issue, so let's see if this can help.
I start an infinite while loop watching for file created and file deleted (if you want file modified or file renamed you have to implement more) using DocumentFile. Below is my sample:
private static int currentFileIndirectory = 0;
private static final int FILE_CREATED = 0;
private static final int FILE_DELETED = 1;
private static DocumentFile[] onDirectoryChanged(DocumentFile[] documentFiles, int event) {
Log.d("FileUtil", "onDirectoryChanged: " + event);
if (event == FILE_CREATED) {
} else {
}
return documentFiles;
}
private static boolean didStartWatching = false;
private static void startWatchingDirectory(final DocumentFile directory) {
if (!didStartWatching) {
didStartWatching = true;
DocumentFile[] documentFiles = directory.listFiles();
if (null != documentFiles && documentFiles.length > 0) {
currentFileIndirectory = documentFiles.length;
}
new Thread(new Runnable() {
public void run() {
while (true) {
DocumentFile[] documentFiles = directory.listFiles();
if (null != documentFiles && documentFiles.length > 0) {
if (documentFiles.length != currentFileIndirectory) {
if (documentFiles.length > currentFileIndirectory) {//file created
DocumentFile[] newFiles = new DocumentFile[documentFiles.length - currentFileIndirectory];
onDirectoryChanged(newFiles, FILE_CREATED);
} else {//file Deleted
onDirectoryChanged(null, FILE_DELETED);
}
currentFileIndirectory = documentFiles.length;
}
}
}
}
}).start();
}
}
So in my app i pass a game object, called datacontroller through out my three scenes. The persistent scene is an empty scene, the menuscreen scene and then the game scene. My application works perfectly on my computer and in editor mode but when i download the apk to my android tablet it no longer works! iv'e read this may have to do with my code for my object but i dont think i written anything that only works in the editor.
enter code here
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.IO; // The System.IO namespace contains functions related to loading and saving
files
public class DataController : MonoBehaviour
{
private RoundData[] allRoundData;
private PlayerProgress playerProgress;
private string gameDataFileName = "data.json";
void Start()
{
DontDestroyOnLoad(gameObject);
LoadGameData();
LoadPlayerProgress();
SceneManager.LoadScene("MenuScreen");
}
public RoundData GetCurrentRoundData()
{
// If we wanted to return different rounds, we could do that here
// We could store an int representing the current round index in PlayerProgress
return allRoundData[0];
}
public void SubmitNewPlayerScore(int newScore)
{
// If newScore is greater than playerProgress.highestScore, update playerProgress with the new value and call SavePlayerProgress()
if (newScore > playerProgress.highestScore)
{
playerProgress.highestScore = newScore;
SavePlayerProgress();
}
}
public int GetHighestPlayerScore()
{
return playerProgress.highestScore;
}
private void LoadGameData()
{
// Path.Combine combines strings into a file path
// Application.StreamingAssets points to Assets/StreamingAssets in the Editor, and the StreamingAssets folder in a build
string filePath = Path.Combine(Application.streamingAssetsPath, gameDataFileName);
if (File.Exists(filePath))
{
// Read the json from the file into a string
string dataAsJson = File.ReadAllText(filePath);
// Pass the json to JsonUtility, and tell it to create a GameData object from it
GameData loadedData = JsonUtility.FromJson<GameData>(dataAsJson);
// Retrieve the allRoundData property of loadedData
allRoundData = loadedData.allRoundData;
}
else
{
Debug.LogError("Cannot load game data!");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void LoadPlayerProgress()
{
// Create a new PlayerProgress object
playerProgress = new PlayerProgress();
// If PlayerPrefs contains a key called "highestScore", set the value of playerProgress.highestScore using the value associated with that key
if (PlayerPrefs.HasKey("highestScore"))
{
playerProgress.highestScore = PlayerPrefs.GetInt("highestScore");
}
}
// This function could be extended easily to handle any additional data we wanted to store in our PlayerProgress object
private void SavePlayerProgress()
{
// Save the value playerProgress.highestScore to PlayerPrefs, with a key of "highestScore"
PlayerPrefs.SetInt("highestScore", playerProgress.highestScore);
}
}
I am starting to go through tutorials of unity myself so I am not an expert. :)
But what I would try is the first thing. using System.IO; not sure if this will work to get files on android because android has a different file structure. So I would first remove it and sort of hard code the file path or comment out the code using System.IO classes then recompile apk in unity and check if it works. I also saw this post : http://answers.unity3d.com/questions/1023391/systemio-dont-work-on-android.html
If that did not work I would comment functionality out and compile apk and check if its working if its not comment more code out until you find the line or the code that causes it to error on android. This method takes long to troubleshoot or get your code causing the problem but for me this has worked before.
I am guessing as it is working on you pc its a class or something its referencing that's not available in android.
Please share your findings if you figure out what part of the code does it. As I would also want to know to prevent me from doing it. :)
Avoid the use of System.IO on Android and in general on Unity.
Use "Resources" instead, just following this steps:
Create a folder called "Resources"
Move json file on it and rename in .txt
Use this code to get the string:
var file = Resources.Load("filename_here") as TextAsset;
Debug.Log(file.text)
In my game, there is a highscore that is supposed to be saved.
private void saveHighscore()
{
FileHandle file = Gdx.files.local("asdwdasfwad/asdawwafs.txt");
String highscoreString = Integer.toString(this.highscore);
file.writeString(highscoreString, false);
Gdx.app.log("saving", "saving");
}
private int loadHighscore()
{
FileHandle file = Gdx.files.local("asdwdasfwad/asdawwafs.txt");
String highscoreString = file.readString();
int highscore = Integer.parseInt(highscoreString);
Gdx.app.log("loading", "loading");
return highscore;
}
When I run this on my phone, "saving" is logged into the console without any errors, even though the path I have specified (asdwdasfwad/asdawwafs.txt) doesn't even exist. Even if I use an existent path, no file is created.
Not a direct answer to your problem, but since you just want to save a highscore:
You can just use Preferences for this. Its an class provided by libGDX that allows you to easily save small data.
For further information visit the official documentation page:
Click here
I need to read a text stream by using StreamReader from file on android platform. File is about 100k lines, so even editor is getting stuck if i try to load it all to TextAsset or if i use WWW.
I simply need to read that file line by line without loading it all to a string. Then i'll do a tree generation from the lines that i got from the file. (But probably that part doesn't matter, i just need help on file reading part.)
I'm giving the code that i wrote down below. It works perfectly on editor, but fails on android.
I would be glad if anyone tell me, what am i missing.
(ps. english is not my native and this is my first question on the site. so sorry for the any mistakes that i may have done.)
private bool Load(string fileName)
{
try
{
string line;
string path = Application.streamingAssetsPath +"/";
StreamReader theReader = new StreamReader(path + fileName +".txt", Encoding.UTF8);
using (theReader)
{
{
line = theReader.ReadLine();
linesRead++;
if (line != null)
{
tree.AddWord(line);
}
}
while (line != null);
theReader.Close();
return true;
}
}
catch (IOException e)
{
Debug.Log("{0}\n" + e.Message);
exception = e.Message;
return false;
}
}
You can't use Application.streamingAssetsPath as a path on Android because streaming assets are stored within the JAR file with the application.
From http://docs.unity3d.com/Manual/StreamingAssets.html:
Note that on Android, the files are contained within a compressed .jar
file (which is essentially the same format as standard zip-compressed
files). This means that if you do not use Unity’s WWW class to
retrieve the file then you will need to use additional software to see
inside the .jar archive and obtain the file.
Use WWW like this in a coroutine:
WWW data = new WWW(Application.streamingAssetsPath + "/" + fileName);
yield return data;
if(string.IsNullOrEmpty(data.error))
{
content = data.text;
}
Or, if you really want to keep it simple (and your file is only a few 100k, stick it in a resource folder:
TextAsset txt = (TextAsset)Resources.Load(fileName, typeof(TextAsset));
string content = txt.text;
The process seemed quite simplistic at first, but there must be something that I am missing going forward with this task. There was a settings file that I wanted to create local to my application for storing a whole bunch of data (not preference worthy). I ended up saving the file with the following code snippet.
protected File createSettingsFileLocation(String fileNameF)
{
File directoryFile = context_.getDir("settings", Context.MODE_PRIVATE);
File settingsFile;
settingsFile = new File(directoryFile, fileNameF);
if (!settingsFile.exists())
{
try
{
settingsFile.createNewFile();
} catch(IOException e)
{
Log.e(MyConstants.LOG_TAG, "Could not create the file as intended within internal storage.");
return null;
}
}
return settingsFile;
}
and then proceeded to retrieve the file later by looking for it locally with the following code snippets.
public String getCurrentFileContainingSettings()
{
List<String >settingFilesInFolder = getLocalStorageFileNames();
if (settingFilesInFolder == null || settingFilesInFolder.isEmpty())
{
return null;
}
String pathToCurrentSettingsFile = settingFilesInFolder.get(0);
return pathToCurrentSettingsFile;
}
protected List<String> getLocalStorageFileNames()
{
return Arrays.asList(context_.fileList());
}
However, the settingFilesInFolder always returns no entries, so I get null back from the getCurrentFileContainingSettings(). As what I could see from the documentation it seems as thought I was doing it right. But, I must be missing something, so I was hoping that someone could point something out to me. I could potentially hard-code the file name once it has been created within the system in a preference file for access later the first time that the settings are created, but I shouldn't have to do something like that I would think.
fileList() only looks in getFilesDir(), not in its subdirectories, such as the one you created via getDir(). Use standard Java file I/O (e.g., list()) instead.