ContentProvider客户端处理provider逻辑分析

目录
  • 引言
  • 1. 获取 provider
    • 1.1 等待 provider 发布
    • 1.2 安装 provider
  • 2. provider 实现多进程实例
  • 3. 两种 provider 区别
  • 结束

引言

前面一篇文章分析了 AMS 端处理 provider 的逻辑,请读者务必仔细阅读前面一篇文章,否则看本文,你可能有很多疑惑。

以查询 provider 为例来分析客户端是如何处理 provider,它调用的是 ContentResolver#query()

// ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
        @Nullable String[] projection, @Nullable Bundle queryArgs,
        @Nullable CancellationSignal cancellationSignal) {
    Objects.requireNonNull(uri, "uri");
    // ApplicationContentResolver 的 mWrapped 为 null
    try {
        if (mWrapped != null) {
            return mWrapped.query(uri, projection, queryArgs, cancellationSignal);
        }
    } catch (RemoteException e) {
        return null;
    }
    // 1. 获取 unstable provider
    IContentProvider unstableProvider = acquireUnstableProvider(uri);
    if (unstableProvider == null) {
        return null;
    }
    IContentProvider stableProvider = null;
    Cursor qCursor = null;
    try {
        long startTime = SystemClock.uptimeMillis();
        // 获取取消操作的接口
        ICancellationSignal remoteCancellationSignal = null;
        if (cancellationSignal != null) {
            cancellationSignal.throwIfCanceled();
            remoteCancellationSignal = unstableProvider.createCancellationSignal();
            cancellationSignal.setRemote(remoteCancellationSignal);
        }
        try {
            // 2. 执行操作
            qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,
                    queryArgs, remoteCancellationSignal);
        } catch (DeadObjectException e) {
            // 处理 unstable provider 进程挂掉的情况
            // 通知 AMS,provider 进程挂掉了
            unstableProviderDied(unstableProvider);
            // 获取 stable provider,再次尝试获取数据
            stableProvider = acquireProvider(uri);
            if (stableProvider == null) {
                return null;
            }
            qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
                    queryArgs, remoteCancellationSignal);
        }
        if (qCursor == null) {
            return null;
        }
        // Force query execution.  Might fail and throw a runtime exception here.
        qCursor.getCount();
        long durationMillis = SystemClock.uptimeMillis() - startTime;
        maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
        // 注意,这里最终还是从 stable provider 获取 provider 接口
        final IContentProvider provider = (stableProvider != null) ? stableProvider
                : acquireProvider(uri);
        final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
        stableProvider = null;
        qCursor = null;
        // 3. 返回数据
        return wrapper;
    } catch (RemoteException e) {
        return null;
    } finally {
        // ...
    }
}

纵观整个 provider 的查询过程,其实就是三步

  • 获取 provider。
  • 从获取到的 provider 执行查询操作。
  • 返回查询的结果。

我们注意到,代码中出现了两种 provider,unstable provider 和 stable provider。这两者的区别是,如果 provider 进程挂掉了,对于 stable provider,会杀死客户端进程,而 unstable 不会。这个我们会在后面分析。

现在我们要抓住重点,来分析如何获取 provider 。unstable provider 和 stable provider 的获取方式其实是一样的,本文只分析获取 unstbale provider。

1. 获取 provider

对于 app 进程来说,ContentResolver 接口的实现类为 ApplicationContentResolver,获取 unstable provider 的操作最终会调用 ApplicationContentResolver#acquireUnstableProvider()

//ContextImpl.java
class ContextImpl {
    private static final class ApplicationContentResolver extends ContentResolver {
        private final ActivityThread mMainThread;
        @Override
        protected IContentProvider acquireUnstableProvider(Context c, String auth) {
            return mMainThread.acquireProvider(c,
                    ContentProvider.getAuthorityWithoutUserId(auth),
                    resolveUserIdFromAuthority(auth), false);
        }
    }
}

原来最终是交给 ActivityThread 来获取 provider

