(八)Service 应用



8.1 Service 概述

Service (服务)是能够在后台执行长时间运行操作并且不提供用户界面的应用程序组件。其他应用程序组件能启动服务,并且即便用户切换到另一个应用程序,服务还可以在后台运行。此外,组件能够绑定到服务并与之交互,甚至执行进程间通信(IPC) 。例如,服务能在后台处理网络事务、播放音乐、执行文件IO或者与ContentProvider通信。

8.1.1 Service 的分类

服务从本质上可以分为以下两种类型。
[V] Started (启动):当应用程序组件(如Activity )通过调用startService()方法启动服务时,服务处于started状态。 一旦启动,服务能在后台无限期运行,即使启动它的组件已经被销毁。通常,启动服务执行单个操作并且不会向调用者返回结果。例如,它可能通过网络下载或者上传文件。如果操作完成,服务需要停止自身。
[V] Bound (绑定) :当应用程序组件通过调用bindService0方法绑定到服务时,服务处于bound状态。绑定服务提供客户端-服务器接口,以允许组件与服务交互、发送请求、获得结果,甚至使用进程间通信(IPC) 跨进程完成这些操作。仅当其他应用程序组件与之绑定时,绑定服务才运行。多个组件可以一次绑定到 一个服务上,当它们都解绑定时,服务被销毁。

尽管本章将两种类型的服务分开讨论,服务也可以同时属于这两种类型,既可以启动(无限期运行)也能绑定。其重点在于是否实现一些回 调方法: onStartCommand()方 法允许组件启动服务; onBind()方法 允许组件绑定服务。
不管应用程序是否为启动状态、绑定状态或者两者兼有,都能通过Intent使用服务,就像使用Activity那样。然而,开发人员可以在配置文件中将服务声明为私有的,从而阻止其他应用程序访问。
服务运行于管理它的进程的主线程,服务不会创建自己的线程,也不会运行于独立的进程(除非开发人员定义)。这意味着,如果服务要完成CPU密集工作或者阻塞操作(如MP3回放或者联网),开发人员需要在服务中创建新线程来完成这些工作。通过使用独立的线程,能减少应用程序不响应(ANR)错误的风险,并且应用程序主线程仍然能用于用户与Activity的交互。

8.1.2 Service 类中的重要方法

为了创建服务,开发人员需要创建Service类( 或其子类)的子类。在实现类中,需要重写一些处理服务生命周期重要方面的回调方法,并根据需要提供组件绑定到服务的机制。需要重写的重要回调方法如下:

[V] onStartCommand()
当其他组件(如Activity)调用startService()方法请求服务启动时,系统调用该方法。一旦该方法执行, 服务就启动(处于started状态)并在后台无限期运行。如果开发人员实现该方法,则需要在任务完成时调用stopSelf()或stopService0方法停止服务(如果仅想提供绑定,则不必实现该方法)。
[V] onBind()
当其他组件调用bindService0方法想与服务绑定时(如执行RPC),系统调用该方法。在该方法的实现中,开发人员必须通过返回IBinder提供客户端用来与服务通信的接口。该方法必须实现,但是如果不想允许绑定,则返回null。
[V] onCreate()
当服务第一次创建时, 系统调用该方法执行一次性建 立过程(在系统调用onStartCommand()或onBind()方法前)。如果服务已经运行,该方法不被调用。
[V] onDestroy()
当服务不再使用并即将销毁时,系统调用该方法。服务应该实现该方法来清理诸如线程、注册监听器、接收者等资源。这是服务收到的最后调用。

回调描述
onStartCommand()其他组件(如活动)通过调用startService()来请求启动服务时,系统调用该方法。如果你实现该方法,你有责任在工作完成时通过stopSelf()或者stopService()方法来停止服务。
onBind()当其他组件想要通过bindService()来绑定服务时,系统调用该方法。如果你实现该方法,你需要返回IBinder对象来提供一个接口,以便客户来与服务通信。你必须实现该方法,如果你不允许绑定,则直接返回null。
onUnbind()当客户中断所有服务发布的特殊接口时,系统调用该方法。
onRebind()当新的客户端与服务连接,且此前它已经通过onUnbind(Intent)通知断开连接时,系统调用该方法。
onCreate()当服务通过onStartCommand()和onBind()被第一次创建的时候,系统调用该方法。该调用要求执行一次性安装。
onDestroy()当服务不再有用或者被销毁时,系统调用该方法。你的服务需要实现该方法来清理任何资源,如线程,已注册的监听器,接收器等。

在这里插入图片描述

8.1.3 Service 的声明

类似于Activity和其他组件,开发人员必须在应用程序配置文件中声明全部的Service。为了声明Service,需要向标签中添加子标签, 子标签的语法如下

<service android: enabled= ["true" | " false"]
	android:exported= ["true" |"false"]
	android:icon="drawable resource'
	android:label= "string resource'
	android:name= "string "
	android:permission="string"
	android:process="string" >
</service>

