Android四大组件之服务

为什么要使用服务呢?

从上面的文字说,我们知道这个服务是用于执行长期后台运行的操作。有些时候,我们没有界面,但是程序仍然需要工作。比如说,我们播放音乐,在后台播放音乐。比如说,我们下载任务,在后台下载文件。这些都是没有界面 的后台运行程序,这些都是用服务做的。

第二个原因是什么呢?先给大家讲几个概念:0
1、前台进程:可以理解为是最顶部的,直接跟用户交互的。比如说我们操作的Activity界面.
2、可见进程:可以见的,但是不操作的,比如说我们在一个Activity的顶部弹出一个Dialog,这个Dialog就是前台进程,但是这个Activity则是可见进程。(操作的只是Dialog,而不是Activity但可见)
3、服务进程:服务可以理解为是忙碌的后台进程,虽然是在后台,但是它很忙碌。
4、后台进程:后台进程就是退隐到后台,不做事的进程。(比如按home键程序在后台但没有被干掉/)
5、空进程:空进程是不做事的,没有任何东西在上面跑着,仅作缓存作用。(比如按返回键退出此时就是空进程,销毁掉的进程就是空进程)
假设,内存不够用了,会先杀谁呢?
首先杀的是空进程,要是还不够就杀后台进程,要是还不够,那么就杀服务,但是服务被杀死以后,等内存够用了,服务又会跑起来了。

所以:如果我们需要长期后台操作的任务,使用Service就对了!其实Framework里多数是服务。如果我们进行音乐播放,即使退到了后台,也可以播放,我们使用服务完成吧!如果我们下载东西,退到后台也能下载,那么我们就使用服务吧!如果我们在不停地记录日志,那我们就用服务吧!

服务是Android中实现程序后台运行的方案,他非常适合去执行那些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

每一个服务都需要在配置文件AndroidManifest.xml文件里进行生命,怎么生命呢?
使用标签,其实跟前面的activity,广播接收者receiver一样生命。
通过Context.startService()来开启服务,通过Context.stop()来停止服务。当然啦,还有一种启动形式就是通过Context.bindService()的方法。

如果面试问到:服务用于执行耗时操作,这是对的吗?
如时服务直接执行耗时操作,也会出现anr。首先ANR的意思是android no response,也就是无响应或者理解为操作超时。
如果在服务中直接做耗时操作,也是会出现ANR异常的。服务可以长期在后台运行,所以你可以这么做:如果要做耗时操作,比如说网络的访问,数据库的读写之类的,可以开线程去做。
首先,创建一个类,继承Service,就像我们之前写Activity要继承自Activity,而广播则继承自BroadcastReceiver。

package com.example.servicetest;import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;import androidx.annotation.Nullablepublic class MyService extends Service {//onCreate()方法再服务创建的时候调用@Overridepublic void onCreate() {super.onCreate();Log.d("MyService", "onCreate executed");}//onStartCommand()方法在每次服务启动的时候调用@Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.d("MyService", "onStartCommand executed");return super.onStartCommand(intent, flags, startId);}//onDestroy()方法在服务销毁的时候调用@Overridepublic void onDestroy() {super.onDestroy();Log.d("MyService", "onDestroy executed");}
}

接着,我们写一个Activity去控制服务:

  @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button startService=(Button) findViewById(R.id.start_service);Button stopService=(Button) findViewById(R.id.stop_service);startService.setOnClickListener(this);stopService.setOnClickListener(this);@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.start_service:Intent startIntent=new Intent(this,MyService.class);startService(startIntent);//启动服务break;case R.id.stop_service:Intent stopIntent=new Intent(this,MyService.class);stopService(stopIntent);//停止服务break;

onCreate()方法是在服务第一次创建的时候调用的,而onStartCommand()方法则在每次启动服务的时候都会调用,由于是我们第一次点击Start Service按钮,服务此时还未创建过,所以两个方法都会执行,之后如果你再连续多点击几次Start Service按钮,你会发现只有onStartCommand()方法可以得到执行。
前面的开启服务方式,有一个弊端。就是没法进行通讯。所以我们接直来呢会学习另外一种启动服务的方式–通过绑定服务的形式来启动服务。

绑定服务,对应的停止服务则是解绑服务了!

活动和服务进行通信(绑定服务)

例子:通过创建一个专门的Binder对象来对下载功能进行管理

