【Android】 IntentService.java 分析

本文详细解析了Android中的IntentService组件工作原理。介绍了如何自定义IntentService类,并深入探讨了其内部处理流程,包括消息传递机制和服务启动停止过程。

 

1、使用IntentService,需要自定义一个继承IntentService的类。

2、看看 IntentService.java 的代码 。Sdk/sources/android-25/android/app/IntentService.java

      2.1  IntentService.java --> onCreate()

 @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

在 oncreate 方法中我们可以看到 首先创建了一个HandlerThread对象,获取此对象绑定的looper,把ServiceHandler与获取的looper绑定。这样ServiceHandler就绑定到了HandlerThread。

如果对这个过程不是很熟悉,推荐看一下其他人的文章了解一下。传送门

   2.2  IntentService.java --> onStart()

  

@Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

onStart方法把消息给ServiceHandler。
 

 

   2.3 IntentService.java的内部类ServiceHandler

 

private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

ServiceHandler类继承了 Handler类,通过handleMessage(Message msg) 方法处理onStart方法传递过来的消息。

 

从handleMessage方法中的代码可以看到,第一步调用了onHandleIntent方法,该方法在 自定义的继承IntentService的类中实现(也是在该方法中处理自己的逻辑)。

由于ServiceHandler与子线程(HandlerThread)绑定的,onHandleIntent方法是在子线程中被调用的,所以可以在onHandleIntent方法中处理耗时操作。

handleMessage方法 处理完成onHandleIntent之后,调用了stopSelf。所以IntentService不需要人为stop。

3、贴上 IntentService.java 的全部代码

 

/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.app;

import android.annotation.WorkerThread;
import android.annotation.Nullable;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;

