Android AIDL示例-RemoteCallbackList添加移除监听

2023-11-10

前言

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言,它是用来实现进程间通讯的,本文使用AIDL写一个小demo来实现夸进程间通讯 。

本文接着这一篇文章写,这篇文章有个留着的——就是远程AIDL服务端注册解绑Listener。

Android AIDL示例-回调方法版

先来看看服务端和客户端大体的AIDL-生成的java文件 。

如上图所示,蓝色框内是我们定义的AIDL文件,红色框内是自动编译生成的java文件。

服务端

这里只是个简单示意,接下来会在此基础上改。下面我们看下服务端的代码:

/**
 * 音乐管理的服务类
 */
public class MusicManagerService extends Service {
 
    private static final String TAG =  MusicManagerService.class.getSimpleName();
 
    private ArrayList<Music> mMusicList = new ArrayList<>();  //生成的音乐列表
    private List<INewMusicArrivedListener> mListenerList = new ArrayList<>(); //客户端注册的接口列表
    private boolean isServiceDestroy = false; //当前服务是否结束
    private int num = 0;
 
    /**
     * 解绑服务
     * @param conn
     */
    @Override
    public void unbindService(ServiceConnection conn) {
        super.unbindService(conn);
        Log.e(TAG,"unbindService-----");
    }
    
    /**
     * 服务端通过Binder实现AIDL的IMusicManager.Stub接口
     * 这个类需要实现IMusicManager相关的抽象方法
     */
    private Binder mBinder = new IMusicManager.Stub() {
        @Override
        public List<Music> getMusicList() throws RemoteException {
            
            return mMusicList;
        }
 
        @Override
        public void addMusic(Music music) throws RemoteException {
            mMusicList.add(music);
        }
 
        @Override
        public void registerListener(INewMusicArrivedListener listener) throws RemoteException {
            mListenerList.add(listener);
            int num = mListenerList.size();
            Log.e(TAG, "添加完成, 注册接口数: " + num);
        }
 
        @Override
        public void unregisterListener(INewMusicArrivedListener listener) throws RemoteException {
            // 后文实现
        }
    };
 
 
    //新音乐到达后给客户端发送相关通知
    private void onNewMusicArrived(Music music) throws Exception {
        mMusicList.add(music);
        Log.e(TAG, "发送通知的数量: " + mMusicList.size());
        int num = mListenerList.size();
        for (int i = 0; i < num; ++i) {
            INewMusicArrivedListener listener = mListenerList.get(i);
            listener.onNewMusicArrived(music);
        }
        for (Music b : mMusicList){
            Log.e(TAG,b.name+"  "+b.author);
        }
    }
 
    @Override public void onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate-------------");
        //首先添加两首歌曲
        mMusicList.add(new Music("《封锁我一生》", "王杰"));
        mMusicList.add(new Music("《稻香》", "周杰伦"));
    }
 
    @Override public void onDestroy() {
        isServiceDestroy = true;
        super.onDestroy();
        Log.e(TAG,"onDestroy-----");
    }
 
    @Nullable @Override 
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
 
}

客户端

