I want to use a library in my android app but want this library to be optional to the users.
To make it optional, I want to distribute the library separate from the apk. So that:
Users who intend to use this option can
download the lib from within the app
And those who don't wish to use this get a smaller apk file.
I want to ask, Is it possible to distribute a library separately from the apk?.
I've seen some games downloading files after their installation. So, I feel this might be achievable but don't know how to achieve this.
Note: Please keep in mind the following:
The classes or methods would only be used in case the library has been downloaded by the user and would not be used in the other case.
Do not confuse distribution of library with distribution of resources. So please keep in mind that I want to separately distribute a library and not resources.
I don't think you can use a library as expansion file because if you use any class, methods of library in your project you have to import them, and you can not import them without using library in project, But I am not sure.
Please see below link for more detail.
https://developer.android.com/google/play/expansion-files.html
You can distribute your base functionality with your APK
If the user chooses to, your application can download additional files, including libraries. If they are not expansion files (OBB), then you should take care hosting them.
These additional libraries will have to be loaded in a similar manner:
for (File pluginFile : pluginFiles)
{
try
{
JarFile jarFile = new JarFile(pluginFile);
Enumeration<? extends JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
if (entry.getName().endsWith(".class"))
{
URLClassLoader loader = URLClassLoader.newInstance(new URL[]{pluginFile.toURI().toURL()});
System.out.println(entry.getName());
Class<?> entryClass = loader.loadClass(entry.getName().replace(".class", "").replace("/", "."));
if (isPlugin(entryClass))
{
IPlugin plugin = (IPlugin)entryClass.newInstance();
plugins.add(plugin);
System.out.println("Added plugin \'" + plugin.getPluginId() + "\' from \'" + pluginFile.getName() + "\'");
}
}
}
}
catch (InstantiationException | ClassNotFoundException | IOException | IllegalAccessException e)
{
e.printStackTrace();
}
}
This assumes you want to load JAR files.
Related
So in my project I have an image object that loads various different sprites throughout the game. In order to implement this, I have a folder with a bunch of .pngs that the game accesses. In order for this to work in a standalone build I put these images in the following path:
StreamingAssets/Question Images
However, in Android I am getting an error because, as the manual says:
"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, you need to use additional software to see inside the .jar archive and obtain the file.
The thing is I have no idea how to go about implementing this, any ideas?
From Application.streamingAssetsPath
It is not possible to access the StreamingAssets folder on WebGL and Android platforms. No file access is available on WebGL. Android uses a compressed .apk file. These platforms return a URL. Use the UnityWebRequest class to access the Assets.
Example using UnityWebRequestTexture
private void Start()
{
StartCoroutine(GetTexture());
}
private IEnumerator GetTexture()
{
// in general I would always avoid to have spaces in file-paths
var path = Path.Combine(Application.streamingAssetsPath, "QuestionImages", "exampleImage.png");
using(var www = UnityWebRequestTexture.GetTexture(path))
{
yield return www.SendWebRequest();
if(www.isNetworkError || www.isHttpError)
{
Debug.LogErrorFormat(this, "Unable to load texture due to {0} - {1}", www.responseCode, www.error);
}
else
{
Texture myTexture = ((DownloadHandlerTexture)www.downloadHandler).texture;
}
}
}
Note: Typed on smartphone so no warrenty but I hope the idea gets clear.
I have some level definitions in xml format (with .txt extension) inside (without any subfolders) my project's rescources folder
I for more scalability, I have a plain text file naming all these level definition XMLs
I read that file using
TextAsset WorldList = (TextAsset)Resources.Load("WorldList");
And then I load the needed world:
TextAsset TA = (TextAsset)Resources.Load(/*"LevelDefs/" +*/ Worlds[worldToload]);
xps.parseXml(TA.text);
loadLevel(levelToLoad);
(you see that I have moved these rescources out of subfolder to reduce the chance of them not loading)
worldToload here is the index number of that world
program works fine on windows but nothing loads on my android test device.
I seem to have problems debugging on device so I only guess something went wrong in loading phase.
any suggestions?
From Unity Documentation:
Most assets in Unity are combined into the project when it is built.
However, it is sometimes useful to place files into the normal
filesystem on the target machine to make them accessible via a
pathname. An example of this is the deployment of a movie file on iOS
devices; the original movie file must be available from a location in
the filesystem to be played by the PlayMovie function.
Any files placed in a folder called StreamingAssets in a Unity project
will be copied verbatim to a particular folder on the target machine.
You can retrieve the folder using the Application.streamingAssetsPath
property. It’s always best to use Application.streamingAssetsPath to
get the location of the StreamingAssets folder, it will always point
to the correct location on the platform where the application is
running.
So below snippet should work:
// Put your file to "YOUR_UNITY_PROJ/Assets/StreamingAssets"
// example: "YOUR_UNITY_PROJ/Assets/StreamingAssets/Your.xml"
if (Application.platform == RuntimePlatform.Android)
{
// Android
string oriPath = System.IO.Path.Combine(Application.streamingAssetsPath, "Your.xml");
// Android only use WWW to read file
WWW reader = new WWW(oriPath);
while ( ! reader.isDone) {}
realPath = Application.persistentDataPath + "/Your"; // no extension ".xml"
var contents = System.IO.File.ReadAll(realPath);
}
else // e.g. iOS
{
dbPath = System.IO.Path.Combine(Application.streamingAssetsPath, "Your.xml");
}
Thanks to David I have resolved this problem.
for more precision I add some small details:
1-As David points out in his answer (and as stated in unity documentations), we should use StreamingAssets if we want to have the same folder structure. One should use Application.streamingAssetsPathto retrieve the path.
However, files are still in the apk package in android's case, so they should be accessed using unity android's www class.
a good practice here is to create a method to read the file (even mandatory if you are going to read more than one file)
here is one such method:
private IEnumerator LoadDataFromResources(string v)
{
WWW fileInfo = new WWW(v);
yield return fileInfo;
if (string.IsNullOrEmpty(fileInfo.error))
{
fileContents = fileInfo.text;
}
else
fileContents = fileInfo.error;
}
This is a coroutine, and to use it we need to call it using
yield return StartCoroutine(LoadDataFromResources(path + "/fileName.ext"));
Yet another remark: we can not write into this file or modify it as it's still inside the apk package.
I implemented the uploading function the file to Cloudinary.
Cloudinary cloudinary = new Cloudinary(Constants.CLOUDINARY_URL);
try {
FileInputStream is = new FileInputStream(new File(filePath));
Uploader uploader = cloudinary.uploader();
Map map = uploader.upload(is, new HashMap());
return map;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Now I would like to show the uploading percent in progress bar while upload.
But I can't find any callback function to get uploading percent.
Please help me.
Thanks.
cprakashagr answer actually works wonders. Just to elaborate on his answer, you will need to download the latest cloudinary library off github by doing this:
git clone https://github.com/cloudinary/cloudinary_java.git
Then you will have to make the changes according to his link:
https://github.com/cloudinary/cloudinary_java/pull/41
You cannot use Android Studio to make these changes as this is a maven project so you will need to use another IDE, like IntelliJ for example.
This is how I made the changes:
You will need to add on the class: UploadCallback
You will need to replace the class Uploader with his class: https://github.com/cprakashagr/cloudinary_java/blob/master/cloudinary-core/src/main/java/com/cloudinary/Uploader.java
Once you have done this, go into the main folder of your cloudinary_java, and then enter into Terminal. Go and type in mvn install and press enter. You will see that mvn will build the jars for the different folders in your directory. After the build is complete, if you navigate into the cloudinary-core folder for example, you will see the cloudinary-core jar.
You must copy the cloudinary-core jar from the folder and add it into your android project "libs" folder in the root of your android project. Once you have done that and it appears there, right click on the jar in Android Studios and click "Add as Library" so that android studios will add it as a dependency.
The cloudinary-android jar does not need to be added as a jar as you can grab a copy off gradle. Your final gradle file should look something like this:
compile('com.cloudinary:cloudinary-android:1.2.2') {
exclude module: 'cloudinary-core'
}
compile files('/Users/XXX/Documents/myApp/libs/cloudinary-core-1.4.2-SNAPSHOT.jar')
Rebuild your android studio project and you will see that the UploadCallback is now an object you can use in your android project. This is the way you know your jar build was successfully modified.
Now inside a service class, add your code for cloudinary direct upload. You will need to put the code inside a service class because you cannot do network operations on a UI thread and you will get an error if you tried:
Map config = new HashMap();
config.put("cloud_name", "XXX");
Cloudinary mobileCloudinary = new Cloudinary(config);
Map map = null;
try {
map = mobileCloudinary.uploader()
.uploadLarge(this,
intent.getSerializableExtra(getString(R.string.file)),
ObjectUtils.asMap("public_id", 123),
"tags", "myphoto",
"folder", "mylibrary",
"unsigned", true,
"upload_preset", "XXX"),
51200);
} catch (IOException e) {
e.printStackTrace();
}
String imageUrl = map.get("url").toString();
Timber.e("imageUrl " + imageUrl);
You must set the buffer to be large enough (but not too large) to make sure your callback is actually called. When I first tried this code out, I set the buffer to something really large, like 200000000, and the callback was not called because the upload would have happened all in one go. If you set it to a small value, like 2048, the callback will be called regularly however, the upload will become really slow. It is up to you to determine an adequate size which will work well for your app. For me it was 51200 which means for every 50kb of the file which is uploaded, a callback will occur.
Once it is working, you will need to transmit the progress information from the service class back to your activity class so you can display the progress on screen. I use a messageHandler to do so. This is the message method in my service class:
public void sendMessage(float progress) {
Message message = Message.obtain();
message.arg1 = Math.round(progress);
try {
messageHandler.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
This is my messageHandler on my activity class:
public class MessageHandler extends Handler {
#Override
public void handleMessage(final Message message) {
Timber.e("transfer: " + message.arg1);
}
}
The callback is not implemented from the cloudinary team yet. Though there have been few works around.
Check this link.
You need to import or build new JARs from the cloudinary source. For that, either you use this repository or make required changes to their original repository.
Let me know if you have some questions.
EDIT: On how to use it
Import these two cloudinary sources in your Android project (src/java dir):
Cloudinary Core Lib Source
Cloudinary Android Lib Source
Do changes as per my pull request.
And call the method uploadLarge as per the changed signature. Like:
cloudinary.uploader().uploadLarge(UploadCallback : uploadCallback, File : file, Map : options, int : bufferSize);
For buffer size, please assure you use at least 5242881. This is the limitation from cloudinary server. The minimum packet needs to be 5.001 MB at least.
I am working on Android and using docx4j to view the docx,pptx and xlsx files into my application.
I am unable to view the ppt files. I am getting compile time error at SvgExporter class. which is not there in docx4j library.
How can I get the SvgExporter class library and build my application and get the Svghtml to load on webview for ppt files? My code is as follows:
String inputfilepath = System.getProperty("user.dir") + "/sample-docs/pptx/pptx-basic.xml";
// Where to save images
SvgExporter.setImageDirPath(System.getProperty("user.dir") + "/sample-docs/pptx/");
PresentationMLPackage presentationMLPackage =
(PresentationMLPackage)PresentationMLPackage.load(new java.io.File(inputfilepath));
// TODO - render slides in document order!
Iterator partIterator = presentationMLPackage.getParts().getParts().entrySet().iterator();
while (partIterator.hasNext()) {
Map.Entry pairs = (Map.Entry)partIterator.next();
Part p = (Part)pairs.getValue();
if (p instanceof SlidePart) {
System.out.println(
SvgExporter.svg(presentationMLPackage, (SlidePart)p)
);
}
}
// NB: file suffix must end with .xhtml in order to see the SVG in a browser
}
SvgExporter uses XSLT and Xalan extension functions to do its thing.
IIRC, there were problems getting Xalan working on Android (you should verify this yourself).
If that remains the case, then you'll need to write a version of SvgExporter which does the traversal in Java code, as opposed to relying on Xalan to do this.
That should be quite feasible; there are "NonXSLT" examples in the docx4j code base.
How to compare two song stored in two files and see, if they are same or not?
Assuming you're not just looking to compare the files, but the actual music encoded within, you need to come up with a way to generate a fingerprint. For music, check out the MusicBrainz.org - they have open source libraries for getting from a song to it's metadata. Not sure if such a project exists for video though.
Use a open source Library for it. like musicg . Only down side is using that library is you can only compare wave files.
Yes this is possible by using the Follow this article
Download the Apache Commons IO 2.5 API from Here
Add jar file in tools which you are following. (netbeans,eclips,android studio)
Import libarary "import org.apache.commons.io.FileUtils";
you then have to use only this function.
File file1 = new File("1st_File_Path");
File file2 = new File("2nd_File_Path");
boolean compareResult = FileUtils.contentEquals(file1, file2);
System.out.println("Are the files are same? " + compareResult);
must remember to vote :)