Guide to APK Expansion File for Google Play Store

DylanLiuDylanLiu USUniversity ✭✭
edited May 2017 in Xamarin.Forms

Background:

"Google Play currently requires that your APK file be no more than 100MB (API level 9-10 and 14+) or 50 MB (API level 8 or lower)... Google Play allows you to attach two large expansion files that supplement your APK ... Each expansion file can be up to 2GB in size.". Your apps can easily exceed the limit if you have a lot of local content (images, videos, etc). This is most common among mobile games. Unfortunately, there seems to be lack of a detailed step-by-step tutorial on how to use expansion files in Xamarin. That is until now ...

Tutorial:

This tutorial assume that you have a lot of high resolution images in your app, and those images have previously been stored in the resource folder of your android project.

  1. Move all the images from the resource folder to somewhere outside of your project, for example a folder named "Images" on your Desktop. The following steps assume that you have all the images names stored in a local database. If you haven't done that yet, look into "https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/databases/" on how to add a database to your application.
  2. Zip the folder named "Images". Note that for videos, you have to use uncompressed format. You can either do it via command line ("zip -r0 Videos.zip Videos") or with using an application like WinZip.
  3. You need to rename the zip file "Main.VERSION_NUMBER.YOUR_BUNDLE_ID.obb". VERSION_NUMBER and BUNDLE_ID must match whatever that is in your AndroidManifest.xml. For example "Main.1.com.example.myapp.obb". Make sure that you have "hide file extension" checked off on your computer, otherwise your file name would still have a hidden ".zip" extension. Then your code won't be able to locate the expansion files.
  4. To test or debug with the expansion file, you need to copy the .obb file to your android testing device in "Android/obb/YOUR_BUNDLE_ID/". For example, it will look like "Android/obb/com.example.myapp/Main.1.com.example.myapp.obb". Note that if you want to test on a simulator, I'm sure there is place that you need to copy the file to. But I didn't really look into where it should be.
  5. Add "Android APK Expansion Downloader" package to your Xamarin.Android project.
  6. Create a new file named "ExpansionFile.cs". The following code basically copies all the images from your expansion file to the Personal folder on the device.

        public static class ExpansionFile
        {
            static string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
    
            public static bool CopyFromExpansionFile()
            {
                var context = Forms.Context;
                var packageInfo = Forms.Context.PackageManager.GetPackageInfo(context.PackageName, 0);
                var expansionFile = ApkExpansionSupport.GetApkExpansionZipFile(context, packageInfo.VersionCode, 0);
    
                // assuming you have the names of all the images stored in a database 
                var imageNames = MyDataBase.GetAllImages();
                foreach (string imageName in imageNames)
                {
                    var imageLocalPath = Path.Combine(documentsPath, imageName);
                    var iconLocalPath = Path.Combine(documentsPath, iconName);
    
                    if (!File.Exists(imageLocalPath))
                    {
                        var imageFileEntry = expansionFile.GetEntry("Images/" + imageName);
                        if (imageFileEntry == null)
                            return false;
                        var success = CopyFile(imageFileEntry, imageLocalPath);
                        if (!success)
                            return false;
                    }
                }
                return true;
            }
    
            private static bool CopyFile(ZipFileEntry fileEntry, string path)
            {
                var zipFile = new ZipFile(fileEntry.ZipFileName);
                return zipFile.ExtractFile(fileEntry, path);
            }
        }
    
  7. In the MainActivity.cs of your Xamarin.Android project add:

            if (!ExpansionFile.CopyFromExpansionFile();)
                    Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
    
  8. In your cross platform (Xamarin.Forms) code where you reference the images:
    Instead of

        Image.Source = ImageSource.FromFile("Image0.jpg");
    

You need:

            if (Device.RuntimePlatform == Device.Android)
            {
        string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
                    Image.Source = ImageSource.FromFile(Path.Combine(documentsPath, "Image0.jpg"));
            }
            else
                    Image.Source = ImageSource.FromFile("Image0.jpg");