我们看下客户端的代码:这里需要注意,回调接口在客户端注册,目的是为了让服务端拿到回调接口,通知客户端。客户端拿到的是服务端的代理,注册回调可看作服务端收到回调listener。

 private TextView music_list;
 
    private IMusicManager mRemoteMusicManager; //音乐管理类  通过aidl文件编译生成的java类
 
 
    //监听新音乐的到达的接口
    private INewMusicArrivedListener musicArrivedListener = new INewMusicArrivedListener.Stub() {
        /**
         * 服务端有新音乐生成
         * @param newMusic
         * @throws RemoteException
         */
        @Override
        public void onNewMusicArrived(Music newMusic) throws RemoteException {
            
        }
    };
 
 
    //绑定服务时的链接参数
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override public void onServiceConnected(ComponentName name, IBinder service) {
            IMusicManager musicManager = IMusicManager.Stub.asInterface(service);
 
            try {
                mRemoteMusicManager = musicManager;
                Music newMusic = new Music("《客户端音乐》", "rock");
                musicManager.addMusic(newMusic);
                musicManager.registerListener(musicArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override public void onServiceDisconnected(ComponentName name) {
            mRemoteMusicManager = null;
            Log.e(TAG, "绑定结束");
        }
    };
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        music_list = findViewById(R.id.music_list);
    }
 
    /**
     * 获取music列表
     *
     * @param view 视图
     */
    public void getMusicList(View view) {
        if (mRemoteMusicManager !=null){
            List<Music> list = null;
            try {
                list = mRemoteMusicManager.getMusicList();
            }catch (Exception e){
 
            }
            if (list!=null){
                String content = "";
                for (int i = 0; i < list.size(); ++i) {
                    content += list.get(i).toString() + "\n";
                }
                music_list.setText(content);
            }
        }
        Toast.makeText(getApplicationContext(), "正在获取中...", Toast.LENGTH_SHORT).show();
    }
 
 
    /**
     * 绑定服务按钮的点击事件
     *
     * @param view 视图
     */
    public void bindService(View view) {
        Intent intent = new Intent(this, MusicManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
 
 
    /**
     * 解绑服务
     */
    public void unbindService(View view){
        unbindService(mConnection);
    }

绑定一个服务 ,然后注册一个新音乐到达的接口,当服务端有新音乐生成的时候就会触发这个接口。

跨进程通信可能遇到问题

在IPC的过程当中,可能会遇到如下状况须要考虑:

在AIDL中客户端向服务端注册一个回调方法时,服务端要考虑客户端是否意外退出(客户端由于错误应用Crash,或者被Kill掉了),服务端还不知道,去回调客户端,出现错误。

客户端和服务端进程状态处理

在进程间通讯过程当中,极可能出现一个进程死亡的状况。若是这时活着的一方不知道另外一方已经死了就会出现问题。那咱们如何在A进程中获取B进程的存活状态呢?

android确定给咱们提供了解决方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法须要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,咱们就能够在binderDied方法里面作一些异常处理,释放资源等操作了。

Android SDK提供一个封装好的对象:RemoteCallbackList,帮我们自动处理了Link-To-Death的问题。

这里,简单介绍一下RemoteCallbackList:

public class RemoteCallbackList
extends Object
java.lang.Object
↳ android.os.RemoteCallbackList<E extends android.os.IInterface>

负责维护远程接口列表的繁琐工作,一般用于执行从Service到其客户端的回调 。特别是:

跟踪一组已注册的IInterface回调,通过其基础唯一性IBinder进行识别:IInterface#asBinder。

附加IBinder.DeathRecipient到每一个已注册的接口,以便在其过程消失时能够将其从列表中清除。

对接口的基础列表执行锁定以处理多线程传入的调用,并以线程安全的方式遍历该列表。

要使用此类,只需与服务一块儿建立一个实例,而后在客户端注册和取消注册服务时调用其register(E)和unregister(E)方法。回调到注册客户端,使用beginBroadcast(), getBroadcastItem(int)和finishBroadcast()。

当注册的回调在过程中消失了,该类将负责自动将其从列表中删除。若是要在这种状况下做其余工作,要建立一个实现该onCallbackDied(E)方法的子类。

RemoteCallbackList帮咱们避免了IPC两个进程在调用过程当中发生意外crash,致使回调失败或者进程crash的问题。

将上面服务端-客户端代码修改如下:

/**
 * 音乐管理的服务类
 */
public class MusicManagerService extends Service {
 
    private static final String TAG =  MusicManagerService.class.getSimpleName();
 
    // 省略部分代码。。。
    
    private RemoteCallbackList<INewMusicArrivedListener> mListenerList = new RemoteCallbackList<>(); //客户端注册的接口列表
    

    //新音乐到达后给客户端发送相关通知
    private void onNewMusicArrived(Music music) throws Exception {
        mMusicList.add(music);
        
         mListenerList.beginBroadcast();
         //遍历全部注册的Listener,逐个调用它们的实现方法,也就是通知全部的注册者
         for(int i=0;i<mListenerList.getRegisteredCallbackCount();i++){
             INewMusicArrivedListener listener = mListenerList.getBroadcastItem(i);
             listener.onNewMusicArrived(music);
         }
         mListenerList.finishBroadcast();

        for (Music b : mMusicList){
            Log.e(TAG,b.name+"  "+b.author);
        }
    }
    
    /**
     * 服务端通过Binder实现AIDL的IMusicManager.Stub接口
     * 这个类需要实现IMusicManager相关的抽象方法
     */
    private Binder mBinder = new IMusicManager.Stub() {
       
        // 省略部分代码。。。
 
        @Override
        public void registerListener(INewMusicArrivedListener listener) throws RemoteException {

            mListenerList.register(listener);
        }
 
        @Override
        public void unregisterListener(INewMusicArrivedListener listener) throws RemoteException {
            
            mListenerList.unregister(listener);
        }
    };
 
}
 private IMusicManager mRemoteMusicManager; //音乐管理类  通过aidl文件编译生成的java类
 
 
    //监听新音乐的到达的接口
    private INewMusicArrivedListener musicArrivedListener = new INewMusicArrivedListener.Stub() {
        /**
         * 服务端有新音乐生成
         * @param newMusic
         * @throws RemoteException
         */
        @Override
        public void onNewMusicArrived(Music newMusic) throws RemoteException {
            
        }
    };

   //进程挂掉通知处理
   private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(mRemoteMusicManager != null){
                //Server端意外died
                try {
                    mRemoteMusicManager.unRegistListener(musicArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                mRemoteMusicManager.asBinder().unlinkToDeath(mDeathRecipient,0);
                mRemoteMusicManager = null;
                //TODO:bindService again
                bindService()
            }
        }
    };
 
 
    //绑定服务时的链接参数
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override public void onServiceConnected(ComponentName name, IBinder service) {
            mRemoteMusicManager = IMusicManager.Stub.asInterface(service);
 
            try {
                //设定死亡接收器,这个是针对IBinder对象的
                service.linkToDeath(mDeathRecipient,0);

                Music newMusic = new Music("《客户端音乐》", "rock");
                mRemoteMusicManager.addMusic(newMusic);
                mRemoteMusicManager.registerListener(musicArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
 
        @Override public void onServiceDisconnected(ComponentName name) {
            mRemoteMusicManager = null;
            Log.e(TAG, "绑定结束");
        }
    };
 
 
    /**
     * 绑定服务按钮的点击事件
     *
     */
    public void bindService() {
        Intent intent = new Intent(this, MusicManagerService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }
 
 
    /**
     * 解绑服务
     */
    public void unbindService(View view){

        try {
            if(mRemoteMusicManager != null && mRemoteMusicManager.asBinder().isBinderAlive()){
                mRemoteMusicManager.unRegistListener(musicArrivedListener);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        unbindService(mConnection);
    }

这里须要说明的是在客户端进程中,经过DeathRecipient对象,调用IBinder的linkToDeath和unLinkToDeath方法,实现了 万一Server进程Died以后,对Service进行再次注册

简单介绍一下linkToDeath和unlinkToDeath:

/**
     * Interface for receiving a callback when the process hosting an IBinder
     * has gone away.
     * 
     * @see #linkToDeath
     */
    public interface DeathRecipient {
        public void binderDied();
    }

    /**
     * Register the recipient for a notification if this binder
     * goes away.  If this binder object unexpectedly goes away
     * (typically because its hosting process has been killed),
     * then the given {@link DeathRecipient}'s
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will be called.
     * 
     * <p>You will only receive death notifications for remote binders,
     * as local binders by definition can't die without you dying as well.
     * 
     * @throws RemoteException if the target IBinder's
     * process has already died.
     * 
     * @see #unlinkToDeath
     */
    public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
            throws RemoteException;

    /**
     * Remove a previously registered death notification.
     * The recipient will no longer be called if this object
     * dies.
     * 
     * @return {@code true} if the <var>recipient</var> is successfully
     * unlinked, assuring you that its
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will not be called;  {@code false} if the target IBinder has already
     * died, meaning the method has been (or soon will be) called.
     * 
     * @throws java.util.NoSuchElementException if the given
     * <var>recipient</var> has not been registered with the IBinder, and
     * the IBinder is still alive.  Note that if the <var>recipient</var>
     * was never registered, but the IBinder has already died, then this
     * exception will <em>not</em> be thrown, and you will receive a false
     * return value instead.
     */
    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);

使用它比较简单,只须要实现DeathRecipient的bindDied方法。

linkToDeath和unLinkToDeath是成对出现的,参照上面客户端中MainActivity的实现,在ServiceConnection中onServiceConnected的时候link,在bindDied方法中unlink。linkToDeath是为IBinder对象设置死亡代理,unLinkToDeath是解除以前设置的死亡代理,并能够在此时做从新绑定的动作。

注意

  1. AIDL只提供了有限的传输数据类型,自定义的传输类型须要序列化
  2. RemoteCallbackList很好的解决了IPC过程当中可能出现的某一方进程Crash,引发另外一方Exception的问题
  3. DeathRecipient理解和调用(解决Server端进程意外终止,Client端得到的Binder对象丢失问题)

为什么这么麻烦,不用remoteCallList可以吗?

如果不用remoteCallList绑定和解绑listener会怎样,为什么这么麻烦呢?

可以增加几条打印,看看解绑和绑定时能不能成功:

 原因就在于我们的注册对象listener是在进程间传输的,Binder在服务端会把客户端传递过来的对象重新转换为新的对象,因而注册和解注册的根本就不是一个对象,当然不能达到解除注册的目的了

我们在服务端解除listener的代码处添加下面两行代码:

System.out.println("解绑定时候的listener:  "+listener);

System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());

在服务端绑定listener的代码处下面两行代码:

System.out.println("绑定时候的listener:  "+listener);

System.out.println("绑定时候的listener对应的Binder:  "+listener.asBinder());

 而后运行,查看Log输出:

 从Log输出上面很明显的可以看到绑定和解绑定的listener并不是同一个对象,那么解除绑定肯定是失败的,但是从Log输出上可以看出listener所对应的Binder对象是相同的,他们都是BinderProxy对象,原因在于:跨进程间通信时,服务端要创建Binder对象,客户端要拿到服务端Binder对象在Binder驱动中的引用,对应到客户端就是BinderProxy对象了,我们这里的情况是服务端和客户端通信,那么此时的服务端将成为客户端,所以收到的Binder就是BinderProxy类型了,为什么绑定和解绑定过程中listener对象不同,但是listener对象对应的Binder对象却的相同的呢?这个我们需要看下系统为我们的IMessageManager.aidl生成的.java文件内容了:

@Override 
public void registerLoginUser(com.hzw.messagesend.ILoginOnListener listener) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
mRemote.transact(Stub.TRANSACTION_registerLoginUser, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}

其他代码就不展示了。因此,如果不用RemoteCallList而用普通的Arralist的话,也可以

public void unRegisterListener(INewMusicArrivedListener listener)
				throws RemoteException {
			for(int i = list.size()-1; i >= 0; i--)
			{
				if(list.get(i).asBinder() == listener.asBinder())
				{
					//解除绑定注册
					System.out.println("解绑定时候的listener:  "+listener);
					System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());
					list.remove(list.get(i));
					break;
				}
			}
		}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android AIDL示例-RemoteCallbackList添加移除监听 的相关文章

  • Android - 保存动态更改布局的状态

    我有一个布局 用户可以在其中添加按钮并将其放置在他们想要的位置 我想允许用户保存他们的布局 以便下次打开应用程序时加载它 有谁知道我是否可以将文件保存到 SD 卡上 或者 我可以使用某种layout getXml 方法并将其放入我的应用程序
  • 在应用程序简历中隐藏软键盘

    我有一个 Android 应用程序 使用 Xamarin 用 C 编写 我已将应用程序简化为包含 TextView 和用于横幅广告的 Google admod AdView 的 LinearLayout 我不希望软键盘出现在应用程序中 这不
  • Android 通知进度条冻结

    这是我正在使用的代码 http pastebin com 3bMCKURu http pastebin com 3bMCKURu 问题是 一段时间后 文件变得更重 通知栏下拉速度变慢 最后它就冻结了 你的通知太频繁了 这就是它冻结的原因 让
  • 使用 Android 前台服务为 MediaPlayer 创建通知

    问题就在这里 我目前正在开发一个应用程序 该应用程序必须提供 A 广播播放器 来自 URL 的 AAC 直播 还有一个播客播放器 来自 URL 的 MP3 流 该应用程序必须能够在后台运行 Android 服务 并通过以下方式向用户公开持续
  • Android 应用程序在后台运行时保存数据

    目前我正在开发 xmmp 客户端 当应用程序位于前台时 该客户端工作得很好 但由于事实上 当应用程序处于后台时 我在 Application 类中保存了大量数据 复杂的 ArrayList 字符串和布尔值作为公共静态 每个字段都被垃圾收集
  • 如何将安卓手机从睡眠状态唤醒?

    如何以编程方式将 Android 手机从睡眠状态唤醒 挂起至内存 我不想获取任何唤醒锁 这意味着手机在禁用 CPU 的情况下进入 真正的 睡眠状态 我想我可以使用某种RTC 实时时钟 机制 有人有例子吗 Thanks 为了让Activity
  • Android:“dp”到“px”转换?

    我正在读这篇文章 http developer android com guide practices screens support html http developer android com guide practices scre
  • 如果我们使用后退按钮退出,为什么 Android 应用程序会重新启动?

    按住主页按钮并返回应用程序时 应用程序不会重新启动 为什么使用后退按钮会重新启动 如果我们使用后退按钮退出 有什么方法可以解决在不重新启动的情况下获取应用程序的问题吗 请帮忙 当您按下Home按钮 应用程序将暂停并保存当前状态 最后应用程序
  • Android 原理图内容提供程序库配置?

    Jake Wharton 在最近的一次演讲中提到了这个库 它看起来是避免大量样板文件的好方法 所以我尝试了一下 但没有任何成功 https github com SimonVT schematic https github com Simo
  • 更新到材质 1.2.0 后,材质按钮上缺少圆角半径属性

    这是我的材质按钮代码
  • Android 版 Robotium - solo.searchText () 不起作用

    我在使用 Robotium 时遇到 searchText 函数问题 我正在寻找这个字符串
  • Android 启动器快捷方式

    我制作了一个简单的打卡 打卡时钟应用程序 我想向用户添加在主屏幕上创建快捷方式的选项 该快捷方式将切换应用程序的状态 超时 超时 但我根本不希望此快捷方式在屏幕上打开应用程序 这是我的 setupShortcut private void
  • 从 android 简单上传到 S3

    我在网上搜索了从 android 上传简单文件到 s3 的方法 但找不到任何有效的方法 我认为这是因为缺乏具体步骤 1 https mobile awsblog com post Tx1V588RKX5XPQB TransferManage
  • 使用 Matrix.setPolyToPoly 选择位图上具有 4 个点的区域

    我正在 Android 上使用位图 在使用 4 个点选择位图上的区域时遇到问题 并非所有 4 点组都适合我 在某些情况下 结果只是一个空白位图 而不是裁剪后的位图 如图所示 并且 logcat 中没有任何错误 甚至是内存错误 这是我用来进行
  • 材质设计图标颜色

    应该是哪种颜色 暗 材质图标 在官方文档上 https www google com design spec style icons html icons system icons https www google com design s
  • Android 设备上的静默安装

    我已经接受了一段时间了 在 Android 上静默安装应用程序是不可能的 也就是说 让程序安装捆绑为 APK 的应用程序 而不提供标准操作系统安装提示并完成应用程序安装程序活动 但现在我已经拿到了 Appbrain 快速网络安装程序的副本
  • 保护 APK 中的字符串

    我正在使用 Xamarin 的 Mono for Android 开发一个 Android 应用程序 我目前正在努力使用 Google Play API 添加应用内购买功能 为此 我需要从我的应用程序内向 Google 发送公共许可证密钥
  • android Accessibility-service 突然停止触发事件

    我有一个 AccessibilityService 工作正常 但由于开发过程中的某些原因它停止工作 我似乎找不到这个原因 请看一下我的代码并告诉我为什么它不起作用 public class MyServicee extends Access
  • 在webview android中加载本地html文件

    我正在尝试在 android 的 webview 中加载 html 文件的内容 但是 它给了我 网页不可用错误 如果我尝试使用谷歌或雅虎等网站 它们就会起作用 html文件位于src gt main gt assests gt index
  • 如何删除因 Google Fitness API 7.5.0 添加的权限

    将我的 play services fitness api 从 7 0 0 更新到 7 5 0 后 我注意到当我将新版本上传到 PlayStore 时 它 告诉我正在添加一个新权限和 2 个新功能 我没有这样做 有没有搞错 在做了一些研究来

随机推荐

  • JQuery DOM

    操作文本 常用方法 代码实现 div 我是div div
  • 用Python赚钱的4个大招,女程序员做副业躺赚

    关于穷 去年有了一个更学术的说法 隐形贫困人口 就是因为有太多 种草达人 让我们为了物质生活超前消费 再加上不理财的话 那简直是雪上加霜 看到知乎上面最近有一个很火的问题 90后的你 现在拥有多少存款 你会看到人生百态 有人父母双亡 白手起
  • Docker : Docker镜像的分层结构

    1 美图 2 概述 3 base镜像 base 镜像简单来说就是不依赖其他任何镜像 完全从0开始建起 其他镜像都是建立在他的之上 可以比喻为大楼的地基 docker镜像的鼻祖 base 镜像有两层含义 不依赖其他镜像 从 scratch 构
  • Github 如何设置 master 为默认分支

    起因 github 新版本上线之后 原默认分支为 master 现在统一改为了 main 我不禁产生疑问 为什么要改呢 原因大概是漂亮国农奴历史遗留问题导致的 master 奴隶主 slaver 奴隶 解决方法 我们已经习惯以 master
  • 算法:优先队列-理论

    目录 优先队列 我们平时比较常见的优先队列的场景有什么 优先队列的实现机制 java的优先队列是怎么实现的 优先队列 我们先回忆一下什么是队列 队列 一种先进先出的数据结构 主要关注点在于先入的元素
  • 【初级计量经济学】内生性问题——工具变量法(Stata实现)

    目录 1 数据来源以及变量详目 1 1变量一览表 1 2数据描述性统计 2 模型设定以及初步回归 2 1 OLS 模型 2 2 回归结果 2 3 回归结果分析 内生性问题校正 1工具变量法 2 两阶段最小二乘 3 豪斯曼检验 stata手工
  • 智能机器人用什么语言编程

    智能机器人用什么语言编程 对于很多家长们来说 他们的任务之一就是培养孩子的学习 很多的家长在培养孩子的学习方面可以说是十分的重视的 会给孩子选择一些能够有利于孩子成长的课程 就拿现在很多的家长想要孩子去学习机器人编程的课程来说 有的家长对于
  • servlet / jsp 学习——java,关系,区别,核心概念,代码例子

    互联网三大基石 HTTP HyperText Transfer Protocol 传输数据 URL Uniform Resource Locator 定位数据 HTML HyperText Markup Language 显示数据 serv
  • 类的构造函数和析构函数

    1 把对象的初始化工作放在构造函数中 把清除工作放在析构函数中 当对象被创建时 构造函数被自动执行 当对象消亡时 析构函数被自动执行 这下就不用担心忘了对象的初始化和清除工作 2 构造函数 析构函数与类同名 由于析构函数的目的与构造函数的相
  • CAD——MV视口与图层

    CAD MV视口与图层 MV视口 MV 空格 CAD MV视口锁定比例 Z S 1 nXP 图层 LA 空格 MV视口 MV 空格 MS 空格 表示进入MV视口空间 PS 空格 表示退出MV视口空间 操作 打开布局 gt MV 空格 gt
  • Flask 实现分页展示数据(简单套路)

    简单方法套路 套用模板 定义宏 在templates里新建一个 macros html 代码如下 macro pagination widget pagination endpoint ul class pagination ul
  • golang开发的准备 - gvm(go版本管理软件)的使用

    概述 gvm软件用于go版本的管理 主要的功能为go版本的查看 下载安装和切换 一 go版本的查看 gvm listall 命令用于查看可以下载安装的go版本 shane ubuntu gvm binscripts gvm listall
  • java Map集合的使用

    Map集合的使用 Map的特点 Map接口的常用实现类 Map集合的常用方法的使用 1 添加map集合元素 2 删除map集合元素 3 替换map集合元素 4 集合中是否包含指定的key和value 5 分别获取map集合中所有的key和v
  • Python中CV2及PIL库无法安装解决方式

    ERROR Could not find a version that satisfies the requirement cv2 from versions none ERROR No matching distribution foun
  • 关于DDOS的几个误区,你知道几个?

    在竞争激烈的互联网领域 总有一些组织和个人利用DDoS攻击进行破坏 从而达到自己的目的 对于DDoS攻击 大部分人的认知来源于新闻报道 新闻报道这种方式在普及DDoS危害性的同时 也会不自觉地引入一些误区 下面小墨就带大家来看看关于DDoS
  • Git常用操作总结,经常遇到但是记不住

    今天分享一些我平时在使用git时经常会遇到的几个问题 由于命令一直记不住 每次都得去搜索贼麻烦 所以今天想着整理一下分享出来 希望能帮助到也经常遇到这些问题的你 Git remote 使用总结 使用场景 新建一个git仓库并与远程关联 初始
  • 纯css画三角形

    思路 用border实现 lt html lang en gt lt head gt lt meta charset UTF 8 gt lt meta name viewport content width device width ini
  • 数学形态学滤波学习

    一 概述 数学形态学是建立在集合论基础上了一门学科 具体在图像处理领域 它把形态学和数学中的集合论结合起来 描述二值或灰度图像中的形态特征 来达到图形处理的目的 形态学主要是通过结构元素和图像的相互作用对图像进行拓补变换从而获得图像结构信息
  • 常用的概率分布:伯努利分布、二项分布、多项式分布、高斯分布、指数分布、拉普拉斯分布和Dirac-delta分布

    伯努利分布 Bernoulli distribution 伯努利分布 单个二值随机变量的分布 由单个参数 0 1 控制 例 抛硬币 正面朝上的概率 二项式分布 binomial distrubution 二项式分布 在n次试验中事件A恰好发
  • Android AIDL示例-RemoteCallbackList添加移除监听

    前言 AIDL是一个缩写 全称是Android Interface Definition Language 也就是Android接口定义语言 它是用来实现进程间通讯的 本文使用AIDL写一个小demo来实现夸进程间通讯 本文接着这一篇文章写