Can I use ifstream in Android NDK to access Assets

2019-01-26 15:35发布

问题:

My question is pretty simple, but I am having a hard time finding any info about this online.

Is it possible to use ifstream to open a file from assets and/or resources using Android NDK?

For example, placing a test.txt file in /assets and trying the following does not work:

    char pLine[256];
    std::ifstream fin("/assets/test.txt");
    if(!fin.fail())
    {
        LOGD( "test.txt opened" );
        while( !fin.eof() )
        {
            fin.getline( pLine, 256 );
            LOGD(pLine);
        }
    }
    else
    {
        LOGD( "test.txt FAILED TO OPEN!" );
    }
    fin.close();

Nor does any variable of:

    std::ifstream fin("assets/test.txt");

    std::ifstream fin("test.txt");

Etc..., nor placing it in /res instead.

So, is it possible to use normal ifstream operators to access assets and or resource files?

回答1:

No, you cannot. Assets are stored within the apk, a zip file. ifstream cannot read within the zip file.

To access these files you either need to access them in java and save them elsewhere or extract the contents of the apk to get to the assets.

Here is an example of doing the former.

http://www.itwizard.ro/android-phone-installing-assets-how-to-60.html

Here is an example of doing the latter.

http://www.anddev.org/ndk_opengl_-_loading_resources_and_assets_from_native_code-t11978.html



回答2:

It is right that std::ifstream cannot be used, but one can create an assetistream that could be used in a similar way. For example:

class asset_streambuf: public std::streambuf
{
public:
    asset_streambuf(AAssetManager* manager, const std::string& filename)
    : manager(manager)
    {
        asset = AAssetManager_open(manager, filename.c_str(), AASSET_MODE_STREAMING);
        buffer.resize(1024);

        setg(0, 0, 0);
        setp(&buffer.front(), &buffer.front() + buffer.size());
    }

    virtual ~asset_streambuf()
    {
        sync();
        AAsset_close(asset);
    }

    std::streambuf::int_type underflow() override
    {
        auto bufferPtr = &buffer.front();
        auto counter = AAsset_read(asset, bufferPtr, buffer.size());

        if(counter == 0)
            return traits_type::eof();
        if(counter < 0) //error, what to do now?
            return traits_type::eof();

        setg(bufferPtr, bufferPtr, bufferPtr + counter);

        return traits_type::to_int_type(*gptr());
    }

    std::streambuf::int_type overflow(std::streambuf::int_type value) override
    {
        return traits_type::eof();
    };

    int sync() override
    {
        std::streambuf::int_type result = overflow(traits_type::eof());

        return traits_type::eq_int_type(result, traits_type::eof()) ? -1 : 0;
    }

private:
    AAssetManager* manager;
    AAsset* asset;
    std::vector<char> buffer;
};


class assetistream: public std::istream
{
public:
    assetistream(AAssetManager* manager, const std::string& file)
    : std::istream(new asset_streambuf(manager, file))
    {
    }
    assetistream(const std::string& file)
    : std::istream(new asset_streambuf(manager, file))
    {
    }

    virtual ~assetistream()
    {
        delete rdbuf();
    }

    static void setAssetManager(AAssetManager* m)
    {
        manager = m;
    }

private:
    static AAssetManager* manager;
};

void foo(AAssetManager* manager)
{
    assetistream::setAssetManager(manager);

    assetistream as("text/tmp.txt");
    std::string s;

    std::getline(as, s);
}

Improvements are very welcome.