public class MyService extends Service {private DownloadBinder mBinder=new DownloadBinder();class DownloadBinder extends Binder{public void startDownload(){Log.d("MyService", "startDownload: executed");}public int getProgress(){Log.d("MyService", "getProgress: executed");return 0;}}public MyService() {}@Nullable@Overridepublic IBinder onBind(Intent intent) {return mBinder;}

当一个活动和服务绑定了之后,就可以调用该服务里的Binder提供的方法了。

public class MainActivity extends AppCompatActivity implements View.OnClickListener {private MyService.DownloadBinder downloadBinder;private ServiceConnection connection=new ServiceConnection() {//分别是绑定成功后和不成功后调用的方法@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//多态  IBinder a=new MyService.DownloadBinder();//多态的类型判断service对象是MyService.DownloadBinder类型的实例if(service instanceof MyService.DownloadBinder) {//这里的downloadBinder就是MyService返回的mBinder//向下转型得到DownloadBinder的实例,可以调用DownloadBinder的任何方法downloadBinder = (MyService.DownloadBinder) service;downloadBinder.startDownload();downloadBinder.getProgress();}}@Overridepublic void onServiceDisconnected(ComponentName name) {}
};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button bindService=(Button) findViewById(R.id.bind_service);Button unbindService=(Button) findViewById(R.id.unbind_service);bindService.setOnClickListener(this);unbindService.setOnClickListener(this);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.bind_service://点击后回调onBind()方法Intent bindIntent=new Intent(this,MyService.class);//BIND_AUTO_CREATE表示绑定服务的时候就会没有创建服务就会去创建服务,如果已经创建就不会再创建//绑定后就返回Binder给onServiceConnectedbindService(bindIntent,connection,BIND_AUTO_CREATE);//绑定服务break;case R.id.unbind_service:unbindService(connection);//解绑服务break;}

首先创建一个ServiceConnection的匿名类,在里面重写了onServiceConnected和onServiceDisconnected方法,这两个方法分别会在绑定成功后以及活动与服务的连接断开的时候调用。我们可以根据活动的具体场景来调用DownloadBinder中的任何public方法,从而实现指挥服务干什么服务就去干什么的功能。做了个简单的测试,在onServiceConnected()方法中调用了用DownloadBinder中startDownload和getProgress方法。
绑定服务在Bind Service按钮的点击事件里完成,构建一个Intent对象,然后调用bindService()方法将MainActivity和MyService进行绑定。bindService()方法接收3个参数,第一个参数是刚构建的Intent对象,第二个参数是前面创建出的ServiceConnection的实例,第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示活动和服务进行绑定后自动创建服务。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。
当需要解除活动和服务之间的绑定需要调用一下unbindService()方法。
在这里插入图片描述
首先是MyService的onCreate()方法得到了执行,然后startDownload()和getProgress()方法都得到了执行,说明我们确实已经在活动里成功调用了服务里提供的方法。
任何一个服务在整个应用程序的范围内都是通用的,即MyService不仅可以和MainActivity绑定,还可以任何一个其他活动进行绑定,而且绑定完成后他们可以获取相同的DownloadBinder实例。
这样子我们就可以控制服务了,假设说我们有一个服务在后台跑着,用它来播放音乐的,因为我们音乐可以后台播放呀,对吧!这个时间 ,我们需要控制音乐的播放和暂停了,就可以通过这种形式去控制音乐了
总结一下绑定服务的特点:
1、绑定服务在系统设置里是没有显进服务正在跑着的;
2、如果onBind方法返回的是null,那么onServiceConnected方法不会被调用;
3、绑定服务的生命周期跟Activity是不求同时生,但求同时死,Activity没了,服务也要解绑;
4、服务在解除绑定以后会停止运行,执行unBind方法—>onDestroy方法;
5、绑定服务开启的服务,只可以解绑一次,多次解绑会抛异常;
6、绑定的connection要跟解绑的connection要对应着,否则没法解绑。

绑定服务和开启服务的区别

绑定服务:可以间接调用服务里面的方法;如果绑定的Activity被销毁了,服务也会跟着销毁。当Activity销毁时要释放服务资源即unbind否则会导致泄露问题,也就是说service的与activity不求同生但求同死。
开启服务:不可以调用服务里面的方法;如果开启服务的Activity销毁,服务还可以长期的在后台运行。
既要保证服务长期在后台运行,又想去调用服务里面的方法:混合使用服务

混合服务

start->bind->unbind查看效果
unbindService之后service并没有destroy,所以说以start开启服务之后,只有stop才可以正常的销毁服务。
start->bind->stop bind之后未unbind,stop是不会起作用的,也就是说当bind之后必须解绑才可以正常的释放资源。

推荐的混合开发服务模式:

  1. 开启服务->为了确保服务可以长期于后台运行
  2. 绑定服务->为了可以进行通讯
  3. 调用服务内部的方法,该干嘛就干嘛,比如说,我们控制音乐的播放/暂停 /停止/快进
  4. 退出Activity,要记得解绑服务->释放资源
  5. 如果不使用服务了,要让服务停止,那么就调用stopService

服务生命周期

在这里插入图片描述
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand方法。如果这个服务之前未创建过,onCreate()方法会先于onStartCommand方法执行 。服务启动后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。虽然每次调用一次startService()方法,onStartCommand方法就会执行一次,但实际上每个服务都只会存在一个实例。不管你调用多少次startService(),只需调用一次stopService()或stopSelf()方法,服务就会停止下来。
还可以调用Context的bindService()获取一个服务的持久连接,这时就会回调服务的onBind()方法。类似的如果这个服务之前未创建过,onCreate()方法会先于onBind方法执行 。之后调用方就可以获取到onBind()方法返回的IBinder对象的实例,这样就能自由地和服务进行通信(比如绑定成功后,活动调用服务的相关方法)
当调用了startService()方法后,又去调用stopService()方法,这是服务中的onDestory()方法就会执行,表示服务已经销毁。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestory()方法也会执行。但是注意当我调用startService()又调用bindService(),这个时候需要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。

前台服务

发送通知

  @Overridepublic void onCreate() {super.onCreate();Log.d("MyService", "onCreate executed");String ID="com.example.servicetest";String NAME="Channel One";Intent intent=new Intent(MyService.this,MainActivity.class);PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);NotificationCompat.Builder notification;NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){NotificationChannel channel=new NotificationChannel(ID,NAME,manager.IMPORTANCE_HIGH);channel.enableLights(true);channel.setShowBadge(true);channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);manager.createNotificationChannel(channel);notification=new NotificationCompat.Builder(MyService.this).setChannelId(ID);}else {notification=new NotificationCompat.Builder(MyService.this);}notification.setContentTitle("标题").setContentText("内容").setWhen(System.currentTimeMillis()).setSmallIcon(R.mipmap.ic_launcher).setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)).setContentIntent(pendingIntent).build();Notification notification1=notification.build();startForeground(1,notification1);}

使用IntentService