[V] android:enabled
服务能否被系统实例化,true表示可以,false 表示不可以,默认值是true。标签也有自己的enabled属性,用于包括服务的全部应用程序组件。 和的enabled属性必须同时设置成true (两者的默认值都是true)才能让服务可用。如果任何一个是false,服务被禁用并且不能实例化。
[V] android:exported
其他应用程序组件能否调用服务或者与其交互,true 表示可以,false 表示不可以。当该值是false时,只有同一个应用程序的组件或者具有相同用户ID的应用程序能启动或者绑定到服务。
默认值依赖于服务是否包含Intent 过滤器。若没有过滤器,说明服务仅能通过精确类名调用,这意味着服务仅用于应用程序内部( 因为其他程序可能不知道类名)。此时,默认值是false;若存在至少一个过滤器,暗示服务可以用于外部,因此默认值是true.该属性不是限制其他应用程序使用服务的唯一方式。 还可以使用permission屈性限制外部实体与服务交互。
[V]android:icon
表示服务的图标。该属性必须设置成包含图片定义的可绘制资源引用。如果没有设置,使用应用程序图标取代。服务图标不管在此设置还是在标签设置,都是所有服务的Intent过滤器默认图标。
[V] android:label
显示给用户的服务名称。如果没有设置,使用应用程序标签取代。服务标签不管在此设置还是在标签设置,都是所有服务的Intent过滤器默认标签。标签应该设置为字符串资源引用,这样可以像用户界面的其他字符串那样本地化。然而,为了开发时方便,也可以设置成原始字符串。
[V] android:name
实现服务的Service子类名称,应该是-一个完整的类名,如com.googl.RoomService.然而,为了简便,如果名称的第一个符号是点号(如.RoomService) ,则会增加在标签中定义的包名。一旦发布了应用程序,不应该再修改子类名称。该属性没有默认值并且必须指定。
[V] android:permission
实体必须包含的权限名称,以便启动或者绑定到服务。如果startService()、bindService()或 stopService()方法调用者没有被授权,方法调用无效,并且Intent对象也不会发送给服务。
如果没有设置该属性,使用标签的permission 属性设置给服务。如果 和标签的permission属性都未设置,服务不受权限保护.
[V] android:process
服务运行的进程名称。通常,应用程序的全部组件运行于为应用程序创建的默认进程。进程名称与应用程序包名相同。标签 的process 属性能为全部组件设置一个相同的默认值。但是组件能用自己的process属性重写默认值,从而允许应用程序跨越多个进程。如果分配给该属性的名称以冒号(:)开头,仅属于应用程序的新进程会在需要时创建,服务能在该进程中运行; 如果进程名称以小写字母开头,服务会运行在以此为名的全局进程,但需要提供相应的权限。这允许不同应用程序组件共享进程,减少资源使用。

8.2 创建 Started Service

Started Service (启动服务)是由其他组件调用startService()方法启动的,这导致服务的onStartCommand()方法被调用。
当服务是started状态时,其生命周期与启动它的组件无关,并且可以在后台无限期运行,即使启动服务的组件已经被销毁。因此,服务需要在完成任务后调用stopSelf()方法停止,或者由其他组件调用stopService()方法停止。
应用程序组件(如Activity)能通过调用startService0方法和传递Intent对象来启动服务,在Intent对象中指定了服务并且包含服务需要使用的全部数据。服务使用onStartCommand()方法接收Intent。
例如,假设Activity需要保存一些数据到在线数据库。Activity可以启动伴侣服务并通过传递Intent到startService()方法来发送需要保存的数据。服务在onStartCommand0方法中收到Intent,联入网络并执行数据库事务。当事务完成时,服务停止自身并销毁。
Android提供了两个类供开发人员继承以创建启动服务。
[V] Service: 这是所有服务的基类。当继承该类时,创建新线程来执行服务的全部工作是非常重要的。因为服务默认使用应用程序主线程,这可能降低应用程序Activity的运行性能。
[V] IntentService: 这是Service类的子类,它每次使用一个工作线程来处理全部启动请求。在不必同时处理多个请求时,这是最佳选择。开发人员仅需要实现onHandleIntent0方法,该方法接收每次启动请求的Intent 以便完成后台任务。

8.2.1 继承IntentService类

因为多数启动服务不必同时处理多个请求(在多线程情境下会很危险),所以使用IntentService 类实现服务是非常好的选择。IntentService 可完成如下任务:
[V]创建区别于应用程序主线程的默认工作线程来执行发送到onStartCommand()方法的全部Intent。
[V]创建工作队列,每次传递一个Intent到onHandleIntent)方法实现,这样就不必担心多线程。
[V]所有启动请求处理完毕后停止服务,这样就不必调用stopSelf()方法。
[V]提供onBind()方法默认实现,其返回值是null
[V]提供onStartCommand()方法默认实现,它先发送Intent到工作队列,然后到onHandlelntent()方法实现。
以上说明开发人员仅需要实现onHandleIntent()方法来完成客户端提供的任务。由于IntentService类没有提供空参数的构造方法,因此需要提供一个构造 方法。下面的代码是IntentSrvice实现类的例子,在onHandlerIntent()方法中,仅让线程休眠了5秒钟。