The same goes if you are only doing (Xamarin.Android).

  1. Some sources say that the expansion file might not be successfully downloaded from the google play store when the apk file is downloaded. So you need to add some code to manually trigger the download. I didn't find this to be the case with my app. So I didn't add this part. But if you want to, here is where you can find that code: https://github.com/mattleibow/android.Play.ExpansionLibrary
  2. You need to have "ReadExternalStorage" checked in your AndroidManifest.xml
  3. In some cases (API 23 and above), the app will crash with an error "System.UnauthorizedAccessException: Access to the path "/storage/emulated/0/Android/obb/com.example.myapp/Main.1.com.example.myapp.obb is denied" even when you have "ReadExternalStorage" checked from step 10. This issue is reported in the following posts:
    https://code.google.com/p/android/issues/detail?id=197287
    https://code.google.com/p/android/issues/detail?id=217899
    But it was never acknowledged or fixed by Google. It is reported to happen 100% of the time on Samsung S5 S6 S7 running android 6.0. That's big percentage of your user base there. So you have to pick up Google's slack and fix it yourself.

To fix this issue, you need to add code to explicitly ask for permission in runtime.

            const string storagePermission = Manifest.Permission.ReadExternalStorage;
            if ((int)Build.VERSION.SdkInt >= 23 && CheckSelfPermission(storagePermission) != (int)Permission.Granted)
            {
                string[] PermissionsStorage = { storagePermission };
                const int RequestLocationId = 0;
                // You have to explicityly ask for storage permission in runtime for API 23 and above
                RequestPermissions(PermissionsStorage, RequestLocationId);
            }

(note that CheckSelfPermission and RequestPermissions only works on API 23 and above, so make sure the conditional statement for checking the version goes before checking permission.)

then your code for copying the images over becomes:

        if ((int)Build.VERSION.SdkInt >= 23)
        {
            if (CheckSelfPermission(storagePermission) == (int)Permission.Granted)
            {
                // copy images from expansion file to personal folder
                if (!ExpansionFile.CopyFromExpansionFile();)
                    Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
            }
        }
        else
        {
            // The issue with storage permission doesn't exist on API 22 and below
            // copy images from expansion file to personal folder
            if (!ExpansionFile.CopyFromExpansionFile();)
                Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
        }
  1. When you upload the expansion file to Google Play Store, follow the instructions here: https://support.google.com/googleplay/android-developer/answer/2481797?hl=en

Afterthought:

It's been a struggle to get my app published in the google play store. I place partial blame on google to place this antiquated restriction on apk file size. We used HockeyApp for beta testing / crash reports. HockeyApp doesn't support Expansion files, because expansion files are proprietary to Google Play Store. So I can see in the future that it is going to cause me more pain to undo everything in this tutorial and return the images back to resources folder. So unless you have to have local content for some reason, I suggest you host all your high resolution images and videos online and make your .apk file super small.

References:

http://www.applandeo.com/en/5-steps-using-expansion-files-android-xamarin-forms/
https://dotnetdevaddict.co.za/2014/12/21/downloading-expansion-files-in-xamarin-android/

Credit goes to Matthew Leibowitz who did most of the heavy lifting. I just complied everything together.