// ActivityThread.java
public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
    // 从本地获取
    final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
    if (provider != null) {
        return provider;
    }
    ContentProviderHolder holder = null;
    // 合成一个 KEY
    final ProviderKey key = getGetProviderKey(auth, userId);
    try {
        synchronized (key) {
            // 1. 获取 ActivityManagerService 获取
            holder = ActivityManager.getService().getContentProvider(
                    getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            // 2. 等待 provider 发布完成
            // holder != null 表示 provider 存在
            // holder.provider == null 表示 provider 正在发布中
            // holder.mLocal 为 false,表示 provider 不是安装在客户端
            if (holder != null && holder.provider == null && !holder.mLocal) {
                synchronized (key.mLock) {
                    // 2.1 超时等等 provider 发布
                    // 超时时间一般为 20s
                    key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                    // 这里可能因为超时被唤醒,获取的数据为空
                    // 也可以是因为provider发布完成,被AMS唤醒,holder 为AMS返回的数据
                    holder = key.mHolder;
                }
                // 2.2 确认是否是超时唤醒
                if (holder != null && holder.provider == null) {
                    // probably timed out
                    holder = null;
                }
            }
        }
    }
    // ...
    // 这里记录了获取provider失败的日志
    if (holder == null) {
        if (UserManager.get(c).isUserUnlocked(userId)) {
            Slog.e(TAG, "Failed to find provider info for " + auth);
        } else {
            Slog.w(TAG, "Failed to find provider info for " + auth + " (user not unlocked)");
        }
        return null;
    }
    // 3. 成功从服务端获取 provider,本地安装它
    holder = installProvider(c, holder, holder.info,
            true /*noisy*/, holder.noReleaseNeeded, stable);
    return holder.provider;
}

客户端获取 provider 的过程大致分为如下几步

  • 从 AMS 获取 provider。
  • 如果 provider 还是发布的过程中,那么就超时等待它发布完成。 但是等待是有时间限制的,大约为 20s。超时等待的过程中被唤醒,有两种可能,一种是因为超时了,另外一种是因为 provider 成功发布,AMS 唤醒了客户端。因此需要判断到底是哪一种情况,检测的条件是被唤醒后,是否获取到 provider binder,也就是 holder.provider。详见【1.1 等待 provider 发布
  • 从 AMS 成功获取到 provider 后,那就在本地“安装”。这个方法的命令起的并不是很好,如果成功从 AMS 获取到 provider,其实这里的逻辑是保存数据。而如果 AMS 通知客户端,provider 可以安装在客户端进程中,客户端会在这个方法中创建 ContentProvider 对象并保存,这才叫安装。详见【1.2 安装 provider

1.1 等待 provider 发布

从前面的文章可知,当 provider 发布超时 或者 成功发布时,都会调用 ContentProviderRecord#onProviderPublishStatusLocked(boolean status) 来通知客户端 provider 的发布状态。参数 status 如果为 true,表示发布成功,如果为 false,表示发布超时。

// ContentProviderRecord.java
void onProviderPublishStatusLocked(boolean status) {
    final int numOfConns = connections.size();
    for (int i = 0; i < numOfConns; i++) {
        // 遍历所有等待 provider 发布的客户端连接
        final ContentProviderConnection conn = connections.get(i);
        if (conn.waiting && conn.client != null) {
            final ProcessRecord client = conn.client;
            // 记录发布超时的日志
            if (!status) {
                // 从这里可以看出status为false时,不一定表示发布超时,还可能因为进程挂掉了
                if (launchingApp == null) {
                    Slog.w(TAG_AM, "Unable to launch app "
                            + appInfo.packageName + "/"
                            + appInfo.uid + " for provider "
                            + info.authority + ": launching app became null");
                    EventLogTags.writeAmProviderLostProcess(
                            UserHandle.getUserId(appInfo.uid),
                            appInfo.packageName,
                            appInfo.uid, info.authority);
                } else {
                    Slog.wtf(TAG_AM, "Timeout waiting for provider "
                            + appInfo.packageName + "/"
                            + appInfo.uid + " for provider "
                            + info.authority
                            + " caller=" + client);
                }
            }
            // 通知客户端
            final IApplicationThread thread = client.getThread();
            if (thread != null) {
                try {
                    thread.notifyContentProviderPublishStatus(
                            newHolder(status ? conn : null, false),
                            info.authority, conn.mExpectedUserId, status);
                } catch (RemoteException e) {
                }
            }
        }
        conn.waiting = false;
    }
}

很简单,通过遍历所有等待 provider 发布的客户端连接,然后通过客户端 attach 的 thread 来通知它们。

