多个应用程序使用相同的内容提供商

发布于 2024-09-11 09:26:29 字数 519 浏览 3 评论 0原文

我正在开发一组应用程序,这些应用程序仅在某些品牌上有所区别(想想不同的运动队);但是,我遇到了一个问题,我为所有特定品牌的应用程序使用一个库项目,并希望为所有这些应用程序使用相同的 ContentProvider。当我创建 ContentProvider 时,我将 AUTHORITY 声明为类中的常量(根据开发示例代码),并且我在清单文件中的每个特定应用程序中使用相同的权限。看起来我无法在每个应用程序中使用相同的权限,因为在尝试安装第二个应用程序时出现此错误(我安装一个品牌应用程序很好,但第二次安装):

WARN/PackageManager(66): Can't install because provider name com.xxx.Provider (in package com.xxx) is already used by com.zzz

我尝试了几种方法,但没有一种似乎有效。我还没有完成的一个想法是创建一个库 jar 并省略我拥有的 Provider 类并在每个特定应用程序中自定义它。关于如何解决这个问题而不诉诸于此的任何想法?

I am developing a set of apps that are distinguished only in certain brandings (think different sports teams); however, I am running into a problem where I am using one Library project for all of the specifically branded apps and want to use the same ContentProvider for all of them. When I created the ContentProvider I declared the AUTHORITY as a constant in the class (per the dev example code) and I am using the same authority in every specific app in the manifest files. It looks like I can't use the same authority across every app as I get this error when trying to install a second app (I install one branded one just fine but the second install):

WARN/PackageManager(66): Can't install because provider name com.xxx.Provider (in package com.xxx) is already used by com.zzz

I've tried several approaches but none of them seem to work. One idea that I haven't done yet, was to create a library jar and just omit the Provider class I have and customize it in each specific app. Any ideas on how to get around this problem without resorting to that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

只是一片海 2024-09-18 09:27:10

可以使用下面的方式在库中封装一个ContentProvider,并在运行时设置ContentProvider的权限,这样就可以包含到多个项目中,而不会发生ContentProvider权限冲突。这是有效的,因为真正的“权限”来自 AndroidManifest...而不是 ContentProvider 类。

从基本的 ContentProvider 实现开始..AUTHORITY、CONTENT_URI 和 UriMatcher 是静态的,但不是“final”....

public class MyContentProvider extends ContentProvider {
    public static String  AUTHORITY = "com.foo.bar.content";
    public static Uri     CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    protected static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

然后,重写“attachInfo”方法,以便在首次初始化 ContentProvider 时,将使用以下内容调用您的 ContentProvider从 AndroidManifest 中收集的 ProviderInfo。这将在进行任何可能的查询之前发生,最有可能在初始应用程序类设置期间发生。利用这个机会将 AUTHORITY、CONTENT_URI 和 UriMatcher 重置为其“真实”值,如使用 ContentProvider 库的应用程序提供的那样。

    @Override
public void attachInfo(Context context, ProviderInfo info) {
    super.attachInfo(context, info);
    AUTHORITY = info.authority;
    CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(AUTHORITY, AlarmTable.TABLENAME, ALARMS);
    uriMatcher.addURI(AUTHORITY, AttributeTable.TABLENAME, ATTRIBUTES);
    uriMatcher.addURI(AUTHORITY, DeepLinkTable.TABLENAME, DEEPLINKS);
    uriMatcher.addURI(AUTHORITY, NotificationTable.TABLENAME, NOTIFICATIONS);
    uriMatcher.addURI(AUTHORITY, MetaDataTable.TABLENAME, RESOURCE_METADATA);
    uriMatcher.addURI(AUTHORITY, ResourceTable.TABLENAME, RESOURCES);
    uriMatcher.addURI(AUTHORITY, ResourceAttributeTable.TABLENAME, RESOURCES_ATTRIBUTES);
    uriMatcher.addURI(AUTHORITY, ResourceTagTable.TABLENAME, RESOURCES_TAGS);
    uriMatcher.addURI(AUTHORITY, TagTable.TABLENAME, TAGS);
    uriMatcher.addURI(AUTHORITY, UserTagTable.TABLENAME, USER_TAGS);
    uriMatcher.addURI(AUTHORITY, UserTable.TABLENAME, USERS);
    uriMatcher.addURI(AUTHORITY, CUSTOM, RAW);
}

当应用程序启动时,ContentProvider 实际上与应用程序类一起实例化,因此它将可以访问所有必需的包信息。 ProviderInfo 对象将包含 AndroidManifest 中提供的信息...最终应用程序中包含的列表。