public class HelloIntentService extends IntentService {
    public HelloIntentService() {
        super("HelloIntentService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        long endtime = System.currentTimeMillis() + 5 * 1000;
        while(System.currentTimeMillis()<endtime){
            synchronized (this){
                try {
                    wait(endtime-System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

这就是实现IntentService类所必须的全部操作:没有参数的构造方法和onHandleIntent()方法。
如果开发人员决定重写其他回调方法,如onCreate()、onStartCommand()或 onDestroy(),需要调用父类实现,这样IntentService能正确处理工作线程的生命周期.
例如,onStartCommand()方法必 须返回默认实现:

@Override
public int onStartCommand (Intent intent, int flags, int startId) {
	Toast . makeText(this, "service starting", Toast. LENGTH SHORT). show();
	return super .onStartCommand ( intent , flags, startId);
}

除onHandleIntentO方法外,仅有onBind(方法不必调用父类实现,该方法在服务允许绑定时实现。

8.2.2 继承Service类

如上所述,使用IntentService类将简化启动服务的实现。然而,如果需要让服务处理多线程(取代使用工作队列处理启动请求),则可以继承Service类来处理各个Intent。
作为对比,下面通过实现Service类来完成与实现IntentService类完全相同的任务。对于每次启动请求,它使用工作线程来执行任务,并且每次处理一个请求。

public class HelloService extends Service {
    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler{
        public ServiceHandler(Looper looper){
            super(looper);
        }
    }
    public void handleMessage(Message msg){
        long endtime = System.currentTimeMillis() + 5 * 1000;
        while(System.currentTimeMillis()<endtime){
            synchronized (this){
                try {
                    wait(endtime-System.currentTimeMillis());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mServiceLooper= thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Message msg= mServiceHandler.obtainMessage();
        msg.arg1=startId;
        mServiceHandler.sendMessage(msg);
        return START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
    }
}

如上所示,这比使用IntentService增加了许多代码。
然而,由于开发人员自己处理onStartCommand()方法调用,可以同时处理多个请求。这与示例代码不同,但是如果需要,就可以为每次请求创建一个新线程并 且立即运行它们( 避免等待前一个请求结束)。
onStartCommand()方法必须返回一个 整数。该值用来描述系统停止服务后如何继续服务(如前所述, IntentService默认实现已经处理了这些,开发人员也可以进行修改)onStartCommand()方法返回值必 须是下列常量之一。
[V] START_ NOT STICKY
如果系统在onStartCommand()方法返回后停止服务,不重新创建服务,除非有PendingIntent要发送。为避免不在不需要的时候运行服务,这是最佳选择。
[V] START_ STICKY
如果系统在onStartCommand()方法返回后停止服务,重新创建服务并调用onStartCommand()方法,但是不重新发送最后的Intent;相反,系统使用空Intent调用onStartCommand0方法,除非有PendingIntent来启动服务,此时,这些Intent会被发送。这适合多媒体播放器(或者类似服务),它们不执行命令但是无限期运行并等待工作。
[V] START_REDELIVER_ INTENT
如果系统在onStartCommand0方法返回后停止服务,重新创建服务并使用发送给服务的最后Intent调用onStrtCommand0方法,全部PendingIntent依次发送。这适合积极执行应该立即恢复工作的服务,如下载文件。
说明:这些常 量都定义在Service类中。

8.2.3 启动服务

开发人员可以从Activity或者其他应用程序组件通过传递Intent 对象(指定要启动的服务)到startService()方法启动服务。Android 系统调用服务的onStartCommand()方法并将Intent 传递给它。
注意:请不要 直接调用onStartCommand0方法。
例如,Activity 能使用显式Intent 和startService()方法启动前面章节的示例服务(HelloService) ,其代码如下:

Intent intent = new Intent(this, HelloService.class);
startService(intent);

startService()方法立即返回,然后Android 系统调用服务的onStartCommand()方法。如果服务还没有运行,系统首先调用onCreate()方法,接着调用onStartCommand0方法。
如果服务没有提供绑定,startService()方 法发送的Intent是应用程序组件和服务之间唯一的通信模式。 然而,如果开发人员需要服务返回结果,则启动该服务的客户端能为广播 创建PendingIntent (使用getBroadcat()方法)并通过启动服务的Intent进行发送。服务接下来便能使用广播来发送结果。
多次启动服务的请求导致Senice的onStartCommand()方法被调用多次,然而,仅需要一个停 止方法(stopSelf()或stopService()方法)来停止服务。

8.2.4 停止服务

启动服务必须管理自己的生命周期,即系统不会停止或销毁服务,除非系统必须回收系统内存而且在onStartCommand()方法返回后服务继续运行。因此,服务必须调用stopSelf()方法停止自身,或者其他组件调用stopService()方法停止服务。
当使用stopSelf()或stopService()方法请求停止时,系统会尽快销毁服务。
然而,如果服务同时处理多个onStartCommand()方法调用请求,则处理完一个请求后,不应该停止服务,因为可能收到一个新的启动请求(在第一一个请求结束后停止会终止第二个请求)。为了解决这个问题,开发人员可以使用stopSelf(int)方法来确保停止服务的请求总是基于最近收到的启动请求。即当调用stopSelf(int)方法时,同时将启动请求的ID (发送给onStartCommand0方法的startld) 传递给停止请求。这样,如果服务在调用stopSelf(int)方法前接收到新启动请求,会因ID不匹配而不停止服务。
注意:应用程序应该在任务完成后停止服务,来避免系统资源浪费和电池消耗。如果必要,其他组件能通过stopService()方法停止服务。即便能够绑定服务,如果调用了onStartCommand()方法就必须停止服务。

8.2.5 实例1:继承IntentService输出当前时间

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/current_time"
       android:text="当前时间"
       android:textColor="@android:color/black"
       android:textSize="30dp"
       />

</LinearLayout>

CurrentTimeService.java

public class CurrentTimeService extends IntentService {
    public CurrentTimeService() {
        super("CurrentTimeService");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String currentTime = dateFormat.format(date);
        Log.i("CurrentTimeService",currentTime);//控制台输出当前时间
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.current_time);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startService(new Intent(MainActivity.this,CurrentTimeService.class));//启动服务
            }
        });
    }
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jingyi.aboutservice">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Application">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".CurrentTimeService"></service>
    </application>

</manifest>

在这里插入图片描述

在这里插入图片描述

8.2.6 实例 2: 继承Service 输出当前时间

修改上一项目中CurrentTimeService.java

public class CurrentTimeService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String currentTime = dateFormat.format(date);
        Log.i("CurrentTimeService",currentTime);//控制台输出当前时间
        return START_STICKY;
    }
}
8.3 创建 Bound Service

绑定服务是允许其他应用程序绑定并且与之交互的Service类实现类。为了提供绑定,开发人员必须实现onBind()回调方法。该方法返回IBinder对象,它定义了客户端用来与服务交互的程序接口。
客户端能通过bindService0方法绑定到服务。此时,客户端必须提供ServiceConnection接口的实现类,它监视客户端与服务之间的连接。bindService()方法立即返回,但是当Android 系统创建客户端与服务之间的连接时,它调用ServiceConnection接口的onServiceConncted()方法,来发送客户端用来与服务通信的IBinder对象。
多个客户端能同时连接到服务。然而,仅当第-一个客户端绑定时,系统调用服务的onBind()方法来获取IBinder对象。系统接着发送同一个IBinder对象到其他绑定的客户端,但是不再调用onBind()方法。
当最后的客户端与服务解绑定时,系统销毁服务(除非服务也使用startService()方法启动)。
在实现绑定服务时,最重要的是定义onBind()回调方法返回的接口,有以下3种方式。
(1)继承Binder类
如果服务对应用程序私有并且与客户端运行于相同的进程(这非常常见),则应该继承Binder类来创建接口,并且从onBind()方法返回其一个实例。 客户端接收Binder对象并用其来直接访问Binder 实现类或者Service类中可用公共方法。
当服务仅用于私有应用程序时,推荐使用该技术。但当服务可以用于其他应用程序或者访问独立进程时,则不能使用该技术。
(2)使用Messenger
如果需要接口跨进程工作,则可以使用Messenger来为服务创建接口。此时,服务定义Handler对象来响应不同类型的Message对象。Handler 是Messenger 的基础,能与客户端分享IBinder,允许客户端使用Message对象向服务发送命令。此外,客户端能定义自己的Messenger对象,这样服务能发送回消息。
使用Messenger是执行进程间通信(IPC) 的最简单方式,因为Messenger类将所有请求队列化到单独的线程,这样开发人员就不必设计服务为线程安全。
(3)使用AIDL
AIDL (Android接口定义语言)执行分解对象到原语的全部工作,以便操作系统能理解并且跨进程执行IPC。使用Messenger创建接口,实际上将AIDL作为底层架构。如上所述,Messenger 在单个线程中将所有客户端请求队列化,这样服务每次收到一个 请求。如果希望服务能同时处理多个请求,则可以直接使用AIDL。此时,服务必须能处理多线程并且要保证线程安全。
为了直接使用AIDL,开发人员必须创建定义编程接口的.aidl文件。AndroidSDK工具使用该文件来生成抽象类,它实现接口并处理IPC,然后就可以在服务中使用了。
说明:绝大多 数应用程序不应该使用AIDL来创建绑定服务,因为它需要多线程能力而且会导致更加复杂的实现。因此,本章不详细讲解AIDL的使用。

8.3.1 继承Binder类

如果服务仅用于本地应用程序并且不必跨进程工作,则开发人员可以实现自己的Binder类来为客户端提供访问服务公共方法的方式。
注意:这仅当客户端与服务位于同一个应用程序和进程时才有效,这也是最常见的情况。例如,音乐播放器需要绑定Activity到自己的服务来在后台播放音乐。
其实现步骤如下:
(1)在服务中,创建Binder类实例来完成下列操作之一:
[V]包含客户端能调用的公共方法。
[V]返回当前Service实例,其中包含客户端能调用的公共方法。
[V]返回服务管理的其他类的实例,其中包含客户端能调用的公共方法。
(2)从onBind0回调方法中返回Binder类实例.
(3)在客户端,从onServiceConnected()回调方法接收Binder类实例,并且使用提供的方法调用绑定服务。
说明:服务和客户端必须位于同一个应用程序的原因是,客户端能转型返回对象并且适当地调用其方法。服务和客户端必须也位于同一个进程,因为该技术不支持跨进程。
例如,下面的服务通过Binder实现类为客户端提供访问服务中方法的方法。

public class LocalService extends Service {
    private final IBinder binder=new LocalBinder();
    private final Random genertor=new Random();
    public class LocalBinder extends Binder{
        LocalService getService(){
            return LocalService.this;
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
    public  int getRandomNumber(){
        return  genertor.nextInt(100);
    }
}

LocalBinder类为客户端提供了getService()方 法来获得当前LocalService的实例。这允许客户端调用服务中的公共方法。例如,客户端能从服务中调用getRandomNumber()方法。
下面的Activity绑定到LocalService, 并且在单击按钮时调用getRandomNumber()方法。

public class BindingActivity extends AppCompatActivity {
    LocalService localService;
    boolean bound=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent,connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (bound){
            unbindService(connection);
            bound=false;
        }
    }
    public  void onButtonClick(View v){
        if (bound){
            int number = localService.getRandomNumber();
            Toast.makeText(this, "随机数字:"+number, Toast.LENGTH_SHORT).show();
        }
    }

    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
                LocalService.LocalBinder binder=(LocalService.LocalBinder)service;
                localService = binder.getService();
                bound=true;
        }
        public void  onServiceDisconnected(ComponentName arg0){
            bound=false;
        }
    };
}
8.3.2 使用Messenger类

如果开发人员需要服务与远程进程通信,则可以使用Messenger来为服务提供接口。该技术允许不使用AIDL执行进程间通信(IPC) 。
使用Messenger时需注意:
[V]实现Handler的服务因为每次从客户端调用而收到回调。
[V] Handler 用于创建Messenger对象(它是Handler的引用)。
[V] Messenger创建IBinder,服务从onBind0方法将其返回到客户端。
[V]客户端使用IBinder来实例化Messenger,然后使用它来发送Message对象到服务。
[V]服务在其Handler的handleMessage0方法接收Message.
此时,没有供客户端在服务上调用的方法。相反,客户端发送消息(Message 对象)到服务的Handler方法。
下面的代码演示了使用Messenger接口的服务:

public class MessengerService extends Service {
    static final int HELLO_WORLD = 1;

    class IncomingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case HELLO_WORLD:
                    Toast.makeText(getApplicationContext(), "Hello World!",
                            Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    final Messenger messenger = new Messenger(new IncomingHandler());

    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "Binding", Toast.LENGTH_SHORT).show();
        return messenger.getBinder();
    }
}

Handler中的handleMessage()方法是服务接收Message对象的地方,并且根据Message类的what成员变量决定如何操作。
客户端需要完成的全部工作就是根据服务返回的IBinder创建Messenger并且使用send()方法发送消息。例如,下面的Activity绑定到服务并发送HELLO WORLD给服务。

public class ActivityMessenger extends AppCompatActivity {
    Messenger messenger=null;
    boolean bound;
    private ServiceConnection connection=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            messenger=new Messenger(service);
            bound=true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            messenger=null;
            bound=false;
        }
        public void halo(View v){
            if (!bound)
                return;
            Message msg=Message.obtain(null,MessengerService.HELLO_WORLD,0,0);
            try {
                messenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        bindService(new Intent(this,MessengerService.class),connection,Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (bound){
            unbindService(connection);
            bound=false;
        }
    }
}

该实例并没有演示服务如何响应客户端。如果希望服务响应,则需要在客户端也创建Messenger。当客户端收到onServiceConected()回调方法时,发送Message到服务。Message 的replyTo成员变量包含客户端的Messenger。

8.3.3 绑定到服务

应用程序组件(客户端)能调用bindService()方法绑定到服务,接下来Android系统调用服务的onBind()方法,返回IBinder来与服务通信。
绑定是异步的。bindService()方法立即返回并且不返回IBinder 到客户端。为了接收IBinder,客户端必须创建ServiceConnection实例,然后将其传递给bindService()方法。ServiceConnection 包含系统调用发送IBinder的回调方法。
注意:只有Activity、Service 和ContentProvider能绑定到服务,BroadcastReceiver 不能绑定到服务。
如果需要从客户端绑定服务,需要完成以下操作:
(1)实现ServiceConnection,这需要重写onServiceConnected()和onServiceDisconnected()两个回调方法。
(2)调用bindService()方法,传递ServiceConnection实现。
(3)当系统调用onServiceConnected()回调方法时,就可以使用接口定义的方法调用服务。
(4)调用unbindService()方法解绑定。
当客户端销毁时,会将其从服务上解绑定。但是当与服务完成交互或者Activity 暂停时,最好解绑定,以便系统能及时停止不用的服务。

8.3.4 实例1 : 继承Binder类绑定服务显示时间

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/current_time"
       android:text="当前时间"
       android:textColor="@android:color/black"
       android:textSize="30dp"
       />

</LinearLayout>

CurrentTimeService.java

public class CurrentTimeService extends Service {
    private final IBinder binder=new LocalBinder();
    public  class LocalBinder extends Binder{
        CurrentTimeService getService(){
            return CurrentTimeService.this;//返回当前服务的实例
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    public String getCurrentTime(){
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        String currentTime = dateFormat.format(date);
        return currentTime;
    }
}

CurrentTimeActivity.java

public class CurrentTimeActivity extends AppCompatActivity {
    CurrentTimeService currentTimeService;
    boolean bound;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Button button = (Button) findViewById(R.id.current_time);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(CurrentTimeActivity.this, CurrentTimeService.class);
                bindService(intent,serviceconn,BIND_AUTO_CREATE);//绑定服务
                if (bound){
                    Toast.makeText(CurrentTimeActivity.this, currentTimeService.getCurrentTime(), Toast.LENGTH_SHORT).show();
                }
            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (bound){
            bound=false;
            unbindService(serviceconn);
        }
    }
    private  ServiceConnection serviceconn=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            CurrentTimeService.LocalBinder binder = (CurrentTimeService.LocalBinder) service;//获得自定义的LocalBinder对象
            binder.getService();//获取CurrentTimeService对象
            bound=true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bound=false;
        }
    };
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jingyi.aboutservice">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Application">
        <activity
            android:name=".CurrentTimeActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".CurrentTimeService"></service>
    </application>

</manifest>

在这里插入图片描述

8.3.5 实例 2: 使用Messenger类绑定服务显示时间

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:id="@+id/current_time"
       android:text="当前时间"
       android:textColor="@android:color/black"
       android:textSize="30dp"
       />

</LinearLayout>

CurrentTimeService.java

public class CurrentTimeService extends Service {
    public static final int CURRENT_TIME=0;

    private class IncomingHandler extends Handler{
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what==CURRENT_TIME){
                Date date = new Date();
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                String currentTime = dateFormat.format(date);
                Toast.makeText(CurrentTimeService.this, currentTime, Toast.LENGTH_SHORT).show();
            }else {
                super.handleMessage(msg);
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        Messenger messenger = new Messenger(new IncomingHandler());
        return messenger.getBinder();
    }

}

CurrentTimeActivity.java

public class CurrentTimeActivity extends AppCompatActivity {
    Messenger messenger;
    boolean bound;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Button button = (Button) findViewById(R.id.current_time);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(CurrentTimeActivity.this, CurrentTimeService.class);
                bindService(intent,serviceconn,BIND_AUTO_CREATE);//绑定服务
                if (bound){
                    Message message = Message.obtain(null, CurrentTimeService.CURRENT_TIME, 0, 0);
                    try {
                        messenger.send(message);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (bound){
            bound=false;
            unbindService(serviceconn);
        }
    }
    private  ServiceConnection serviceconn=new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            messenger = new Messenger(service);
            bound=true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            messenger=null;
            bound=false;
        }
    };
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jingyi.aboutservice">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Application">
        <activity
            android:name=".CurrentTimeActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".CurrentTimeService"></service>
    </application>

</manifest>

在这里插入图片描述

8.4 管理 Service 的生命周期

服务的生命周期比Activity简单很多,但是却需要开发人员更加关注服务如何创建和销毁,因为服务可能在用户不知情的情况下在后台运行。服务的生命周期可以分成两个不同的路径:
[V] Started Service
当其他组件调用startServicc()方法时,服务被创建。接着服务无限期运行,其自身必须调用stopSelf()方法或者其他组件调用stopService()方法来停止服务。当服务停止时,系统将其销毁。
[V] Bound Service
当其他组件调用bindService()方法时,服务被创建。接着客户端通过IBinder接口与服务通信。客户端通过unbindService()方法关闭连接。多个客户端能绑定到同一个服务并且当它们都解绑定时,系统销毁服务(服务不需要被停止)。
这两条路径并非完全独立,即开发人员可以绑定已经使用startService()方法启动的服务。例如,后台音乐服务能使用包含音乐信息的Intent通过调用startService(方法启动。当用户需要控制播放器或者获得当前音乐信息时,可以调用bindService()方法绑定Activity 到服务。此时,stopService()和 stopSelf()方法直到全部客户端解绑定时才能
停止服务。下图演示了两类服务的生命周期。
在这里插入图片描述

练习8

登录后主界面列表选择第七个选项后,跳转到另一个Activity,其含音乐文件选择控件、启动和停止按钮、状态显示区,当选择一个音乐启动播放后,Activity可以退出,音乐在后台不停,
再次进去应用,能正确显示播放状态,能停止音乐播放。

MainActivity.java

package com.jingyi.newwork;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName MainActivity
 * @Description TODO
 * @Author Yu
 * @Date 2022/5/13 16:11
 * @Version 1.0
 **/
public class MainActivity extends Activity {
    public ListView mListView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = findViewById(R.id.listView);

        String[] name = {"图库", "音量与亮度", "网络监控", "手机设置", "我的账单","在线音乐","本地音乐"};
        int[] nums = {1, 2, 3, 4, 5, 6,7};
        List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
        for (int i = 0; i < name.length; i++) {
            Map<String, Object> map = new HashMap<>();
            map.put("name", name[i]);
            map.put("index", nums[i]);
            list.add(map);
        }
        SimpleAdapter adapter = new SimpleAdapter(this, list, R.layout.item, new String[]{"name", "index"}, new int[]{R.id.name_item, R.id.index_item});
        mListView.setAdapter(adapter);

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if(position==0){
                    Intent intent = new Intent(MainActivity.this, imagesActivity.class);
                    startActivity(intent);
                }if(position==1){
                    Intent intent = new Intent(MainActivity.this, AdjustActivity.class);
                    Bundle bundle = new Bundle();
                    bundle.putString("sentence","请在这里进行详细调节");
                    intent.putExtras(bundle);
                    startActivity(intent);
                }if (position==2){//点击第三项
                    Bean bean = new Bean();
                    bean.setFunctionName("网络监控");
                    bean.setFunctionDescription("监控网络连接状态");
                    Intent intent = new Intent();
                    Byte aByte = new Byte((byte) 33);
                    intent.putExtra("intNum",3);
                    intent.putExtra("byteNum",aByte);
                    intent.putExtra("Info",bean);
                    intent.setClass(MainActivity.this,NetActivity.class);
                    startActivity(intent);
                }if (position==3){//点击第四项
                    Intent intent = new Intent(MainActivity.this,PhoneSetActivity.class);
                    startActivity(intent);
                }if(position==4){//点击第五项
                    Intent intent = new Intent(MainActivity.this,Bookkeeping.class);
                    startActivity(intent);
                }if (position==5){
                    Intent intent = new Intent(MainActivity.this,MusicActivity.class);
                    startActivity(intent);
                }if (position==6){
                    Intent intent = new Intent(MainActivity.this,MusicServiceActivity.class);
                    startActivity(intent);
                }
                Toast.makeText(MainActivity.this, "您选中的是第"+(position+1)+"项", Toast.LENGTH_SHORT).show();
            }
        });


    }
}

musicservicelayout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <ImageButton
        android:id="@+id/addMusic_btn"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_marginTop="85dp"
        android:layout_marginLeft="165dp"
        android:background="#00FF0000"
        android:src="@drawable/addmusic"
        android:scaleType="centerInside"
        />
    <TextView
        android:id="@+id/music_path"
        android:layout_marginTop="30dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        />
    <ProgressBar
        android:id="@+id/music_bar"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="40dp"
        android:layout_width="345dp"
        android:layout_height="5dp"
        style="?android:progressBarStyleHorizontal"/>

    <ImageButton
        android:src="@drawable/play"
        android:id="@+id/function_btn"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginTop="48dp"
        android:background="@drawable/bg_play"
        android:layout_gravity="center"
        />
</LinearLayout>

MusicService.java

package com.jingyi.newwork;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import androidx.annotation.Nullable;

/**
 * @ClassName MusicService
 * @Description TODO
 * @Author Yu
 * @Date 2022/6/10 12:13
 * @Version 1.0
 **/
public class MusicService extends Service {
    public static MediaPlayer mediaPlayer;
    private String path=null;//音乐文件路径
    private final Binder binder=new LocalBinder();
    public  class LocalBinder extends Binder{
        MusicService getMusicService(){
            return MusicService.this;//返回当前服务实例
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        path=intent.getStringExtra("Filepath");

        Log.i("serPAth",path+"ccdd");
        mediaPlayer= MediaPlayer.create(this, Uri.parse(path));
        /*
         * 这里没法使用setDataSource(),具体原因我也弄不清楚,这个问题折磨了我半天时间
         * */
        //mediaPlayer.setDataSource(path);
        //mediaPlayer.prepare();//装载流媒体文件
        return binder;
    }

    public  void play(){//播放
        mediaPlayer.start();
    }

    public void pause(){//暂停
        mediaPlayer.pause();
    }


}

MusicServiceActivity.java

package com.jingyi.newwork;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import java.util.Timer;
import java.util.TimerTask;

/**
 * @ClassName MusicServiceActivity
 * @Description TODO
 * @Author Yu
 * @Date 2022/6/10 12:14
 * @Version 1.0
 **/
public class MusicServiceActivity extends Activity {
    private ImageButton add,function;
    private TextView musicPath_tv;
    private ProgressBar bar;

    private String path;

    MusicService musicService;
    boolean bound;
    boolean play=true;

    private ServiceConnection connection=new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            MusicService.LocalBinder binder = (MusicService.LocalBinder) service;
            musicService = binder.getMusicService();
            bound=true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bound=false;
        }
    };

    @Override
    protected void onDestroy() {
        if (bound){
            bound=false;
            unbindService(connection);
        }
        super.onDestroy();
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.musicservicelayout);

        add=findViewById(R.id.addMusic_btn);
        function=findViewById(R.id.function_btn);
        musicPath_tv=findViewById(R.id.music_path);
        bar=findViewById(R.id.music_bar);

        add.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showFileSelect();
            }
        });

        function.setOnClickListener(new View.OnClickListener() {
            @RequiresApi(api = Build.VERSION_CODES.M)
            @Override
            public void onClick(View v) {
                if (play){
                    if (bound){
                        play=false;
                        function.setImageResource(R.drawable.pause);
                        musicService.play();
                        Toast.makeText(MusicServiceActivity.this, "开始播放", Toast.LENGTH_SHORT).show();
                        bar.setMax(musicService.mediaPlayer.getDuration());//设置总进度
                        Timer timer = new Timer();
                        TimerTask timerTask = new TimerTask(){
                            @Override
                            public void run() {
                                bar.setProgress(musicService.mediaPlayer.getCurrentPosition());//设置当前进度
                            }
                        };
                        timer.schedule(timerTask,0,10);//每10毫秒刷新一次
                    }
                }else{
                    if(bound) {
                        play=true;
                        function.setImageResource(R.drawable.play);
                        musicService.pause();
                        Toast.makeText(MusicServiceActivity.this, "播放暂停", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
    }

    private void showFileSelect() {//进入文件管理器
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("*/*");//无类型限制
//        有类型限制是这样的:
//        intent.setType(“image/*”);//选择图片
//        intent.setType(“audio/*”); //选择音频
//        intent.setType(“video/*”); //选择视频 (mp4 3gp 是android支持的视频格式)
//        intent.setType(“video/*;image/*”);//同时选择视频和图片
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        startActivityForResult(intent ,1);
       /* new LFilePicker()
                .withActivity(MusicServiceActivity.this)
                .withRequestCode(REQUESTCODE_FROM_ACTIVITY)
                .withStartPath("/storage/0DFA-3807/Music")//指定初始显示路径
                .start();*/
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (resultCode == Activity.RESULT_OK) {
            Uri uri = data.getData();
            path = FileChooseUtil.getPath(MusicServiceActivity.this,uri);
            Log.i("path222222",path+"aabb");
           /* String[] dataStr = path.split("/");
            String fileTruePath = "/sdcard";
            for(int i=4;i<dataStr.length;i++){
                fileTruePath = fileTruePath+"/"+dataStr[i];
            }*/
            musicPath_tv.setText("当前歌曲路径为:"+path);
            Intent intent = new Intent(MusicServiceActivity.this, MusicService.class);
            intent.putExtra("Filepath",path);
            bindService(intent,connection,MusicServiceActivity.this.BIND_AUTO_CREATE);
        }

        /*super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == Activity.RESULT_OK) {
            Uri uri = data.getData();
            if ("file".equalsIgnoreCase(uri.getScheme())){//使用第三方应用打开
                path = uri.getPath();
                musicPath_tv.setText(path);
                Log.d("queryfilepath", "返回结果: " + path);
                return;
            }
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) {//4.4以后
                path = FileChooseUtil.uriToFileApiQ(this,uri);
                Log.i("path222222",path+"aabb");
                musicPath_tv.setText("当前歌曲路径为:"+path);
                Intent intent = new Intent(MusicServiceActivity.this, MusicService.class);
                intent.putExtra("path",path);
                bindService(intent,connection,MusicServiceActivity.this.BIND_AUTO_CREATE);

            }
        }*/
        /*super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            if (requestCode == REQUESTCODE_FROM_ACTIVITY) {
                path = data.getStringExtra("path");
                musicPath_tv.setText("当前歌曲路径为:"+path);
                Intent intent = new Intent(MusicServiceActivity.this, MusicService.class);
                intent.putExtra("path",path);
                bindService(intent,connection,MusicServiceActivity.this.BIND_AUTO_CREATE);
            }
        }*/
    }
}

FileChooseUtil.java

package com.jingyi.newwork;

import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;

/**
 * @ClassName FileChooseUtil
 * @Description TODO
 * @Author Yu
 * @Date 2022/6/10 12:57
 * @Version 1.0
 **/
public class FileChooseUtil {
    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     * Framework Documents, as well as the _data field for the MediaStore and
     * other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @author paulburke
     */
    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
                /*String id = DocumentsContract.getDocumentId(uri);
                //尝试第一次获取路径
                if (id.startsWith("raw:")) {
                    final String path = id.replaceFirst("raw:", "");
                    return path;
                }
                Uri contentUri = uri;
                // 尝试第二次获取
                String[] contentUriPrefixesToTry = new String[]{
                        "content://downloads/public_downloads",
                        "content://downloads/my_downloads"

                };

                for (String contentUriPrefix : contentUriPrefixesToTry) {
                    contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
                    try {
                        String path = getDataColumn(context, contentUri, null, null);
                        if (path != null) {

                            return path;
                        }
                    } catch (Exception e) {

                    }
                }*/
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {

            // Return the remote address
            if (isGooglePhotosUri(uri))
                return uri.getLastPathSegment();

            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }
    /**
     **
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    //获取uri中的文件名称
    public static String getFileName( Context context,Uri uri)
    {
        String mimeType=context.getContentResolver().getType(uri);

        if(mimeType!=null)

        {
            try {
                Cursor returnCursor=context.getContentResolver().query(uri,null,null,null,null);

                if (returnCursor != null && returnCursor.moveToFirst()) {
                    final int index = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME);

                    return returnCursor.getString(index);
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }


        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    public static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }
}

AndroidManifest.xml

package com.jingyi.newwork;

import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;

/**
 * @ClassName FileChooseUtil
 * @Description TODO
 * @Author Yu
 * @Date 2022/6/10 12:57
 * @Version 1.0
 **/
public class FileChooseUtil {
    /**
     * Get a file path from a Uri. This will get the the path for Storage Access
     * Framework Documents, as well as the _data field for the MediaStore and
     * other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @author paulburke
     */
    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
                /*String id = DocumentsContract.getDocumentId(uri);
                //尝试第一次获取路径
                if (id.startsWith("raw:")) {
                    final String path = id.replaceFirst("raw:", "");
                    return path;
                }
                Uri contentUri = uri;
                // 尝试第二次获取
                String[] contentUriPrefixesToTry = new String[]{
                        "content://downloads/public_downloads",
                        "content://downloads/my_downloads"

                };

                for (String contentUriPrefix : contentUriPrefixesToTry) {
                    contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.valueOf(id));
                    try {
                        String path = getDataColumn(context, contentUri, null, null);
                        if (path != null) {

                            return path;
                        }
                    } catch (Exception e) {

                    }
                }*/
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {

            // Return the remote address
            if (isGooglePhotosUri(uri))
                return uri.getLastPathSegment();

            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }
    /**
     **
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    //获取uri中的文件名称
    public static String getFileName( Context context,Uri uri)
    {
        String mimeType=context.getContentResolver().getType(uri);

        if(mimeType!=null)

        {
            try {
                Cursor returnCursor=context.getContentResolver().query(uri,null,null,null,null);

                if (returnCursor != null && returnCursor.moveToFirst()) {
                    final int index = returnCursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME);

                    return returnCursor.getString(index);
                }
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }


        }
        return null;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    public static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     */
    public static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     */
    public static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    public static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值