如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR的情况,这个时候需要用Android多线程技术。我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。
Android提供了一个IntentService类,首先提供一个无参的构造函数,并且需要在其内部调用父类的有参构造。然后要在子类中去实现onHandleIntent这个抽象方法,在这个方法中可以去处理一些具体的逻辑,而且不用担心ANR问题,因为这个方法是在子线程中运行的。这个服务在运行结束之后会自动停止的

package com.example.servicetest;import android.app.IntentService;
import android.content.Intent;
import android.util.Log;import androidx.annotation.Nullable;public class MyIntentService extends IntentService {public MyIntentService() {super("MyIntentService");//调用父类的有参构造函数}@Overridepublic void onDestroy() {super.onDestroy();Log.d("MyIntentService", "onDestroy: executed");}@Overrideprotected void onHandleIntent(@Nullable Intent intent) {//打印当前线程的idLog.d("MyIntentService", "Thread id is: "+Thread.currentThread().getId());}}
   @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Button startIntentService=(Button) findViewById(R.id.start_intent_service);startIntentService.setOnClickListener(this);}
    @Overridepublic void onClick(View v) {switch (v.getId()){case R.id.start_intent_service://打印主线程的idLog.d("MainActivity", "Thread id is"+Thread.currentThread().getId());Intent intentService=new Intent(this,MyIntentService.class);startService(intentService);break;default:break;}}

在这里插入图片描述

隐式意图服务(银行例子)

绑定服务

package com.example.bankservicedemo;import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;import com.example.bankservicedemo.interfaces.INormalUserAction;public class NormalUserActivity extends AppCompatActivity {private static final String TAG = "NormalUserActivity";private INormalUserAction mNormalUserAction;private NormalUserConnection mNormalUserConnection;private boolean mIsBind;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_normal_user);doBindService();}//绑定服务private void doBindService() {Log.d(TAG, "doBindService: ");//由于service那边是返回action,所以需要用隐式意图来进行绑定Intent intent = new Intent();intent.setAction("com.example.ACTION_NORMAL_USER");intent.addCategory(Intent.CATEGORY_DEFAULT);//Android5.0后,要在服务使用隐式意图,需要加上目标绑定包名packName,应用的包名intent.setPackage("com.example.bankservicedemo");mNormalUserConnection = new NormalUserConnection();mIsBind = bindService(intent, mNormalUserConnection, BIND_AUTO_CREATE);}private class NormalUserConnection implements ServiceConnection{//服务连接成功时@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.d(TAG, "onServiceConnected: "+name);mNormalUserAction = (INormalUserAction) service;}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.d(TAG, "onServiceDisconnected: "+name);}}//下面三个是按钮点击事件public void saveMoneyClick(View view){Log.d(TAG, "saveMoneyClick: ");mNormalUserAction.saveMoney(10000);}public void getMoneyClick(View view){Log.d(TAG, "getMoneyClick: ");mNormalUserAction.getMoney();}public void loadMoneyClick(View view){Log.d(TAG, "loadMoneyClick: ");mNormalUserAction.loadMoney();}//销毁时记得解开服务@Overrideprotected void onDestroy() {super.onDestroy();if(mIsBind&&mNormalUserConnection!=null){unbindService(mNormalUserConnection);Log.d(TAG, "unbind service.. ");mNormalUserConnection=null;mIsBind=false;}}
}

服务创建

package com.example.bankservicedemo;import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.text.TextUtils;import androidx.annotation.Nullable;import com.example.bankservicedemo.impl.BankBossActionImpl;
import com.example.bankservicedemo.impl.BankWorkerActionImpl;
import com.example.bankservicedemo.impl.NormalUserActionImpl;public class BankService extends Service {@Nullable@Overridepublic IBinder onBind(Intent intent) {String action=intent.getAction();if(!TextUtils.isEmpty(action)){if("com.example.ACTION_NORMAL_USER".equals(action)){return new NormalUserActionImpl();}else if("com.example.ACTION_BANK_WORKER".equals(action)){return new BankWorkerActionImpl();}else if("com.example.ACTION_BANK_BOSS".equals(action)){return new BankBossActionImpl();}}return null;}
}

隐式服务声明

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.bankservicedemo"><applicationandroid: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.ServiceTest"><activity android:name=".bankWorkerActivity"></activity><activity android:name=".bank_bossActivity" /><activity android:name=".NormalUserActivity" /><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><!-- 服务隐式意图 --><!--  android:exported="true"可以在外部的第三方应用拉起服务--><serviceandroid:name=".BankService"android:exported="true"><intent-filter><action android:name="com.example.ACTION_NORMAL_USER" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><intent-filter><action android:name="com.example.ACTION_BANK_WORKER" /><category android:name="android.intent.category.DEFAULT" /></intent-filter><intent-filter><action android:name="com.example.ACTION_BANK_BOSS" /><category android:name="android.intent.category.DEFAULT" /></intent-filter></service></application></manifest>

Android跨进程通信AIDL

  • AIDL概述
    AIDL意思即Android Interface Definition Language(安卓接口定义语言),用于定义服务器与客户端进行通信的一种描述语言,本质是AIDL其实是android端为我们定义的一个模板文件.aidl,最终还是会编译为.java文件。

在Android中,默认每个应用(application)执行在它自己的进程中,无法直接调用到其他应用的资源,这也符合沙箱(SandBox)的理念。所谓沙箱原理,一般来说用在移动电话业务中,简单地说旨在部分地或全部地隔离应用程序。

Android沙箱技术: Android“沙箱”的本质是为了实现不同应用程序和进程之间的互相隔离,即在默认情况 下,应用程序没有权限访问系统资源或其它应用程序的资源。 每个APP和系统进程都被分配唯一并且固定的User Id(用户身份标识),这个uid与内核层进程的uid对应。 每个APP在各自独立的Dalvik虚拟机中运行,拥有独立的地址空间和资源。 运行于Dalvik虚拟机中的进程必须依托内核层Linux进程而存在,因此Android使用Dalvik虚拟机和Linux的文件访问控制来实现沙箱机制,任何应用程序如果想要访问系统资源或者其它应用程序的资源必须在自己的manifest文件中进行声明权限或者共享uid。

  • AIDL的引入
    android开发中一项任务可能需要多个进程相互协作,相互委托,比如支付服务,某app需要进行支付,那么他需要调起第三方支付,进程之间需要通信,当支付完毕,第三方返回支付数据给app进程之间也需要通信,所以说如果一个应用只是单单的一个UI主进程而不涉及多个进程间的通信,那么这个app是不完美的!通过AIDL就可以满足进程间通信的需求,本质上也是通过Binder对象来进行传递数据。

通常,暴露接口方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。

AIDL模拟支付宝支付

1.明确需求
某app需要进行SQ币充值,需要调用第三方支付服务,然后第三方支付服务拉起一个新的Activity提供用户账单信息
2.项目结构
服务端(Server):
在这里插入图片描述
客户端(Client):
在这里插入图片描述
3. 代码实现

  • 编写支付服务和支付界面(Server端)

客户端(Client)通过bindService(intent, mAlipayConnection, BIND_AUTO_CREATE);绑定服务端的服务后执行服务端的onBind() 会返回return mThirdPartPlay;然后执行 public void onServiceConnected(ComponentName name, IBinder service)方法中的IBinder对象就是Server通过调用onBind方法返回的一个间接继承Binder类的对象。私有内部类ThirdPartPayImpl继承ThirdPartPayAction.Stub.Stub类实现了AIDL接口并且继承了Binder类(AIDL通信的本质)。PayAction支付动作类,因为app绑定第三方支付后,当调用requestPay时,service会拉起一个支付的PayActivity,这个Activity也需要与该支付服务做绑定因为支付操作都是在该界面进行的,与服务通信的IBinder对象是return new PayAction()所给。
整个程序中PayActivity绑定了两次,第一次是客户端(return mThirdPartPlay),第二次是拉起的第三方支付(return new PayAction())。

package com.example.aliplay;import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;public class PayService extends Service {private static final String TAG = "PayService";private ThirdPartPlayImpl mThirdPartPlay;public PayService() {}@Overridepublic IBinder onBind(Intent intent) {String action = intent.getAction();Log.d(TAG, "onBind: ->action"+action);if(action!=null&&"com.example.aliplay.THIRD_PART_PAY".equals(action)){//说明这个是第三方要求我们自付宝进行支付mThirdPartPlay = new ThirdPartPlayImpl();return mThirdPartPlay;}return new PayAction();}public class PayAction extends Binder{public void Play(float payMoney){Log.d(TAG, "Play money is-->"+payMoney);//实际的支付是比较复杂的,比如加密,比如向服务器发起请求,等待服务器的结果//支付方法if(mThirdPartPlay!=null){mThirdPartPlay.paySuccess();}}public  void onUserCancel(){//用户点击界面上的取消/退出if(mThirdPartPlay!=null){mThirdPartPlay.payFailed(1,"user cancel pay");}}}private class ThirdPartPlayImpl extends ThirdPartPayAction.Stub{private ThirdPartPayResult mCallback;//ThirdPartPayResult 参数作为回调接口@Overridepublic void requestPay(String orderInfo, float payMoney, ThirdPartPayResult callback) throws RemoteException {Log.d(TAG, "oderInfo->"+orderInfo+"pay Money->"+payMoney);this.mCallback=callback;//第三方应用发起请求,打开一个支付界面Intent intent = new Intent();intent.setClass(PayService.this,PayActivity.class);intent.putExtra(Constants.KEY_BILL_INFO,orderInfo);intent.putExtra(Constants.KEY_PAY_MONEY,payMoney);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//表示Service和Activity跑在不同的任务上startActivity(intent);}public void paySuccess(){if(mCallback!=null) {try {mCallback.onPaySuccess();} catch (RemoteException e) {e.printStackTrace();}}}public void payFailed(int errorCode,String errorMsg) {if (mCallback != null) {try {mCallback.onPayFailed(errorCode, errorMsg);} catch (RemoteException e) {e.printStackTrace();}}}}
}

AIDL文件
requestPay方法中ThirdPartPayResult参数作为回调接口供客户端使用。说到这提下接口,接口可以隐藏内部细节,对调用者来说使用方便,对开发者来说接口使代码的健壮性增强,对调用者所使用的权限做了一定的限定。接口提高了开发效率,各端人员各司其职(面向接口编程)。

// ThirdPartPayAction.aidl
package com.example.aliplay;
import com.example.aliplay.ThirdPartPayResult;
// Declare any non-default types here with import statementsinterface ThirdPartPayAction {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*///发起支付void requestPay(String orderInfo,float payMoney,ThirdPartPayResult callback);
}
// ThirdPartPayResult.aidl
package com.example.aliplay;// Declare any non-default types here with import statementsinterface ThirdPartPayResult {/*** Demonstrates some basic types that you can use as parameters* and return values in AIDL.*/void onPaySuccess();void onPayFailed(in int errorcode,in String msg);
}

PayActivity支付交互界面

package com.example.aliplay;import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;public class PayActivity extends AppCompatActivity {private boolean mMIsBind;private EditText mMPasswordBox;private PayService.PayAction mPlayAction;private static final String TAG="PayActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_pay);Log.d(TAG, "onCreate: 重新启动");//因为我们的Activity也要跟服务进行通讯,告诉服务支付结果,所以的话我们也要绑定服务doBindService();initView();}@Overridepublic void onBackPressed() {super.onBackPressed();if (mPlayAction!=null){mPlayAction.onUserCancel();}}private void initView() {Intent intent = getIntent();String orderInfo = intent.getStringExtra(Constants.KEY_BILL_INFO);float payMoney = intent.getFloatExtra(Constants.KEY_PAY_MONEY, 0f);TextView orderInfoTv = this.findViewById(R.id.order_info_tv);orderInfoTv.setText("支付信息:"+orderInfo);TextView payMoneyTv = this.findViewById(R.id.pay_money);payMoneyTv.setText("支付金额:"+payMoney);mMPasswordBox = this.findViewById(R.id.pay_password_input);Button commitButton = this.findViewById(R.id.pay_commit);commitButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//提交点击了String trim=mMPasswordBox.getText().toString().trim();if("123456".equals(trim)&&mPlayAction!=null){mPlayAction.Play(payMoney);Toast.makeText(PayActivity.this,"支付成功",Toast.LENGTH_SHORT).show();finish();Log.d(TAG, "onClick: pay finish");}else {Toast.makeText(PayActivity.this,"密码错误",Toast.LENGTH_SHORT).show();}}});}private void doBindService() {Intent intent = new Intent(this, PayService.class);mMIsBind = bindService(intent,mConnection , BIND_AUTO_CREATE);}private ServiceConnection mConnection=new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {Log.d(TAG, "onServiceConnected: '"+iBinder);mPlayAction = (PayService.PayAction) iBinder;}@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.d(TAG, "onServiceDisconnected: "+componentName);mPlayAction=null;}};@Overrideprotected void onDestroy() {super.onDestroy();if(mMIsBind&&mConnection!=null){unbindService(mConnection);Log.d(TAG, "onDestroy: unbindService");mMIsBind=false;mConnection=null;}}
}

编写某App,导入AIDL接口文件
通过ThirdPartPay.Stub.asInterface得到第三方服务对象,继承ThirdPartPayResult.Stub重写回调接口方法,然后调用服务端的requestPay方法的时候将相应对象传入即可。

package com.example.sobclient;import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import com.example.aliplay.ThirdPartPayAction;
import com.example.aliplay.ThirdPartPayResult;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private Button mBuySobBtn;private TextView mSobCountTv;private AlipayConnection mAlipayConnection;private boolean mIsBind;private ThirdPartPayAction mThirdPartPayAction;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d(TAG, "onCreate: 重新启动");bindAlipayService();//找到控件initView();setListener();}private void bindAlipayService() {Intent intent = new Intent();intent.setAction("com.example.aliplay.THIRD_PART_PAY");intent.addCategory(Intent.CATEGORY_DEFAULT);intent.setPackage("com.example.aliplay");mAlipayConnection = new AlipayConnection();mIsBind = bindService(intent, mAlipayConnection, BIND_AUTO_CREATE);}private class AlipayConnection implements ServiceConnection{@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.d(TAG, "onServiceConnected: "+mIsBind);mThirdPartPayAction = ThirdPartPayAction.Stub.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.d(TAG, "onServiceDisconnected: "+name);}}private void setListener() {mBuySobBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//进行充值try {if(mThirdPartPayAction!=null) {mThirdPartPayAction.requestPay("充值100SO币", 100.00f, new PayCallback());}} catch (RemoteException e) {e.printStackTrace();}}});}//回调接口private class PayCallback extends ThirdPartPayResult.Stub{@Overridepublic void onPaySuccess() {//支付成功,那么我们就去修改UI上的内容//实际上失去修改数据库,其实,支付宝是通过回调URL地址,通知我们的服务器
//点击支付回调方法/*跨进程通信的时候,走的是子线程,请不要在子线程操作ui,比如更新text,toast等等*/runOnUiThread(new Runnable() {@Overridepublic void run() {mSobCountTv.setText("100");Toast.makeText(MainActivity.this,"充值成功!",Toast.LENGTH_SHORT).show();}});}@Overridepublic void onPayFailed(int errorcode, String msg)  {Log.d(TAG, "error code is: "+errorcode+"error Msg-"+msg);Toast.makeText(MainActivity.this,"充值失败!",Toast.LENGTH_SHORT).show();}}private void initView() {mSobCountTv=findViewById(R.id.sob_count_tv);mBuySobBtn=findViewById(R.id.buy_sob_btn);}@Overrideprotected void onDestroy() {super.onDestroy();if(mIsBind&&mAlipayConnection!=null){Log.d(TAG, "unbind service... ");unbindService(mAlipayConnection);mAlipayConnection=null;mIsBind=false;}}
}