        <provider android:authorities="com.foo.barapp.content"
              android:name="com.foo.bar.MyContentProvider"/>

现在将使用“com.foo.barapp.content”而不是默认值重写 Authority,并且 UriMatcher 将更新为应用程序的值而不是默认值。依赖“AUTHORITY”的类现在将访问更新后的值,并且 UriMatcher 将正确区分“com.foo.barapp.content”的传入查询。

我已经同时使用示例应用程序和 androidTest 包对此进行了测试,发现它可以正常工作。

The following way can be used to package a ContentProvider within a library and set the ContentProvider's authority at runtime, so that it can be included into multiple projects without ContentProvider Authority conflict. This works because the real 'authority' comes from the AndroidManifest...not the ContentProvider class.

Start with the basic ContentProvider implementation..AUTHORITY, CONTENT_URI and UriMatcher are static, but not 'final'....

public class MyContentProvider extends ContentProvider {
    public static String  AUTHORITY = "com.foo.bar.content";
    public static Uri     CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    protected static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

Then, override the 'attachInfo' method, so that when the ContentProvider is first initialized, your ContentProvider will be called with the ProviderInfo that's gleaned from the AndroidManifest. This will occur BEFORE any possible queries are made, most likely during the initial Application class setup. Use this opportunity to reset the AUTHORITY, CONTENT_URI and UriMatcher to their 'real' values, as provided by the Application that's using the ContentProvider library.

    @Override
public void attachInfo(Context context, ProviderInfo info) {
    super.attachInfo(context, info);
    AUTHORITY = info.authority;
    CONTENT_URI = Uri.parse("content://" + AUTHORITY);
    uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    uriMatcher.addURI(AUTHORITY, AlarmTable.TABLENAME, ALARMS);
    uriMatcher.addURI(AUTHORITY, AttributeTable.TABLENAME, ATTRIBUTES);
    uriMatcher.addURI(AUTHORITY, DeepLinkTable.TABLENAME, DEEPLINKS);
    uriMatcher.addURI(AUTHORITY, NotificationTable.TABLENAME, NOTIFICATIONS);
    uriMatcher.addURI(AUTHORITY, MetaDataTable.TABLENAME, RESOURCE_METADATA);
    uriMatcher.addURI(AUTHORITY, ResourceTable.TABLENAME, RESOURCES);
    uriMatcher.addURI(AUTHORITY, ResourceAttributeTable.TABLENAME, RESOURCES_ATTRIBUTES);
    uriMatcher.addURI(AUTHORITY, ResourceTagTable.TABLENAME, RESOURCES_TAGS);
    uriMatcher.addURI(AUTHORITY, TagTable.TABLENAME, TAGS);
    uriMatcher.addURI(AUTHORITY, UserTagTable.TABLENAME, USER_TAGS);
    uriMatcher.addURI(AUTHORITY, UserTable.TABLENAME, USERS);
    uriMatcher.addURI(AUTHORITY, CUSTOM, RAW);
}

When the Application is started, the ContentProvider is actually instantiated along with the Application class, so it will have access to all the required package info. the ProviderInfo object will contain the information provided in the AndroidManifest... The listing that's included in the final Application.

        <provider android:authorities="com.foo.barapp.content"
              android:name="com.foo.bar.MyContentProvider"/>

The Authority will now be rewritten with "com.foo.barapp.content" instead of the default value, and the UriMatcher will be updated to the application's value instead of the default. Classes that rely on the "AUTHORITY" will now access the updated value, and the UriMatcher will properly distinguish the incoming queries for the 'com.foo.barapp.content'.

I've tested this with both both a sample application and an androidTest package simultaneously and found it to work correctly.

懒的傷心 2024-09-18 09:27:09

可以说你的
库包是com.android.app.library
免费包是com.android.app.free
付费包是com.android.app.paid

在你的免费项目和付费项目中,在一个包中创建一个相同的文件,可以是任何东西,但必须相同。

示例:

  1. 使用 com.android.app.data 在免费版本中创建新包

    使用

  2. 创建一个名为 Authority 的文件。 java 和里面(Authority.java)放:

    公共类权威{

    `public static final String CONTENT_AUTHORITY = "您的提供商";`
    

    }

  3. 对于付费版本重复此操作,记住保持包名称和类名称相同。

    对于付费版本重复此操作

现在,在您的合同文件中,在您的库中使用以下内容:

public static String AUTHORITY = initAuthority();

    private static String initAuthority() {
        String authority = "something.went.wrong.if.this.is.used";

        try {

            ClassLoader loader = Contract.class.getClassLoader();

            Class<?> clz = loader.loadClass("com.android.app.data.Authority");
            Field declaredField = clz.getDeclaredField("CONTENT_AUTHORITY");

            authority = declaredField.get(null).toString();
        } catch (ClassNotFoundException e) {} 
        catch (NoSuchFieldException e) {} 
        catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }

        return authority;
    }

    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);

现在您应该能够使用两个权限。