// ActivityThread.java
public void notifyContentProviderPublishStatus(@NonNull ContentProviderHolder holder,
        @NonNull String authorities, int userId, boolean published) {
    final String auths[] = authorities.split(";");
    for (String auth: auths) {
        final ProviderKey key = getGetProviderKey(auth, userId);
        synchronized (key.mLock) {
            // 保存服务端传过来的数据
            key.mHolder = holder;
            // 唤醒等待provider的线程
            key.mLock.notifyAll();
        }
    }
}

客户端收到信息后,唤醒了等待的线程,谁在等待呢?这里是不是有点熟悉,其实就是前面分析获取 provider 时,超时等待,部分代码如下

// ActivityThread.java
public final IContentProvider acquireProvider(
        Context c, String auth, int userId, boolean stable) {
    // ...
    try {
        synchronized (key) {
            holder = ActivityManager.getService().getContentProvider(
                    getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
            // 等待 provider 发布完成
            if (holder != null && holder.provider == null && !holder.mLocal) {
                synchronized (key.mLock) {
                    // 超时等待
                    key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS);
                    // 这里可能因为超时被唤醒,获取的数据为空
                    // 也可以是因为provider发布完成,被AMS唤醒,holder 为AMS返回的数据
                    holder = key.mHolder;
                }
                // 确认是否是超时唤醒
                if (holder != null && holder.provider == null) {
                    // probably timed out
                    holder = null;
                }
            }
        }
    }
    // ...
}

超时等待 provider 发布时,如果一旦被唤醒,再次获取 key.mHolder,因为如果成功发布,holder.provider 是不为空的,因为它就是 provider binder,否则就是超时唤醒。

1.2 安装 provider

客户端如果成功从 AMS 获取到 provider,那么就会安装它,其实这里的操作是保存数据,其实最主要的就是保存 provider 接口,同时也是保存 provider binder.

private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    // 成功从 AMS 获取 provider,下面两个条件都是不成立
    if (holder == null || holder.provider == null) {
        // ...
    } else {
        // 获取 provider 接口,其实就是获取 provider binder
        provider = holder.provider;
    }
    ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        // 从 provider 接口中获取 binder 对象
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            // ...
        } else {
            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
            if (prc != null) {
                // ...
            } else {
                // 1. 创建 provider 记录,并保存
                ProviderClientRecord client = installProviderAuthoritiesLocked(
                        provider, localProvider, holder);
                // persistent app 的 provider 是不需要释放的
                if (noReleaseNeeded) {
                    prc = new ProviderRefCount(holder, client, 1000, 1000);
                } else {
                    prc = stable
                            ? new ProviderRefCount(holder, client, 1, 0)
                            : new ProviderRefCount(holder, client, 0, 1);
                }
                // 2. 保存 provider 计数
                mProviderRefCountMap.put(jBinder, prc);
            }
            retHolder = prc.holder;
        }
    }
    return retHolder;
}
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
        ContentProvider localProvider, ContentProviderHolder holder) {
    final String auths[] = holder.info.authority.split(";");
    final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
    // ...
    //  创建一条 provider 记录
    final ProviderClientRecord pcr = new ProviderClientRecord(
            auths, provider, localProvider, holder);
    // 一个 ContentProvider 可以声明多个 authority
    for (String auth : auths) {
        final ProviderKey key = new ProviderKey(auth, userId);
        //  mProviderMap 保存
        final ProviderClientRecord existing = mProviderMap.get(key);
        if (existing != null) {
            Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
                    + " already published as " + auth);
        } else {
            mProviderMap.put(key, pcr);
        }
    }
    return pcr;
}

很简单,就是用两个数据结构保存数据。

2. provider 实现多进程实例

前面我们总是隐隐约约地提到,provider 可以安装在客户端进程,那么什么样的条件下,provider 可以安装在客户端进程中? 前面一篇文章的分析中有提到过,现在展示出部分代码