服务例子音乐播放器

Activity负责UI界面,Service负责逻辑,之间通过接口进行通讯

两个接口

package com.example.musicplayer.Interfaces;/*** 控制音乐的接口*/
public interface IPlayerControl {//播放状态//播放int PLAY_STATE_PLAY=1;int PLAY_STATE_PAUSE=2;int PLAY_STATE_STOP=3;/*** 把UI的控制接口设置给逻辑层(让逻辑层通知更新UI界面)**/void setViewController(IPlayerViewControl viewController);/*** 取消接口通知的注册(防止设置过来内存泄露)*/void unRegisterViewController();/*** 播放音乐*/void playOrPause();/*** 停止播放*/void stopPlay();/*** 设置播放进度*/void seekTo(int seek);
}
package com.example.musicplayer.Interfaces;/*** 通知UI更新的接口*/
public interface IPlayerViewControl {/*** 播放状态改变的通知* @param state*/void onPlayerStateChange(int state);/*** 播放进度的改变* @param seek*/void onSeekChange(int seek);}

主界面也就是UI层

package com.example.musicplayer;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import android.Manifest;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Toast;import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.services.PlayService;import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_PAUSE;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_PLAY;
import static com.example.musicplayer.Interfaces.IPlayerControl.PLAY_STATE_STOP;public class MainActivity extends AppCompatActivity {private SeekBar mSeekBar;private Button mPlayOrPause;private Button mClose;private PlayerConnection mPlayerConnection;private IPlayerControl mIPlayerControl;private boolean isUserTouchProgressBar=false;private final static String TAG="MainActivity" ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//动态申请权限if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);}else {}initView();//设置相关的事件initEvent();//启动播放的服务initService();//绑定服务initBindService();Log.d(TAG, "onCreate: "+ Environment.getExternalStorageDirectory());}/*** 开启播放的服务*/private void initService() {Log.d(TAG, "initService: ");startService(new Intent(this,PlayService.class));}/*** 绑定服务*/private void initBindService() {Log.d(TAG, "initBindService");Intent intent = new Intent(this, PlayService.class);mPlayerConnection = new PlayerConnection();bindService(intent,mPlayerConnection,BIND_AUTO_CREATE);}private class PlayerConnection implements ServiceConnection{@Overridepublic void onServiceConnected(ComponentName componentName, IBinder iBinder) {Log.d(TAG, "onServiceConnected: "+iBinder);mIPlayerControl = (IPlayerControl) iBinder;mIPlayerControl.setViewController(mIPlayerViewControl);}@Overridepublic void onServiceDisconnected(ComponentName componentName) {Log.d(TAG, "onServiceDisconnected");mIPlayerControl=null;}}private void initEvent() {mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {@Overridepublic void onProgressChanged(SeekBar seekBar, int i, boolean b) {//进度条发生改变}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//我的手已经触摸上去拖动isUserTouchProgressBar=true;}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {int touchProgress=seekBar.getProgress();Log.d(TAG, "onStopTrackingTouch: "+touchProgress);//停止拖动if (mIPlayerControl != null) {mIPlayerControl.seekTo(seekBar.getProgress());}isUserTouchProgressBar=false;}});mPlayOrPause.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Log.d(TAG, "onClick: ");//播放或暂停if(mIPlayerControl!=null) {//如果绑定不成功为null就会崩,需要判空一下。在一些接口回调的时候就需要对其判空mIPlayerControl.playOrPause();}}});mClose.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//关闭按钮被点击了Log.d(TAG, "unonClick: ");if(mIPlayerControl!=null){mIPlayerControl.stopPlay();}}});}private void initView() {mSeekBar = (SeekBar) findViewById(R.id.seek_bar);mPlayOrPause = (Button) findViewById(R.id.play_or_pause_btn);mClose = (Button) findViewById(R.id.close_bt);}@Overrideprotected void onDestroy() {super.onDestroy();if(mPlayerConnection!=null){Log.d(TAG, "unbindService  onDestroy: ");unbindService(mPlayerConnection);}}//实现通知UI更新的接口private IPlayerViewControl mIPlayerViewControl=new IPlayerViewControl() {@Overridepublic void onPlayerStateChange(int state) {switch (state){case PLAY_STATE_PLAY://播放中的话,我们要修改按钮显示成暂停mPlayOrPause.setText("暂停");break;case PLAY_STATE_PAUSE:case PLAY_STATE_STOP:mPlayOrPause.setText("播放");break;}}@Overridepublic void onSeekChange(int seek) {//从上面的Log我们可以发现,这个不是主线程,所以不可以用于更新UI//为什么更新进度不会崩溃呢?//因为在android里面,有两个控件是可以用子线程去更新//一个是ProgressBar,另外一个是surfaceViewrunOnUiThread(new Runnable() {@Overridepublic void run() {if(!isUserTouchProgressBar){mSeekBar.setProgress(seek);}}});//改变播放进度,有一个条件,什么->当用户的手触摸到进度条的时候,就不更新(不然会抖动)}};/*** 动态申请权限* @param context    上下文* @param permission 要申请的一个权限,列如写的权限:Manifest.permission.WRITE_EXTERNAL_STORAGE* @return  是否有当前权限*/private boolean RequestPermissions(Context context, String permission) {if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {Log.i("requestMyPermissions",": 【 " + permission + " 】没有授权,申请权限");ActivityCompat.requestPermissions((Activity) context, new String[]{permission}, 100);return false;} else {Log.i("requestMyPermissions",": 【 " + permission + " 】有权限");return true;}}@Overridepublic void onRequestPermissionsResult(int requestCode, @NonNull  String[] permissions, @NonNull  int[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch (requestCode){case 1:if(grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){mIPlayerControl.playOrPause();}else {Toast.makeText(this,"拒绝权限将无法使用程序",Toast.LENGTH_SHORT).show();finish();}break;default:}}
}