图片来源:Ian Warick(代码编写)
Android - 在应用项目中拥有提供商权限
免责声明:我也将其发布在这里:Android重复提供者权限问题 - 不确定是否允许以相同的答案回答相同类型的问题。

Lets say your
library package is com.android.app.library
free package is com.android.app.free
paid package is com.android.app.paid

In your free project and paid project, make an identical file in a package which can be anything, but must be the same.

Example:

  1. Create a new package in your free version with com.android.app.data

  2. Create a file called Authority.java and inside (Authority.java) put:

    public class Authority {

    `public static final String CONTENT_AUTHORITY = "YOUR PROVIDER";`
    

    }

  3. Repeat this for the paid version, remember to keep the package name the same and class name.

Now, in your contract file, in your library use the following:

public static String AUTHORITY = initAuthority();

    private static String initAuthority() {
        String authority = "something.went.wrong.if.this.is.used";

        try {

            ClassLoader loader = Contract.class.getClassLoader();

            Class<?> clz = loader.loadClass("com.android.app.data.Authority");
            Field declaredField = clz.getDeclaredField("CONTENT_AUTHORITY");

            authority = declaredField.get(null).toString();
        } catch (ClassNotFoundException e) {} 
        catch (NoSuchFieldException e) {} 
        catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }

        return authority;
    }

    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);

Now you should be able to use two authorities.

Credit: Ian Warick (code write up)
Android - Having Provider authority in the app project
Disclaimer: I posted it here as well: Android duplicate provider authority problem - Not sure if allowed to answer the same type of question with the same answer.

极致的悲 2024-09-18 09:27:07

你可以!

正如这篇文章中所述(解释了 Firebase 如何在不通过 Application#onCreate() 方法为其提供上下文的情况下初始化其库),您可以在清单中使用占位符,如下所示:

    <provider
         android:authorities="${applicationId}.yourcontentprovider"
         android:name=".YourContentProvider" />

YOU CAN!

As said in this post (wich explains how Firebase initializes its library without giving it a context from your Application#onCreate() method), you can use a placeholder in your manifest, like this:

    <provider
         android:authorities="${applicationId}.yourcontentprovider"
         android:name=".YourContentProvider" />
白色秋天 2024-09-18 09:27:03

ContentProviders由权威机构标识,因此需要是唯一的。我不认为这有什么技巧。

此外,Android 平台中存在一个错误,该错误也会阻止两个不同的 ContentProvider 使用相同的类名,即使它们具有不同的权限并且包含在单独的 APK 中。请此处查看该错误。

我为您建议的解决方案是在您的库项目中创建抽象提供程序类,然后在每个单独的应用程序中使用唯一的名称对其进行扩展。为了使其实用,您可能需要创建一个脚本来生成/修改各个清单和 contentprovider 类。

希望这有帮助。

ContentProviders are identified by the authority, so it needs to be unique. I don't think there are any tricks around that.

Additionally, there's a bug in the Android platform that also prevents using the same classname for two different ContentProviders, even if they have different authority and are contained in separate APKs. See the bug here.

The solution I advise for you is to create the abstract provider class in your library project, and then extend it with a unique name in each of the individual applications. To make this practical you will probably need to create a script to generate/modify the individual manifests and contentprovider classes.

Hope this helps.

長街聽風 2024-09-18 09:26:57

这是一个老问题,但我最近正在考虑做类似的事情。有了构建风格,现在就真的很简单了。

在 gradle 文件中指定 BuildConfigField:

    productFlavors {
    free {
        applicationId "com.example.free"
        buildConfigField 'String', 'AUTHORITY', '"com.example.free.contentprovider"'
    }

    paid {
        applicationId "com.example.paid"
        buildConfigField 'String', 'AUTHORITY', '"com.example.paid.contentprovider"'
    }

在清单中指定提供程序权限:

    <provider
        android:name=".ContentProvider"
        android:authorities="${applicationId}.contentprovider" />

使用 BuildConfigField 变量在提供程序中设置权限:

    public static final String AUTHORITY = BuildConfig.AUTHORITY

It's an old question, but I was looking at doing something similar recently. With the Build flavours, its really straight forward now.

Specify the BuildConfigField in the gradle file:

    productFlavors {
    free {
        applicationId "com.example.free"
        buildConfigField 'String', 'AUTHORITY', '"com.example.free.contentprovider"'
    }

    paid {
        applicationId "com.example.paid"
        buildConfigField 'String', 'AUTHORITY', '"com.example.paid.contentprovider"'
    }

Specify the provider authority in the manifest:

    <provider
        android:name=".ContentProvider"
        android:authorities="${applicationId}.contentprovider" />

Set the authority in the provider using the BuildConfigField Variable:

    public static final String AUTHORITY = BuildConfig.AUTHORITY
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文