// ContentProviderHelper.java
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
        String name, IBinder token, int callingUid, String callingPackage, String callingTag,
        boolean stable, int userId) {
    // ...
    synchronized (mService) {
        // 获取客户端的进程实例
        ProcessRecord r = null;
        if (caller != null) {
            r = mService.getRecordForAppLOSP(caller);
            if (r == null) {
                throw new SecurityException("Unable to find app for caller " + caller
                        + " (pid=" + Binder.getCallingPid() + ") when getting content provider "
                        + name);
            }
        }
        // ...
        // provider 正在运行
        if (providerRunning) {
            cpi = cpr.info;
            if (r != null &amp;&amp; cpr.canRunHere(r)) {
                // This provider has been published or is in the process
                // of being published...  but it is also allowed to run
                // in the caller's process, so don't make a connection
                // and just let the caller instantiate its own instance.
                ContentProviderHolder holder = cpr.newHolder(null, true);
                // don't give caller the provider object, it needs to make its own.
                holder.provider = null;
                return holder;
            }
            // ...
        }
        // provider 没有运行
        if (!providerRunning) {
            // ...
            if (r != null &amp;&amp; cpr.canRunHere(r)) {
                // If this is a multiprocess provider, then just return its
                // info and allow the caller to instantiate it.  Only do
                // this if the provider is the same user as the caller's
                // process, or can run as root (so can be in any process).
                return cpr.newHolder(null, true);
            }
            // ...
        }
        // ...
    }
    // ...
}

可以看到,无论 provider 是否已经运行,都有机会在客户端进程中创建 provider 实例,而这个机会就在 ContentProviderRecord#canRunHere()

provider 已经运行,居然还可以运行在客户端进程中,也就是在客户端进程中创建 ContentProvider 实例,这样的设计又是为了什么呢?

public boolean canRunHere(ProcessRecord app) {
    // info 为 provider 信息,也就是在 AndroidManifest 中声明的 provider 信息
    // provider 可以 运行在客户端进程中的条件
    // 1. provider 所在的 app 的 uid 与客户端 app 的 uid 相同
    // 2. provider 支持多进程 或者 provider 的进程名与客户端 app 的进程名相同
    return (info.multiprocess || info.processName.equals(app.processName))
            && uid == app.info.uid;
}

这里的条件可要看清楚了,首先 provider 所在 app 和 客户端 app 的 uid 相同,其实就是下面这个玩意要一样

<manifest
    android:sharedUserId="">

然后,还需要 provider 支持多进程,其实就是下面这个玩意

<provider
    android:multiprocess="true"/>

如果 provider 不支持多进程,只要 provider 的进程名与客户端 app 的进程名一样,provider 也是可以运行在客户端进程中。那么 provider 进程名是什么呢? provider 可以声明自己的进程名,如下

<provider
    android:process=""
   />

而如果 provider 没有声明自己的进程名,那么 provider 进程名取自 app 的进程名。

现在 provider 怎样运行在客户端进程中,大家会玩了吗?如果会玩了,那么继续看下客户端如何安装 provider,这一次可就是真的安装 provider 了

private ContentProviderHolder installProvider(Context context,
        ContentProviderHolder holder, ProviderInfo info,
        boolean noisy, boolean noReleaseNeeded, boolean stable) {
    ContentProvider localProvider = null;
    IContentProvider provider;
    // 此时 holder.provider == null 是成立的
    if (holder == null || holder.provider == null) {
        // ...
        try {
            final java.lang.ClassLoader cl = c.getClassLoader();
            LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
            if (packageInfo == null) {
                // System startup case.
                packageInfo = getSystemContext().mPackageInfo;
            }
            // 1. 通过反射创建 ContentProvider 对象
            localProvider = packageInfo.getAppFactory()
                    .instantiateProvider(cl, info.name);
            // 获取 provider 接口,其实就是获取 provider binder
            provider = localProvider.getIContentProvider();
            if (provider == null) {
                return null;
            }
            // 2. 为 ContentProvider 对象保存 provider 信息,并且调用 ContentProvider#onCreate()
            localProvider.attachInfo(c, info);
        } catch (java.lang.Exception e) {
            // ...
        }
    } else {
        // ...
    }
    ContentProviderHolder retHolder;
    synchronized (mProviderMap) {
        if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
                + " / " + info.name);
        IBinder jBinder = provider.asBinder();
        if (localProvider != null) {
            ComponentName cname = new ComponentName(info.packageName, info.name);
            ProviderClientRecord pr = mLocalProvidersByName.get(cname);
            if (pr != null) {
                // ...
            } else {
                // 本地创建 ContentProviderHolder
                holder = new ContentProviderHolder(info);
                // 保存 provider binder
                holder.provider = provider;
                // 本地安装的 provider,不需要释放
                holder.noReleaseNeeded = true;
                // 3. 创建 provider 记录,并保存
                pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                mLocalProviders.put(jBinder, pr);
                mLocalProvidersByName.put(cname, pr);
            }
            retHolder = pr.mHolder;
        } else {
            // ...
        }
    }
    return retHolder;
}