逻辑层

package com.example.musicplayer.presenter;import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.util.Log;import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.services.PlayService;import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingDeque;public class PlayerPresenter extends Binder implements IPlayerControl {private static final String TAG="PlayerPresenter";//控制UIprivate IPlayerViewControl mViewControl;private int mCurrentState=PLAY_STATE_STOP;private MediaPlayer mMediaPlayer;private Timer mTimer;private SeekTimeTask mTimeTask;@Overridepublic void setViewController(IPlayerViewControl viewController) {this.mViewControl=viewController;}@Overridepublic void unRegisterViewController() {mViewControl=null;}@Overridepublic void playOrPause() {Log.d(TAG, "playOrPause: ..");if(mCurrentState==PLAY_STATE_STOP){//创建播放器initPlayer();//设置数据源try {mMediaPlayer.setDataSource("/storage/emulated/0/Music/my_look.mp3");mMediaPlayer.prepare();mMediaPlayer.start();mCurrentState=PLAY_STATE_PLAY;startTimer();} catch (IOException e) {e.printStackTrace();}}else if(mCurrentState==PLAY_STATE_PLAY){//如果当前的状态是播放的,那么我们就暂停if (mMediaPlayer!=null) {mMediaPlayer.pause();mCurrentState=PLAY_STATE_PAUSE;stopTimer();}}else if(mCurrentState==PLAY_STATE_PAUSE){//如果当前的状态是暂停,那么我们就继续播放if(mMediaPlayer!=null){mMediaPlayer.start();mCurrentState=PLAY_STATE_PLAY;startTimer();}}//逻辑层通知UI更新界面if(mViewControl!=null){mViewControl.onPlayerStateChange(mCurrentState);}}/*** 初始化播放器*/private void initPlayer() {if(mMediaPlayer==null){mMediaPlayer = new MediaPlayer();}mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);}@Overridepublic void stopPlay() {Log.d(TAG, "stopPlay: ");if(mMediaPlayer!=null&&mMediaPlayer.isPlaying()){//停止的话再次开始会重头再来mMediaPlayer.reset();mCurrentState=PLAY_STATE_STOP;stopTimer();//更新播放状态if(mViewControl!=null){mViewControl.onPlayerStateChange(mCurrentState);}//释放资源mMediaPlayer.release();mMediaPlayer=null;}}@Overridepublic void seekTo(int seek) {Log.d(TAG, "seekTo: .."+seek);//0-100之间//需要做一个转换,得到的seek其实是一个百分比if(mMediaPlayer!=null){int tarSeek=(int) (seek*1.0f/100*mMediaPlayer.getDuration());//设置进度mMediaPlayer.seekTo(tarSeek);}}/*** 开启一个timeTask*/private void startTimer(){if(mTimer==null){mTimer = new Timer();}if(mTimeTask==null) {mTimeTask = new SeekTimeTask();}//每隔500ms更新进度条mTimer.schedule(mTimeTask,0,500);}private void stopTimer(){if(mTimeTask!=null){mTimeTask.cancel();mTimeTask=null;}if(mTimer!=null){mTimer.cancel();mTimer=null;}}private class SeekTimeTask extends TimerTask{@Overridepublic void run() {//获取当前的播放进度if(mMediaPlayer!=null&&mViewControl!=null){//currentPosition表示当前进度getDuration表示总的进度int currentPosition=mMediaPlayer.getCurrentPosition();//需要浮点数不然整除为0Log.d(TAG, "current play position... "+currentPosition*1.0f);//最后除出来是百分比所以需要乘100Log.d(TAG, "getDuration... "+mMediaPlayer.getDuration()*100);int curPosition= (int) (currentPosition*1.0f/mMediaPlayer.getDuration()*100);mViewControl.onSeekChange(curPosition);}}}}

服务

package com.example.musicplayer.services;import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;import androidx.annotation.Nullable;import com.example.musicplayer.Interfaces.IPlayerControl;
import com.example.musicplayer.Interfaces.IPlayerViewControl;
import com.example.musicplayer.presenter.PlayerPresenter;public class PlayService extends Service {private PlayerPresenter mPlayerPresenter;@Overridepublic void onCreate() {super.onCreate();//进行判空,防止重复创建if (mPlayerPresenter == null) {mPlayerPresenter = new PlayerPresenter();}}@Nullable@Overridepublic IBinder onBind(Intent intent) {return mPlayerPresenter;}@Overridepublic void onDestroy() {super.onDestroy();//释放资源(主要用于防止内存泄露)mPlayerPresenter=null;}/*这一部分逻辑层可以抽取出来建个包presenterprivate class InnerBinder extends Binder implements IPlayerControl{@Overridepublic void setViewController(IPlayerViewControl viewController) {}@Overridepublic void unRegisterViewController() {}@Overridepublic void playOrPause() {}@Overridepublic void stopPlay() {}@Overridepublic void seekTo(int seek) {}}
*/}

服务与线程的区别

service:服务是运行在主线程中的,所以不能用来执行耗时操作,必须在服务中新开线程,服务没有Activity界面,即使Activity被销毁,但只要进程还在,服务就会在后台继续执行,服务一般用来在后台执行操作,而线程一般写在服务中,这样activity就能通过服务来控制线程。服务有分为前台服务和后台服务,后台服务优先级比较低,当系统不足时会被系统回收,前台服务优先级比较高,会以通知的形式显示在界面上,不会被系统回收。
线程:线程可以用来执行耗时操作,它与服务最大的区别在于服务是运行在主线程中,线程一般不会直接在activity中创建,因为这不利于Activity对线程的控制,线程一般都在服务中创建。

如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity
没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的
Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/73637.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot内嵌的Tomcat:

SpringBoot内嵌Tomcat源码&#xff1a; 1、调用启动类SpringbootdemoApplication中的SpringApplication.run()方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplicat…

机器学习十大经典算法

机器学习算法是计算机科学和人工智能领域的关键组成部分&#xff0c;它们用于从数据中学习模式并作出预测或做出决策。本文将为大家介绍十大经典机器学习算法&#xff0c;其中包括了线性回归、逻辑回归、支持向量机、朴素贝叶斯、决策树等算法&#xff0c;每种算法都在特定的领…

基于深度学习的高精度80类动物目标检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度80类动物目标检测识别系统可用于日常生活中或野外来检测与定位80类动物目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的80类动物目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YO…

STM32入门学习之外部中断

1.STM32的IO口可以作为外部中断输入口。本文通过按键按下作为外部中断的输入&#xff0c;点亮LED灯。在STM32的19个外部中断中&#xff0c;0-15为外部IO口的中断输入口。STM32的引脚分别对应着0-15的外部中断线。比如&#xff0c;外部中断线0对应着GPIOA.0-GPIOG.0&#xff0c;…

WEB:xff_referer

前提知识 xxf referer 题目 直接在请求头里添加&#xff0c;然后重放后显示内容为 修改referer payload Referer:https://www.google.com 得到flag

AcWing244. 谜一样的牛(树状数组)

输入样例&#xff1a; 5 1 2 1 0输出样例&#xff1a; 2 4 5 3 1 解析&#xff1a; 从后往前遍历&#xff0c;每次需要在剩余的数中&#xff0c;找到第 h[ i ]1 大的数即为当前牛的身高。 每次二分&#xff0c;然后求前缀和。 #include<bits/stdc.h> using namespace …

回归预测 | MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循环单元多输入单输出回归预测

回归预测 | MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循环单元多输入单输出回归预测 目录 回归预测 | MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循环单元多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现SO-CNN-GRU蛇群算法优化卷积门控循…

消息队列——rabbitmq的不同工作模式

目录 Work queues 工作队列模式 Pub/Sub 订阅模式 Routing路由模式 Topics通配符模式 工作模式总结 Work queues 工作队列模式 C1和C2属于竞争关系&#xff0c;一个消息只有一个消费者可以取到。 代码部分只需要用两个消费者进程监听同一个队里即可。 两个消费者呈现竞争关…

【1.2】Java微服务:SpringCloud概论

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a;Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 微服务 ✨特色专栏&#xff1a; 知识分享 &#x…

纯css实现登录表单动效

效果图&#xff1a; 代码展示 // 我这边用的是elementUI表单校验&#xff0c;更改的样式。 <el-form:model"form":rules"rules"ref"fromList":hide-required-asterisk"true"><el-form-item prop"account"><…

【phaser微信抖音小游戏开发005】画布上添加图片

特别注意&#xff1a;真机模拟的时候&#xff0c;尽量使用网络图片资源&#xff0c;不要在小程序源文件里面使用图片&#xff0c;会出现真机加载不成功&#xff0c;小程序包体积过大的问题。我们学习过程中&#xff0c;只是作为演示使用。 推荐使用场景&#xff1a; 背景图片…

vue3引入video.js

一.引入video.js yarn add video.js videojs-player/vue --save 或者 npm install video.js videojs-player/vue --save 二.vue3项目main.js引入 import VueVideoPlayer from "videojs-player/vue" import "video.js/dist/video-js.css" const app cr…

W6100-EVB-PICO做DNS Client进行域名解析

前言 在上一章节中我们用W6100-EVB-PICO通过dhcp获取ip地址&#xff08;网关&#xff0c;子网掩码&#xff0c;dns服务器&#xff09;等信息&#xff0c;给我们的开发板配置网络信息&#xff0c;成功的接入网络中&#xff0c;那么本章将教大家如何让我们的开发板进行DNS域名解…

AP5179 高端电流采样降压恒流驱动IC SOP8 LED车灯电源驱动

产品描述 AP5179是一款连续电感电流导通模式的降压恒流源&#xff0c;用于驱动一颗或多颗串联LED输入电压范围从 5 V 到 60V&#xff0c;输出电流 最大可达 2.0A 。根据不同的输入电压和外部器件&#xff0c; 可以驱动高达数十瓦的 LED。内置功率开关&#xff0c;采用高端电流…

MySQL性能优化

索引下推是什么&#xff1f; 索引下推是索引下推是 MySQL 5.6 及以上版本上推出的&#xff0c;用于对查询进行优化。 索引下推是把本应该在 server 层进行筛选的条件&#xff0c;下推到存储引擎层来进行筛选判断&#xff0c;这样能有效减少回表。 举例说明&#xff1a; 首先…

掌握Java JDK 1.8 API帮助文档中文版,事半功倍编程

文章目录 1. JDK 1.8 API帮助文档简介2. 如何查阅JDK 1.8 API帮助文档中文版2.1 在线文档2.2 本地文档2.3 集成开发环境&#xff08;IDE&#xff09; 3. 如何使用JDK 1.8 API帮助文档中文版3.1 寻找类和方法3.2 阅读文档说明3.3 查看示例代码 4. 总结 引言&#xff1a; Java是一…

Docker Compose 使用方法

目录 前言 安装 Docker Compose Ubuntu 安装与更新 Red Hat 安装与更新 验证是否安装 Docker Compose 创建 docker-compose.yml 文件 创建一个MySQL 与 tomcat 示例 使用Docker Compose启动服务 前言 Docker Compose 是一个工具&#xff0c;旨在帮助定义和 共享多容器…

Docker 容器生命周期:创建、启动、暂停与停止----从创建到停止多角度分析

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

解决Hadoop审计日志hdfs-audit.log过大的问题

【背景】 新搭建的Hadoop环境没怎么用&#xff0c;就一个环境天天空跑&#xff0c;结果今天运维告诉我说有一台服务器磁盘超过80%了&#xff0c;真是太奇怪了&#xff0c;平台上就跑了几个spark测试程序&#xff0c;哪来的数据呢&#xff1f; 【问题调查】 既然是磁盘写满了&…

Python案例分析|使用Python图像处理库Pillow处理图像文件

本案例通过使用Python图像处理库Pillow&#xff0c;帮助大家进一步了解Python的基本概念&#xff1a;模块、对象、方法和函数的使用 使用Python语言解决实际问题时&#xff0c;往往需要使用由第三方开发的开源Python软件库。 本案例使用图像处理库Pillow中的模块、对象来处理…