/**
 * IntentService is a base class for {@link Service}s that handle asynchronous
 * requests (expressed as {@link Intent}s) on demand.  Clients send requests
 * through {@link android.content.Context#startService(Intent)} calls; the
 * service is started as needed, handles each Intent in turn using a worker
 * thread, and stops itself when it runs out of work.
 *
 * <p>This "work queue processor" pattern is commonly used to offload tasks
 * from an application's main thread.  The IntentService class exists to
 * simplify this pattern and take care of the mechanics.  To use it, extend
 * IntentService and implement {@link #onHandleIntent(Intent)}.  IntentService
 * will receive the Intents, launch a worker thread, and stop the service as
 * appropriate.
 *
 * <p>All requests are handled on a single worker thread -- they may take as
 * long as necessary (and will not block the application's main loop), but
 * only one request will be processed at a time.
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 * <p>For a detailed discussion about how to create services, read the
 * <a href="{@docRoot}guide/components/services.html">Services</a> developer
 * guide.</p>
 * </div>
 *
 * @see android.os.AsyncTask
 */
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    /**
     * Unless you provide binding for your service, you don't need to implement this
     * method, because the default implementation returns null.
     * @see android.app.Service#onBind
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * This method is invoked on the worker thread with a request to process.
     * Only one Intent is processed at a time, but the processing happens on a
     * worker thread that runs independently from other application logic.
     * So, if this code takes a long time, it will hold up other requests to
     * the same IntentService, but it will not hold up anything else.
     * When all requests have been handled, the IntentService stops itself,
     * so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link
     *               android.content.Context#startService(Intent)}.
     *               This may be null if the service is being restarted after
     *               its process has gone away; see
     *               {@link android.app.Service#onStartCommand}
     *               for details.
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

 

 

 

 

 

07-24
<think>我们正在解决一个具体的错误:由于FileProvider配置错误导致的`IllegalArgumentException`,错误信息通常是`Couldn't find meta-data for provider with authority`或`java.lang.IllegalArgumentException: Failed to find configured root that contains /data/...`。这个错误发生在使用`FileProvider.getUriForFile()`时,系统无法根据提供的authority找到对应的meta-data配置。 ### 问题分析 当出现`Couldn't find meta-data for provider with authority`错误时,通常有以下原因: 1. **AndroidManifest.xml中FileProvider的`<meta-data>`元素缺失或路径错误**:FileProvider需要配置一个`<meta-data>`指向一个XML文件,该文件定义了可共享的目录。 2. **`authorities`字符串不匹配**:在`FileProvider.getUriForFile()`中使用的authority字符串必须与AndroidManifest.xml中FileProvider的`android:authorities`属性完全一致(包括大小写)。 3. **XML文件路径配置错误**:在`res/xml`目录下的配置文件(如`file_paths.xml`)可能不存在,或者其中定义的路径没有包含要共享的文件。 ### 解决方案 #### 步骤1:检查AndroidManifest.xml中的FileProvider配置 确保在`AndroidManifest.xml`的`<application>`标签内正确声明了FileProvider,并且指定了`meta-data`元素。例如: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> <!-- 确保这里的resource指向正确的xml文件 --> </provider> ``` 注意: - `android:authorities`的值应该是一个唯一的字符串,通常使用应用包名加上后缀(如`.fileprovider`)。在代码中调用`FileProvider.getUriForFile()`时,必须使用相同的字符串。 - `<meta-data>`的`android:resource`属性必须指向`res/xml`目录下的一个XML文件(如`@xml/file_paths`)。如果该文件不存在,需要创建。 #### 步骤2:创建或检查file_paths.xml文件 在`res/xml`目录下创建`file_paths.xml`文件(如果不存在)。该文件定义了FileProvider可以访问的文件路径。以下是一个示例: ```xml <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 共享外部存储根目录 --> <external-path name="external_storage" path="." /> <!-- 共享应用内部存储的files目录 --> <files-path name="internal_files" path="." /> <!-- 共享应用内部缓存目录 --> <cache-path name="cache" path="/" /> <!-- 共享外部存储中应用专属目录(Android/data/...) --> <external-files-path name="external_app_files" path="." /> <!-- 共享外部缓存目录 --> <external-cache-path name="external_app_cache" path="." /> </paths> ``` 根据你要共享的文件所在的位置,选择合适的路径型: - `<files-path>`:对应`Context.getFilesDir()`。 - `<cache-path>`:对应`Context.getCacheDir()`。 - `<external-path>`:对应`Environment.getExternalStorageDirectory()`(即外部存储根目录)。 - `<external-files-path>`:对应`Context.getExternalFilesDir(String)`。 - `<external-cache-path>`:对应`Context.getExternalCacheDir()`。 确保在`file_paths.xml`中至少包含一个能覆盖你要共享文件的路径。例如,如果文件位于外部存储的应用专属缓存目录(如`/storage/emulated/0/Android/data/<package>/cache/`),则应该包含`<external-cache-path>`。 #### 步骤3:确保代码中的authority与Manifest一致 在调用`FileProvider.getUriForFile()`时,传入的authority参数必须与AndroidManifest.xml中定义的`android:authorities`完全一致。例如: ```java // 使用与Manifest中相同的authority字符串 Uri contentUri = FileProvider.getUriForFile( context, context.getPackageName() + ".fileprovider", // 例如:如果Manifest中是"${applicationId}.fileprovider",这里就使用包名+".fileprovider" file ); ``` 注意:如果使用Gradle构建,`${applicationId}`会在构建时被替换为应用的包名。因此,在代码中可以使用`context.getPackageName() + ".fileprovider"`来动态生成authority。 #### 步骤4:确保文件路径在配置的范围内 FileProvider只会为那些位于`file_paths.xml`中定义过的目录下的文件生成URI。如果尝试共享一个不在这些目录下的文件,就会抛出`IllegalArgumentException`。例如,如果你在`file_paths.xml`中只配置了`<external-path>`,那么只能共享位于外部存储根目录下的文件。 #### 步骤5:清理和重建项目 有时Gradle或Android Studio可能没有及时更新资源文件。尝试清理并重建项目: 1. 点击菜单栏的 `Build` -> `Clean Project`。 2. 然后点击 `Build` -> `Rebuild Project`。 #### 步骤6:运行时权限 虽然FileProvider本身不需要运行时权限,但访问某些目录(如外部存储)可能需要权限。确保已经申请了必要的权限(如`READ_EXTERNAL_STORAGE`或`WRITE_EXTERNAL_STORAGE`),特别是针对Android 6.0及以上版本。 ### 示例:修复后的完整代码 假设我们要共享一个位于外部缓存目录的文件(`context.getExternalCacheDir()`): 1. **AndroidManifest.xml**: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` 2. **res/xml/file_paths.xml**: ```xml <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-cache-path name="external_cache" path="/" /> <!-- 其他路径根据需要添加 --> </paths> ``` 3. **Java代码**: ```java File file = new File(context.getExternalCacheDir(), "test.apk"); Uri contentUri = FileProvider.getUriForFile( context, context.getPackageName() + ".fileprovider", file ); Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setDataAndType(contentUri, "application/vnd.android.package-archive"); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(installIntent); ``` ### 常见问题排查 - **错误:`Failed to find configured root that contains /data/data/your.package/cache/filename`** 原因:你的文件位于内部存储(如`getCacheDir()`),但`file_paths.xml`中没有配置`<cache-path>`。 解决:在`file_paths.xml`中添加`<cache-path name="cache" path="." />`。 - **错误:`java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/0/...`** 原因:文件位于外部存储,但`file_paths.xml`中配置的路径没有覆盖该位置。 解决:根据文件位置添加对应的路径元素(如`<external-path>`、`<external-files-path>`或`<external-cache-path>`)。 - **错误:`java.lang.SecurityException: Provider does not allow granting of Uri permissions`** 原因:在AndroidManifest.xml中,FileProvider的`android:grantUriPermissions`属性未设置为`true`。 解决:确保该属性为`true`。 ### 总结 通过正确配置FileProvider的authority、meta-data和路径文件,可以解决`IllegalArgumentException: Couldn't find meta-data for provider with authority`错误。关键点包括: 1. 确保AndroidManifest.xml中FileProvider的`<meta-data>`指向正确的XML资源文件。 2. 在`res/xml`下创建路径配置文件,并包含文件所在目录。 3. 代码中使用的authority必须与Manifest中定义的一致。 [^1]: 参考Android官方文档:https://developer.android.com/reference/androidx/core/content/FileProvider §§相关问题§§ 1. 如何为FileProvider配置多个路径? 2. FileProvider在Android 11(API 30)及以上版本需要注意什么? 3. 如何调试FileProvider的路径匹配问题?</think>### 解决 FileProvider 配置错误导致的 `IllegalArgumentException` 当 Android 应用中出现 `java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority` 错误时,通常是由于 FileProvider 配置不正确导致的。以下是完整的解决方案: --- ### 错误原因分析 1. **缺失 `<meta-data>` 声明** FileProvider 需要 `<meta-data>` 元素指向路径配置文件,否则系统无法识别可共享的目录。 2. **authority 值不匹配** `FileProvider.getUriForFile()` 调用的 authority 参数必须与 Manifest 中的声明完全一致。 3. **路径配置文件错误** `file_paths.xml` 文件未创建或路径配置不正确,导致系统找不到文件根目录。 4. **XML 文件位置错误** 配置文件必须放在 `res/xml/` 目录下,其他位置无效。 --- ### 解决方案步骤 #### 步骤 1:检查 AndroidManifest.xml 配置 确保在 `<application>` 标签内有完整的 FileProvider 声明: ```xml <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <!-- 关键:必须包含 meta-data 指向路径配置 --> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` **注意**: - `android:authorities` 必须使用唯一值(推荐 `${applicationId}.fileprovider`) - `android:resource` 必须指向 `@xml/file_paths` #### 步骤 2:创建路径配置文件 1. 在 `res/` 目录下创建 `xml` 文件夹(如不存在) 2. 在 `res/xml/` 中创建 `file_paths.xml` 文件 3. 添加正确的路径配置: ```xml <?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 对应 Context.getExternalCacheDir() --> <external-cache-path name="cache_files" path="/" /> <!-- 对应 getExternalFilesDir(null) --> <external-files-path name="external_files" path="." /> <!-- 对应 Environment.getExternalStorageDirectory() --> <external-path name="external_storage" path="." /> <!-- 对应 getCacheDir() --> <cache-path name="internal_cache" path="/" /> </paths> ``` **路径型说明**: - `<external-cache-path>`:外部缓存目录 - `<external-files-path>`:应用专属外部文件 - `<external-path>`:整个外部存储 - `<cache-path>`:内部缓存目录 #### 步骤 3:确保代码中的 authority 匹配 在 Java/Kotlin 代码中调用时: ```java // 正确示例(使用与 Manifest 相同的 authority) Uri uri = FileProvider.getUriForFile( context, context.getPackageName() + ".fileprovider", // 必须匹配! file ); // 设置 Intent 并添加权限标志 intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); ``` #### 步骤 4:验证文件路径 确保文件位于配置的路径内: ```java // 正确:文件在配置的缓存目录中 File file = new File(context.getExternalCacheDir(), "test.apk"); // 错误:文件不在任何配置路径中(会触发异常) File invalidFile = new File("/sdcard/random_folder/test.apk"); ``` #### 步骤 5:清理和重建项目 1. 执行 **Build > Clean Project** 2. 执行 **Build > Rebuild Project** 3. 删除应用并重新安装 --- ### 常见错误场景修复 #### 场景 1:缺少 `file_paths.xml` **现象**: `java.lang.IllegalArgumentException: Couldn't find meta-data for provider` **修复**: 创建 `res/xml/file_paths.xml` 并添加至少一个路径配置: ```xml <paths> <cache-path name="default_cache" path="/" /> </paths> ``` #### 场景 2:authority 不匹配 **现象**: `java.lang.IllegalArgumentException: No provider for [your.package].provider` **修复**: 统一 Manifest 和代码中的 authority: ```java // 在代码中使用动态包名 String authority = context.getPackageName() + ".fileprovider"; FileProvider.getUriForFile(context, authority, file); ``` #### 场景 3:路径未覆盖文件位置 **现象**: `Failed to find configured root that contains /data/...` **修复**: 在 `file_paths.xml` 中添加对应路径: ```xml <!-- 添加内部文件目录支持 --> <files-path name="internal_files" path="/" /> ``` --- ### 完整配置示例 **AndroidManifest.xml**: ```xml <application> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application> ``` **res/xml/file_paths.xml**: ```xml <paths> <external-path name="external" path="." /> <external-files-path name="external_files" path="." /> <external-cache-path name="external_cache" path="." /> <cache-path name="internal_cache" path="." /> <files-path name="internal_files" path="." /> </paths> ``` **Java 调用代码**: ```java File apkFile = new File(getExternalFilesDir(null), "test.apk"); Uri contentUri = FileProvider.getUriForFile( context, context.getPackageName() + ".fileprovider", apkFile ); Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setDataAndType(contentUri, "application/vnd.android.package-archive"); installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(installIntent); ``` 通过以上步骤,可彻底解决 `Couldn't find meta-data for provider` 异常。关键点是确保 Manifest 声明、路径配置和代码调用三者的 authority 和路径完全一致[^1]。 [^1]: 基于 Android 官方 FileProvider 文档的最佳实践
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值