其实这一部分代码在前面文章中已经分析过,这里简单介绍下过程

  • 客户端自己创建 ContentProvider 对象,然后保存 provider 信息,并调用 ContentProvider#onCreate() 方法。
  • 创建 provider 记录,也就是 ContentProviderRecord 对象,然后用数据结构保存。

3. 两种 provider 区别

前面我们提到过 unstable provider 和 stable provider 的区别,现在我们用代码来解释下这两者的区别

假设我们通过 ActivityManager#forceStopPackage() 来杀掉 provider 进程,在 AMS 的调用如下

public void forceStopPackage(final String packageName, int userId) {
    // ...
    try {
        IPackageManager pm = AppGlobals.getPackageManager();
        synchronized(this) {
            int[] users = userId == UserHandle.USER_ALL
                    ? mUserController.getUsers() : new int[] { userId };
            for (int user : users) {
                // ...
                if (mUserController.isUserRunning(user, 0)) {
                    // 杀掉进程
                    forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                    // 发送广播
                    finishForceStopPackageLocked(packageName, pkgUid);
                }
            }
        }
    } finally {
        Binder.restoreCallingIdentity(callingId);
    }
}

最终调用如下代码

final boolean forceStopPackageLocked(String packageName, int appId,
        boolean callerWillRestart, boolean purgeCache, boolean doit,
        boolean evenPersistent, boolean uninstalling, int userId, String reason) {
    // ...
    // 获取 app 的所有 provider
    ArrayList<ContentProviderRecord> providers = new ArrayList<>();
    if (mCpHelper.getProviderMap().collectPackageProvidersLocked(packageName, null, doit,
            evenPersistent, userId, providers)) {
        if (!doit) {
            return true;
        }
        didSomething = true;
    }
    // 移除 provider
    for (i = providers.size() - 1; i >= 0; i--) {
        mCpHelper.removeDyingProviderLocked(null, providers.get(i), true);
    }
    // ...
}

不出意外,最终由 ContentProviderHelper 来移除 provider

boolean removeDyingProviderLocked(ProcessRecord proc, ContentProviderRecord cpr,
        boolean always) {
    // ...
    for (int i = cpr.connections.size() - 1; i >= 0; i--) {
        ContentProviderConnection conn = cpr.connections.get(i);
        // ...
        ProcessRecord capp = conn.client;
        final IApplicationThread thread = capp.getThread();
        conn.dead = true;
        // 1. 如有 stable provider 的客户端
        if (conn.stableCount() > 0) {
            final int pid = capp.getPid();
            // 注意,要排除 persistent app 进程,以及 system_server 进程
            if (!capp.isPersistent() && thread != null
                    && pid != 0 && pid != ActivityManagerService.MY_PID) {
                // 杀掉客户端进程
                capp.killLocked(
                        "depends on provider " + cpr.name.flattenToShortString()
                        + " in dying proc " + (proc != null ? proc.processName : "??")
                        + " (adj " + (proc != null ? proc.mState.getSetAdj() : "??") + ")",
                        ApplicationExitInfo.REASON_DEPENDENCY_DIED,
                        ApplicationExitInfo.SUBREASON_UNKNOWN,
                        true);
            }
        }
        // 2. 如果只有 unstable provider 客户端
        else if (thread != null && conn.provider.provider != null) {
            try {
                // 通知客户端移除数据
                thread.unstableProviderDied(conn.provider.provider.asBinder());
            } catch (RemoteException e) {
            }
            // In the protocol here, we don't expect the client to correctly
            // clean up this connection, we'll just remove it.
            cpr.connections.remove(i);
            if (conn.client.mProviders.removeProviderConnection(conn)) {
                mService.stopAssociationLocked(capp.uid, capp.processName,
                        cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
            }
        }
    }
    // ...
}

看到了吧,对于 stable provider,如果 provider 进程挂掉了,那么客户端也会受牵连被杀掉。

而对于 unstable provider,如果 provier 进程挂掉了,客户端只是移除了保存了的数据而已,并不会被杀掉。

最后,我们再来看看文章开头获取 provider 时关于两种 provider 代码

// ContentResolver.java
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
        @Nullable String[] projection, @Nullable Bundle queryArgs,
        @Nullable CancellationSignal cancellationSignal) {
    // ...
    // 获取 unstable provider
    IContentProvider unstableProvider = acquireUnstableProvider(uri);
    if (unstableProvider == null) {
        return null;
    }
    IContentProvider stableProvider = null;
    Cursor qCursor = null;
    try {
        // ...
        // 注意,这里获取的 stable provider 并返回
        final IContentProvider provider = (stableProvider != null) ? stableProvider
                : acquireProvider(uri);
        final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
        stableProvider = null;
        qCursor = null;
        // 返回数据
        return wrapper;
    } catch (RemoteException e) {
        return null;
    } finally {
        // ...
    }
}