Posts

  • nadjibnadjib DZMember ✭✭✭✭

    Thank you @DylanLiu

  • DylanLiuDylanLiu USUniversity ✭✭

    After you've requested the permission, here is how to kick start the copying:

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
        {
            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            switch (requestCode)
            {
                case RequestLocationId:
                    if (grantResults.Length > 0 && grantResults[0] == (int)Permission.Granted)
                    {
                        if (!ExpansionFile.CopyFromExpansionFile())
                            Log.WriteLine(LogPriority.Warn, "Expansion Files", "Could not load expansion files.");
                    }
                    break;
            }
        }
    
  • Srinig29Srinig29 INMember

    Hi @DylanLiu
    I just followed the steps as scripted above.
    To debug as you mentioned I have copied the file to android testing device(Samsung Tablet - It doesn't have SD card, so I copied it to the below folder)

    The below line always gives null.
    expansionFile.GetEntry("Images/" + imageName);

    Can you please help me in this?

  • mattleibowmattleibow ZAXamarin Team Xamurai

    Hi all, I just wanted to let you know about the deprecation of my C# port of the expansion library (Android.Play.ExpansionLibrary). This port has been replaced with a better binding of the official Google code:

    This new library has breaking changes from the v1.x of both the C# port and the initial binding. This new update has far more enums and types that will help discoverability - the old API had a lot of int and string parameters.

    To aid in the upgrade process, I have created a doc that should cover all the changes and their replacements:

    I hope the upgrade is not too painful, please feel free to ask me directly (or via an issue or forum post) if you have any questions.

    I have also created fairly extensive docs on how to get started with the library, although if you are already using Android.Play.ExpansionLibrary then this may not be necessary:

    The new NuGets are:

  • GcubeTechGcubeTech USMember

    how to get pdf files to expansion from resources and then read those files in the app again? can you please help?

  • mattleibowmattleibow ZAXamarin Team Xamurai
    edited September 2017

    It really doesn't matter what type of file that you are trying to read.

    The chances are you want to just zip up the PDF and then download it using the normal bits. Once the expansion files are on the device, you can use whatever PDF reader/viewer you want to read the content from the expansion files.

    If you have a look at this sample:
    https://github.com/xamarin/XamarinComponents/blob/master/Android/GoogleAndroidVending/samples/DownloaderSample/ZipTestActivity.cs

    You can see that I have a zip file that contains a mp4 file. Then I use the VideoView to just play right from the expansion files, no unzip/extraction necessary.

    I have a simple sample and a more complex sample here:
    https://github.com/xamarin/XamarinComponents/tree/master/Android/GoogleAndroidVending/samples

  • AndPopAndPop UAMember ✭✭

    The project was created in Xamarin PCL profile No. 111 (without UWP). All classes are in the portable project, and the below-mentioned class in the Droid project.
    Here is my implementation of this provider
    `[assembly: Dependency(typeof(KidsApp.Droid.ZipFileContentProvider))]
    namespace KidsApp.Droid
    {
    [ContentProvider(new[] { ContentProviderAuthority }, Exported = false)]
    [MetaData(APEZProvider.MetaData.MainVersion, Value = "9")]
    [MetaData(APEZProvider.MetaData.PatchVersion, Value = "0")]
    public class ZipFileContentProvider : APEZProvider, IDeviceInfo
    {
    public const string ContentProviderAuthority = "kids.Kids.ZipFileContentProvider";
    public override string Authority => ContentProviderAuthority;

        public string AddAdres(string a)
        {
            var uri = Android.Net.Uri.Parse(string.Format(a, ContentProviderAuthority));
            return uri.ToString();
        }
        public string AddDirect()
        {
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory("Android/obb/kids.Kids");            
            return path.ToString();
        }
    }
    

    }`

    I use the AddAdres (string a) function using bindings in various classes of a portable project to specify the name of objects in the extension file.
    `public class startLayer : CCLayerColor
    {
    CCSprite DP;
    CCSprite An;
    CCSprite op;
    IDeviceInfo dev;
    public startLayer() : base(CCColor4B.Black)
    {
    var touchListener = new CCEventListenerTouchAllAtOnce();
    touchListener.OnTouchesEnded = (touches, ccevent) => Director.PushScene(new GameScena(GameView));
    AddEventListener(touchListener, this);
    dev = DependencyService.Get();
    }
    protected override void AddedToScene()
    {
    base.AddedToScene();
    AddAn();
    Addop();
    AddDP();
    Schedule(RunGameLogic);
    }

        void AddDP()
        {                      
            DP = new CCSprite(dev.AddAdres("DP"));
            DP.AnchorPoint = CCPoint.AnchorMiddle;
            DP.ScaleX = App.Width / DP.TextureRectInPixels.MaxX * 0.4f;
            DP.ScaleY = App.Height / DP.TextureRectInPixels.MaxY * 0.15f;
            DP.PositionX = App.Width / 2; ;
            DP.PositionY = App.Height / 2; ;
            DP.Opacity = 0;
            AddChild(DP);
        }`
    

    the function AddDirect () is used only in one class, again, with a binding, in a portable project to specify the directory in which the digital content is located.
    namespace KidsApp { public class ViewController : ContentView { IDeviceInfo dev; public CocosSharpView GameView; public ViewController() { dev = DependencyService.Get<IDeviceInfo>(); var GameView = new CocosSharpView { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; if (GameView != null) { GameView.ViewCreated += (sender, e) => { CCGameView gameView = sender as CCGameView; if (gameView != null) { gameView.DesignResolution = new CCSizeI(App.Width, App.Height); var contentSearchPaths = new List<string> { dev.AddDirect() }; gameView.ContentManager.SearchPaths = contentSearchPaths; CCScene sceneStart = new StartScene(gameView); gameView.Director.RunWithScene(sceneStart); gameView.TouchEnabled = true; } }; } Content = GameView; } } }
    Total: the function dev.AddDirect () returns the string "/storage/emulated/0/Android/obb/kids.Kids", which seems to be required,
    function AddAdres (string a) returns the name of the file transferred in the parameter. The question is, why does not it work? Why does not it read the files in the uncompressed archive (main.7.kids.Kids.obb)?

  • AndPopAndPop UAMember ✭✭

    @AndPop said:
    The project was created in Xamarin PCL profile No. 111 (without UWP). All classes are in the portable project, and the below-mentioned class in the Droid project.
    Here is my implementation of this provider
    `[assembly: Dependency(typeof(KidsApp.Droid.ZipFileContentProvider))]
    namespace KidsApp.Droid
    {
    [ContentProvider(new[] { ContentProviderAuthority }, Exported = false)]
    [MetaData(APEZProvider.MetaData.MainVersion, Value = "9")]
    [MetaData(APEZProvider.MetaData.PatchVersion, Value = "0")]
    public class ZipFileContentProvider : APEZProvider, IDeviceInfo
    {
    public const string ContentProviderAuthority = "kids.Kids.ZipFileContentProvider";
    public override string Authority => ContentProviderAuthority;

        public string AddAdres(string a)
        {
            var uri = Android.Net.Uri.Parse(string.Format(a, ContentProviderAuthority));
            return uri.ToString();
        }
        public string AddDirect()
        {
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory("Android/obb/kids.Kids");            
            return path.ToString();
        }
    }
    

    }`

    I use the AddAdres (string a) function using bindings in various classes of a portable project to specify the name of objects in the extension file.
    `public class startLayer : CCLayerColor
    {
    CCSprite DP;
    CCSprite An;
    CCSprite op;
    IDeviceInfo dev;
    public startLayer() : base(CCColor4B.Black)
    {
    var touchListener = new CCEventListenerTouchAllAtOnce();
    touchListener.OnTouchesEnded = (touches, ccevent) => Director.PushScene(new GameScena(GameView));
    AddEventListener(touchListener, this);
    dev = DependencyService.Get();
    }
    protected override void AddedToScene()
    {
    base.AddedToScene();
    AddAn();
    Addop();
    AddDP();
    Schedule(RunGameLogic);
    }

        void AddDP()
        {                      
            DP = new CCSprite(dev.AddAdres("DP"));
            DP.AnchorPoint = CCPoint.AnchorMiddle;
            DP.ScaleX = App.Width / DP.TextureRectInPixels.MaxX * 0.4f;
            DP.ScaleY = App.Height / DP.TextureRectInPixels.MaxY * 0.15f;
            DP.PositionX = App.Width / 2; ;
            DP.PositionY = App.Height / 2; ;
            DP.Opacity = 0;
            AddChild(DP);
        }`
    

    the function AddDirect () is used only in one class, again, with a binding, in a portable project to specify the directory in which the digital content is located.
    namespace KidsApp { public class ViewController : ContentView { IDeviceInfo dev; public CocosSharpView GameView; public ViewController() { dev = DependencyService.Get<IDeviceInfo>(); var GameView = new CocosSharpView { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; if (GameView != null) { GameView.ViewCreated += (sender, e) => { CCGameView gameView = sender as CCGameView; if (gameView != null) { gameView.DesignResolution = new CCSizeI(App.Width, App.Height); var contentSearchPaths = new List<string> { dev.AddDirect() }; gameView.ContentManager.SearchPaths = contentSearchPaths; CCScene sceneStart = new StartScene(gameView); gameView.Director.RunWithScene(sceneStart); gameView.TouchEnabled = true; } }; } Content = GameView; } } }
    Total: the function dev.AddDirect () returns the string "/storage/emulated/0/Android/obb/kids.Kids", which seems to be required,
    function AddAdres (string a) returns the name of the file transferred in the parameter. The question is, why does not it work? Why does not it read the files in the uncompressed archive (main.7.kids.Kids.obb)?

  • AndPopAndPop UAMember ✭✭

    @AndPop said:

    @AndPop said:
    The project was created in Xamarin PCL profile No. 111 (without UWP). All classes are in the portable project, and the below-mentioned class in the Droid project.
    Here is my implementation of this provider
    `[assembly: Dependency(typeof(KidsApp.Droid.ZipFileContentProvider))]
    namespace KidsApp.Droid
    {
    [ContentProvider(new[] { ContentProviderAuthority }, Exported = false)]
    [MetaData(APEZProvider.MetaData.MainVersion, Value = "9")]
    [MetaData(APEZProvider.MetaData.PatchVersion, Value = "0")]
    public class ZipFileContentProvider : APEZProvider, IDeviceInfo
    {
    public const string ContentProviderAuthority = "kids.Kids.ZipFileContentProvider";
    public override string Authority => ContentProviderAuthority;

        public string AddAdres(string a)
        {
            var uri = Android.Net.Uri.Parse(string.Format(a, ContentProviderAuthority));
            return uri.ToString();
        }
        public string AddDirect()
        {
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory("Android/obb/kids.Kids");            
            return path.ToString();
        }
    }
    

    }`

    I use the AddAdres (string a) function using bindings in various classes of a portable project to specify the name of objects in the extension file.
    public class startLayer : CCLayerColor
    {
    CCSprite DP;
    CCSprite An;
    CCSprite op;
    IDeviceInfo dev;
    public startLayer() : base(CCColor4B.Black)
    {
    var touchListener = new CCEventListenerTouchAllAtOnce();
    touchListener.OnTouchesEnded = (touches, ccevent) => Director.PushScene(new GameScena(GameView));
    AddEventListener(touchListener, this);
    dev = DependencyService.Get();
    }
    protected override void AddedToScene()
    {
    base.AddedToScene();
    AddAn();
    Addop();
    AddDP();
    Schedule(RunGameLogic);
    }

        void AddDP()
        {                      
            DP = new CCSprite(dev.AddAdres("DP"));
            DP.AnchorPoint = CCPoint.AnchorMiddle;
            DP.ScaleX = App.Width / DP.TextureRectInPixels.MaxX * 0.4f;
            DP.ScaleY = App.Height / DP.TextureRectInPixels.MaxY * 0.15f;
            DP.PositionX = App.Width / 2; ;
            DP.PositionY = App.Height / 2; ;
            DP.Opacity = 0;
            AddChild(DP);
        }
    

    the function AddDirect () is used only in one class, again, with a binding, in a portable project to specify the directory in which the digital content is located.
    namespace KidsApp { public class ViewController : ContentView { IDeviceInfo dev; public CocosSharpView GameView; public ViewController() { dev = DependencyService.Get<IDeviceInfo>(); var GameView = new CocosSharpView { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; if (GameView != null) { GameView.ViewCreated += (sender, e) => { CCGameView gameView = sender as CCGameView; if (gameView != null) { gameView.DesignResolution = new CCSizeI(App.Width, App.Height); var contentSearchPaths = new List<string> { dev.AddDirect() }; gameView.ContentManager.SearchPaths = contentSearchPaths; CCScene sceneStart = new StartScene(gameView); gameView.Director.RunWithScene(sceneStart); gameView.TouchEnabled = true; } }; } Content = GameView; } } }
    Total: the function dev.AddDirect () returns the string "/storage/emulated/0/Android/obb/kids.Kids", which seems to be required,
    function AddAdres (string a) returns the name of the file transferred in the parameter. The question is, why does not it work? Why does not it read the files in the uncompressed archive (main.7.kids.Kids.obb)?

    The project was created in Xamarin PCL profile No. 111 (without UWP). All classes are in the portable project, and the below-mentioned class in the Droid project.
    Here is my implementation of this provider
    `[assembly: Dependency(typeof(KidsApp.Droid.ZipFileContentProvider))]
    namespace KidsApp.Droid
    {
    [ContentProvider(new[] { ContentProviderAuthority }, Exported = false)]
    [MetaData(APEZProvider.MetaData.MainVersion, Value = "9")]
    [MetaData(APEZProvider.MetaData.PatchVersion, Value = "0")]
    public class ZipFileContentProvider : APEZProvider, IDeviceInfo
    {
    public const string ContentProviderAuthority = "kids.Kids.ZipFileContentProvider";
    public override string Authority => ContentProviderAuthority;

        public string AddAdres(string a)
        {
            var uri = Android.Net.Uri.Parse(string.Format(a, ContentProviderAuthority));
            return uri.ToString();
        }
        public string AddDirect()
        {
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory("Android/obb/kids.Kids");            
            return path.ToString();
        }
    }
    

    }`

    I use the AddAdres (string a) function using bindings in various classes of a portable project to specify the name of objects in the extension file.
    `public class startLayer : CCLayerColor
    {
    CCSprite DP;
    CCSprite An;
    CCSprite op;
    IDeviceInfo dev;
    public startLayer() : base(CCColor4B.Black)
    {
    var touchListener = new CCEventListenerTouchAllAtOnce();
    touchListener.OnTouchesEnded = (touches, ccevent) => Director.PushScene(new GameScena(GameView));
    AddEventListener(touchListener, this);
    dev = DependencyService.Get();
    }
    protected override void AddedToScene()
    {
    base.AddedToScene();
    AddAn();
    Addop();
    AddDP();
    Schedule(RunGameLogic);
    }

        void AddDP()
        {                      
            DP = new CCSprite(dev.AddAdres("DP"));
            DP.AnchorPoint = CCPoint.AnchorMiddle;
            DP.ScaleX = App.Width / DP.TextureRectInPixels.MaxX * 0.4f;
            DP.ScaleY = App.Height / DP.TextureRectInPixels.MaxY * 0.15f;
            DP.PositionX = App.Width / 2; ;
            DP.PositionY = App.Height / 2; ;
            DP.Opacity = 0;
            AddChild(DP);
        }`
    

    the function AddDirect () is used only in one class, again, with a binding, in a portable project to specify the directory in which the digital content is located.
    namespace KidsApp { public class ViewController : ContentView { IDeviceInfo dev; public CocosSharpView GameView; public ViewController() { dev = DependencyService.Get<IDeviceInfo>(); var GameView = new CocosSharpView { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; if (GameView != null) { GameView.ViewCreated += (sender, e) => { CCGameView gameView = sender as CCGameView; if (gameView != null) { gameView.DesignResolution = new CCSizeI(App.Width, App.Height); var contentSearchPaths = new List<string> { dev.AddDirect() }; gameView.ContentManager.SearchPaths = contentSearchPaths; CCScene sceneStart = new StartScene(gameView); gameView.Director.RunWithScene(sceneStart); gameView.TouchEnabled = true; } }; } Content = GameView; } } }
    Total: the function dev.AddDirect () returns the string "/storage/emulated/0/Android/obb/kids.Kids", which seems to be required,
    function AddAdres (string a) returns the name of the file transferred in the parameter. The question is, why does not it work? Why does not it read the files in the uncompressed archive (main.7.kids.Kids.obb)?

    ``

  • AndPopAndPop UAMember ✭✭

    @AndPop said:

    @AndPop said:

    @AndPop said:
    The project was created in Xamarin PCL profile No. 111 (without UWP). All classes are in the portable project, and the below-mentioned class in the Droid project.
    Here is my implementation of this provider
    `[assembly: Dependency(typeof(KidsApp.Droid.ZipFileContentProvider))]
    namespace KidsApp.Droid
    {
    [ContentProvider(new[] { ContentProviderAuthority }, Exported = false)]
    [MetaData(APEZProvider.MetaData.MainVersion, Value = "9")]
    [MetaData(APEZProvider.MetaData.PatchVersion, Value = "0")]
    public class ZipFileContentProvider : APEZProvider, IDeviceInfo
    {
    public const string ContentProviderAuthority = "kids.Kids.ZipFileContentProvider";
    public override string Authority => ContentProviderAuthority;

        public string AddAdres(string a)
        {
            var uri = Android.Net.Uri.Parse(string.Format(a, ContentProviderAuthority));
            return uri.ToString();
        }
        public string AddDirect()
        {
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory("Android/obb/kids.Kids");            
            return path.ToString();
        }
    }
    

    }`

    I use the AddAdres (string a) function using bindings in various classes of a portable project to specify the name of objects in the extension file.
    public class startLayer : CCLayerColor
    {
    CCSprite DP;
    CCSprite An;
    CCSprite op;
    IDeviceInfo dev;
    public startLayer() : base(CCColor4B.Black)
    {
    var touchListener = new CCEventListenerTouchAllAtOnce();
    touchListener.OnTouchesEnded = (touches, ccevent) => Director.PushScene(new GameScena(GameView));
    AddEventListener(touchListener, this);
    dev = DependencyService.Get();
    }
    protected override void AddedToScene()
    {
    base.AddedToScene();
    AddAn();
    Addop();
    AddDP();
    Schedule(RunGameLogic);
    }

        void AddDP()
        {                      
            DP = new CCSprite(dev.AddAdres("DP"));
            DP.AnchorPoint = CCPoint.AnchorMiddle;
            DP.ScaleX = App.Width / DP.TextureRectInPixels.MaxX * 0.4f;
            DP.ScaleY = App.Height / DP.TextureRectInPixels.MaxY * 0.15f;
            DP.PositionX = App.Width / 2; ;
            DP.PositionY = App.Height / 2; ;
            DP.Opacity = 0;
            AddChild(DP);
        }
    

    the function AddDirect () is used only in one class, again, with a binding, in a portable project to specify the directory in which the digital content is located.
    namespace KidsApp { public class ViewController : ContentView { IDeviceInfo dev; public CocosSharpView GameView; public ViewController() { dev = DependencyService.Get<IDeviceInfo>(); var GameView = new CocosSharpView { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; if (GameView != null) { GameView.ViewCreated += (sender, e) => { CCGameView gameView = sender as CCGameView; if (gameView != null) { gameView.DesignResolution = new CCSizeI(App.Width, App.Height); var contentSearchPaths = new List<string> { dev.AddDirect() }; gameView.ContentManager.SearchPaths = contentSearchPaths; CCScene sceneStart = new StartScene(gameView); gameView.Director.RunWithScene(sceneStart); gameView.TouchEnabled = true; } }; } Content = GameView; } } }
    Total: the function dev.AddDirect () returns the string "/storage/emulated/0/Android/obb/kids.Kids", which seems to be required,
    function AddAdres (string a) returns the name of the file transferred in the parameter. The question is, why does not it work? Why does not it read the files in the uncompressed archive (main.7.kids.Kids.obb)?

  • AndPopAndPop UAMember ✭✭

    I apologize for the spam that I've arranged here. I accidentally

  • AndPopAndPop UAMember ✭✭

    @AndPop said:

    @AndPop said:
    The project was created in Xamarin PCL profile No. 111 (without UWP). All classes are in the portable project, and the below-mentioned class in the Droid project.
    Here is my implementation of this provider
    `[assembly: Dependency(typeof(KidsApp.Droid.ZipFileContentProvider))]
    namespace KidsApp.Droid
    {
    [ContentProvider(new[] { ContentProviderAuthority }, Exported = false)]
    [MetaData(APEZProvider.MetaData.MainVersion, Value = "9")]
    [MetaData(APEZProvider.MetaData.PatchVersion, Value = "0")]
    public class ZipFileContentProvider : APEZProvider, IDeviceInfo
    {
    public const string ContentProviderAuthority = "kids.Kids.ZipFileContentProvider";
    public override string Authority => ContentProviderAuthority;

        public string AddAdres(string a)
        {
            var uri = Android.Net.Uri.Parse(string.Format(a, ContentProviderAuthority));
            return uri.ToString();
        }
        public string AddDirect()
        {
            var path = Android.OS.Environment.GetExternalStoragePublicDirectory("Android/obb/kids.Kids");            
            return path.ToString();
        }
    }
    

    }`

    I use the AddAdres (string a) function using bindings in various classes of a portable project to specify the name of objects in the extension file.
    `public class startLayer : CCLayerColor
    {
    CCSprite DP;
    CCSprite An;
    CCSprite op;
    IDeviceInfo dev;
    public startLayer() : base(CCColor4B.Black)
    {
    var touchListener = new CCEventListenerTouchAllAtOnce();
    touchListener.OnTouchesEnded = (touches, ccevent) => Director.PushScene(new GameScena(GameView));
    AddEventListener(touchListener, this);
    dev = DependencyService.Get();
    }
    protected override void AddedToScene()
    {
    base.AddedToScene();
    AddAn();
    Addop();
    AddDP();
    Schedule(RunGameLogic);
    }

        void AddDP()
        {                      
            DP = new CCSprite(dev.AddAdres("DP"));
            DP.AnchorPoint = CCPoint.AnchorMiddle;
            DP.ScaleX = App.Width / DP.TextureRectInPixels.MaxX * 0.4f;
            DP.ScaleY = App.Height / DP.TextureRectInPixels.MaxY * 0.15f;
            DP.PositionX = App.Width / 2; ;
            DP.PositionY = App.Height / 2; ;
            DP.Opacity = 0;
            AddChild(DP);
        }`
    

    the function AddDirect () is used only in one class, again, with a binding, in a portable project to specify the directory in which the digital content is located.
    namespace KidsApp { public class ViewController : ContentView { IDeviceInfo dev; public CocosSharpView GameView; public ViewController() { dev = DependencyService.Get<IDeviceInfo>(); var GameView = new CocosSharpView { HorizontalOptions = LayoutOptions.FillAndExpand, VerticalOptions = LayoutOptions.FillAndExpand }; if (GameView != null) { GameView.ViewCreated += (sender, e) => { CCGameView gameView = sender as CCGameView; if (gameView != null) { gameView.DesignResolution = new CCSizeI(App.Width, App.Height); var contentSearchPaths = new List<string> { dev.AddDirect() }; gameView.ContentManager.SearchPaths = contentSearchPaths; CCScene sceneStart = new StartScene(gameView); gameView.Director.RunWithScene(sceneStart); gameView.TouchEnabled = true; } }; } Content = GameView; } } }
    Total: the function dev.AddDirect () returns the string "/storage/emulated/0/Android/obb/kids.Kids", which seems to be required,
    function AddAdres (string a) returns the name of the file transferred in the parameter. The question is, why does not it work? Why does not it read the files in the uncompressed archive (main.7.kids.Kids.obb)?

Sign In or Register to comment.