我们可以看到,查询的时候使用的是 unstable provier,但是返回的结果 Curosr 使用的是 stable provider。这说明了什么? 它说明了,在 Cursor 没有被 close 之前,只要 provider 进程挂掉了,那么客户端也会受牵连,会被杀掉。

结束

以上就是ContentProvider客户端处理provider逻辑分析的详细内容,更多关于ContentProvider客户端provider的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android ContentProvider基础应用详解

    目录 一.适用场景 二.概念介绍 1.ContentProvider简介 2.Uri类简介 三.使用步骤 1.首先创建一个继承自ContentProvider的类,并实现其6个方法: 2.在Manifest文件中注册这个ContentProvider: 3.在外部应用中访问它: 一.适用场景 1.ContentProvider为存储和读取数据提供了统一的接口 2. 使用ContentProvider,应用程序可以实现数据共享 3. android内置的许多数据都是使用ContentProvide

  • Android利用ContentProvider初始化组件的踩坑记录

    目录 项目描述 问题排查 总结 项目描述 先简单描述一下遇到的问题. 项目比较庞大是以组件化的形式进行构建的,记录崩溃日志是由专门的一个组件去做,这里且叫它crash吧.而crash的核心逻辑如下: //伪代码 public class MyCrash implements UncaughtExceptionHandler { private static UncaughtExceptionHandler defaultUncaughtExceptionHandler; public stati

  • Android利用ContentProvider获取联系人信息

    本文实例为大家分享了Android利用ContentProvider获取联系人信息的具体代码,供大家参考,具体内容如下 在写代码前我们首先看一下运行的效果 运行效果如下: 点了获取联系人就展示如下效果 读取联系人信息的例子(MainActivity) package com.example.administrator.myapplication; import android.content.ContentResolver; import android.database.Cursor; imp

  • Android中多个ContentProvider的初始化顺序详解

    目录 缘起: 1. 利用 ContentProvider 初始化 Library: 2. 自定义 ContentProvider 初始化顺序: 总结 缘起: 利用 ContentProvider 来初始化你的 Library, 这个相信大家已经不太陌生了,下面简要说下. 1. 利用 ContentProvider 初始化 Library: 在日常开发过程中, 经常会遇到 Library 都需要传入 Context 参数以完成初始化,此时这个 Context 参数一般会从 Application

  • Android利用ContentProvider读取短信内容

    本文实例为大家分享了Android利用ContentProvider读取短信内容的具体代码,供大家参考,具体内容如下 首先,我们来看下运行效果 运行效果如下: 展示短信内容的效果如下: 布局文件(activity_sms.xml) <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/an

  • ContentProvider客户端处理provider逻辑分析

    目录 引言 1. 获取 provider 1.1 等待 provider 发布 1.2 安装 provider 2. provider 实现多进程实例 3. 两种 provider 区别 结束 引言 前面一篇文章分析了 AMS 端处理 provider 的逻辑,请读者务必仔细阅读前面一篇文章,否则看本文,你可能有很多疑惑. 以查询 provider 为例来分析客户端是如何处理 provider,它调用的是 ContentResolver#query() // ContentResolver.ja

  • Android手机App安全漏洞整理(小结)

    本文主要介绍了APP安全漏洞整理,分享给大家,具体如下: 1.源码安全漏洞 1.1 代码混淆漏洞 当前APK文件的安全性是非常令人堪忧的.APK运行环境依赖的文件/文件夹 res.DEX.主配文件Lib 只有简单的加密或者甚至没有任何加密.诸如apktool这类工具可轻易将其破解,再配合其他例如dex2jar.jd-gui等工具基本可以做到:源码暴露.资源文件暴露.主配文件篡改.核心SO库暴露.暴力破解恶意利用等.因此需要对安卓代码进行代码混淆. 代码混淆(Obfuscated code)亦称花

  • Java连接各种数据库的方法

    本文实例讲述了Java连接各种数据库的方法.分享给大家供大家参考.具体如下: 复制代码 代码如下: //MySQL:       String Driver="com.mysql.jdbc.Driver";   //驱动程序      String URL="jdbc:mysql://localhost:3306/db_name";    //连接的URL,db_name为数据库名       String Username="username"

  • Java SpringBoot整合SpringCloud

    目录 1. SpringCloud特点 2. 分布式系统的三个指标CAP 3. Eureka 4. SpringCloud Demo 4.1 registry 4.2 api 4.3 provider 4.4 consumer 4.5 POSTMAN一下 1. SpringCloud特点 SpringCloud专注于为典型的用例和扩展机制提供良好的开箱即用体验,以涵盖其他情况: 分布式/版本化配置 服务注册和发现 Eureka 路由 Zuul 服务到服务的呼叫 负载均衡 Ribbon 断路器 H

  • SpringBoot整合SpringCloud的过程详解

    目录 1. SpringCloud特点 2. 分布式系统的三个指标CAP 3. Eureka 4. SpringCloud Demo 4.1 registry 4.2 api 4.3 provider 4.4 consumer 4.5 POSTMAN一下 1. SpringCloud特点 SpringCloud专注于为典型的用例和扩展机制提供良好的开箱即用体验,以涵盖其他情况: 分布式/版本化配置 服务注册和发现 Eureka 路由 Zuul 服务到服务的呼叫 负载均衡 Ribbon 断路器 H

  • 基于Android ContentProvider的总结详解

    1.适用场景1) ContentProvider为存储和读取数据提供了统一的接口2) 使用ContentProvider,应用程序可以实现数据共享3) android内置的许多数据都是使用ContentProvider形式,供开发者调用的(如视频,音频,图片,通讯录等)2.相关概念介绍1)ContentProvider简介当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据.虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的

  • Android中ContentProvider和ContentResolver详解

    Android中ContentProvider和ContentResolver详解 在Android中,我们的应用有的时候需要对外提供数据接口,可以有如下几种方法: 1)AIDL 2)Broadcast 3)ContentProvider. 使用AIDL需要我们编写AIDL接口以及实现,而且对方也要有相应的接口描述,有点麻烦:使用Broadcast,我们不需要任何接口描述,只要协议文档就可以了,但是有点不好就是,这种方式不直接而且是异步的:使用ContentProvider我们不需要接口描述,只

  • Android应用中使用ContentProvider扫描本地图片并显示

    之前群里面有朋友问我,有没有关于本地图片选择的Demo,类似微信的效果,他说网上没有这方面的Demo,问我能不能写一篇关于这个效果的Demo,于是我研究了下微信的本地图片选择的Demo,自己仿照的写了下分享给大家,希望对以后有这样子需求的朋友有一点帮助吧,主要使用的是ContentProvider扫描手机中的图片,并用GridView将图片显示出来,关于GridView和ListView显示图片的问题,一直是一个很头疼的问题,因为我们手机的内存有限,手机给每个应用程序分配的内存也有限,所以图片多

  • Android开发教程之ContentProvider数据存储

    一.ContentProvider保存数据介绍 一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProvider是以类似数据库中表的方式将数据暴露的.那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一样,只不过是采用URL来表示外界需要访问的"数据库". ContentProvider提供了一种多应用间数据共享的方式. ContentProvider是个实现了一组用于提供其他应用程序存取数据的标准方法的类.应用

  • 实例讲解Android中ContentProvider组件的使用方法

    ContentProvider基本使用 为了在应用程序之间交换数据,android提供了ContentProvider,ContentProvider是不同应用程序之间进行数据交换的标准API,当一个应用程序需要把自己的数据暴露给其他程序使用时,该应用程序就可以通过提供ContentPRovider来实现,其他应用程序就可以通过ContentResolver来操作ContentProvider暴露的数据. 实现ContentProvider的步骤: 1)编写一个类,继承ContentProvid

随机推荐