Skip to content

钩子专属

  • 首页
  • 安卓技术
  • 安卓教程

All posts by oshook

  • 首页   /  
  • 作者: oshook
  • ( 页面2 )
安卓技术, 安卓教程 6 月 21,2023

十三、数据存储

1、SharedPreferences:以键值对的形式存储在xml中,
应用场景:轻量级存储以及持久性存储
2、SQLite数据库:嵌入式数据库、使用SQL语言
应用场景:结构性数据
3、文件存储:通过Java IO操作写入文件中
量大的数据、文件缓存
4、ContentProvider: Android的四大组件之一,仅作为传输数据的媒介
进程间数据共享、交换
5、网络存储:通过网络获取远程服务器的数据
与远程应用共享,交互。更新数据

作者 oshook
安卓技术, 安卓教程 6 月 21,2023

十二、性能优化-绘制优化和布局优化

绘制性能的好坏主要影响:应用中的页面显示速度
优化方向:

  1. 降低View.onDraw()的复杂度
  2. 避免过度绘制,即布局优化

一、降低View.onDraw()的复杂度

二、布局优化

布局性能的好坏主要影响:应用中页面的显示速度

具体优化方案

1、选择耗费性能较少的布局

性能耗费低的布局 = 功能简单 = FrameLayout、LinearLayout
性能耗费高的布局 = 功能复杂 = RelativeLayout
注:

  1. 嵌套所耗费的性能 > 单个布局本身耗费的性能
  2. 即 完成需求时:宁选择 1个耗费性能高的布局,也不采用嵌套多个耗费性能低的布局

2、减少布局的层级(嵌套)

抽取公共布局时《merge》,子view定位是跟引用《merge》时的父布局有关

3、提高布局的复用性

使用 布局标签 《include》

4、减少初次测量和绘制时间

优化方案:使用 布局标签《ViewStub》 & 尽可能少用布局属性 wrap_content
所以在已知宽高为固定值时,不使用wrap_content

参考:https://www.jianshu.com/nb/21401973

作者 oshook
安卓技术, 安卓教程 6 月 21,2023

十一、性能优化–内存泄漏

背景:Android系统分配给每个应用程序的内存有限
内存泄漏的定义:本该被回收的对象不能被回收而停留在堆内存中
内存泄漏的原因:当一个对象已经不再被使用时,本该被回收但却因为另外一个正在使用的对象持有它的引用从而导致它无法被回收
内存泄漏导致的后果:会使应用崩溃

人为原因:无意识地持有对象引用,使得持有引用者的生命周期>被引用者的生命周期

原理:程序在申请内存后,当该内存不需使用但却无法被释放以及归还给程序的现象

Java垃圾回收器(GC)的内存释放 = 垃圾回收算法

分代收集算法

算法思路:

  1. 根据对象存活周期的不同将Java堆内存分为:新生代和老年代
  2. 每块区域特点:
    新生代:对象存活率较低与垃圾回收行为频率高(朝生夕灭的对象,比如方法的局部变量)
    老年代:对象存活率较高与垃圾回收行为频率低 (存活得比较久,但还是要死的对象,如类的成员变量)

3.新生代:采用复制算法,老年代:采用标记-清除、标记-整理算法
还有永久代:在方法区

常见的内存泄漏原因与解决方案

  1. 集合类
  2. Static关键字修饰的成员变量
  3. 非静态内部类/匿名类
  4. 资源对象使用后未关闭

集合类

泄漏原因:集合添加了某个对象,当对象为null时,因为集合仍引用着该对象,会导致该对象没法被回收,从而导致内存泄漏
示例代码:

// 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
List objectList = new ArrayList();
   for (int i = 0; i 

解决方案:集合不再用时,清空集合对象 & 设置为null
示例代码:
objectList.clear();
objectList=null;

Static 关键字修饰的成员变量

背景:被Static关键字修饰的成员变量的生命周期=应用程序的生命周期
如Activity的context传给Static的成员变量
典型案例:单例模式
泄漏原因:
若1个对象已不需再使用 而单例对象还持有该对象的引用,那么该对象将不能被正常回收 从而 导致内存泄漏
示例错误单例代码:

// 创建单例时,需传入一个Context
// 若传入的是Activity的Context,此时单例 则持有该Activity的引用
// 由于单例一直持有该Activity的引用(直到整个应用生命周期结束),即使该Activity退出,该Activity的内存也不会被回收
// 特别是一些庞大的Activity,此处非常容易导致OOM

public class SingleInstanceClass {
    private static SingleInstanceClass instance;
    private Context mContext;
    private SingleInstanceClass(Context context) {
    this.mContext = context; // 传递的是Activity的context
}  
  
public SingleInstanceClass getInstance(Context context) {
    if (instance == null) {
        instance = new SingleInstanceClass(context);
        }
    return instance;
    }
}  

解决方案:应传递Application的Context,因Application的生命周期 = 整个应用的生命周期

非静态内部类 / 匿名类

根本原理: 非静态内部类 / 匿名内部类,运行时默认持有外部类的引用

泄露原因:若 非静态内部类所创建的实例 = 静态(其生命周期 = 应用的生命周期),会因 非静态内部类默认持有外部类的引用 而导致外部类无法释放,最终 造成内存泄露
即 外部类中 持有 非静态内部类的静态对象
示例代码:

    // 背景:
   a. 在启动频繁的Activity中,为了避免重复创建相同的数据资源,会在Activity内部创建一个非静态内部类的单例
   b. 每次启动Activity时都会使用该单例的数据

public class TestActivity extends AppCompatActivity {  

// 非静态内部类的实例的引用
// 注:设置为静态  
public static InnerClass innerClass = null; 
   
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);   

// 保证非静态内部类的实例只有1个
if (innerClass == null)
innerClass = new InnerClass();
}

// 非静态内部类的定义
private class InnerClass {
//...
}
}

// 造成内存泄露的原因:
// a. 当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期、持有外部类TestActivity的引用
// b. 故 TestActivity无法被GC回收,从而导致内存泄漏    

解决方案:

  1. 将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
  2. 尽量 避免 非静态内部类所创建的实例 = 静态

多线程:AsyncTask、实现Runnable接口、继承Thread类

泄漏原因:
当 工作线程正在处理任务 & 外部类需销毁时, 由于 工作线程实例 持有外部类引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露
泄漏示例代码:

 /** 
 * 方式1:新建Thread子类(内部类)
 */  
public class MainActivity extends AppCompatActivity {

public static final String TAG = "carson:";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 通过创建的内部类 实现多线程
new MyThread().start();

}
// 自定义的Thread子类
private class MyThread extends Thread{
@Override
public void run() {
try {
Thread.sleep(5000);
Log.d(TAG, "执行了多线程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

   /** 
 * 方式2:匿名Thread内部类
 */ 
 public class MainActivity extends AppCompatActivity {

public static final String TAG = "carson:";

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 通过匿名内部类 实现多线程
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
Log.d(TAG, "执行了多线程");
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}.start();
}
}


/** 
  * 分析:内存泄露原因
  */ 
  // 工作线程Thread类属于非静态内部类 / 匿名内部类,运行时默认持有外部类的引用
  // 当工作线程运行时,若外部类MainActivity需销毁
  // 由于此时工作线程类实例持有外部类的引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露  

其他:对Context的引用不要超过它本身的生命周期

参考:https://www.jianshu.com/nb/21401973

作者 oshook
安卓技术, 安卓教程 6 月 21,2023

十、性能优化-优化Bitmap资源

背景:Android系统分配给每个应用程序的内存有限,而图片资源非常消耗内存(即Bitmap)
原因:若对Bitmap的使用与内存管理不当,则可能引发内存溢出,从而导致程序崩溃

Bitmap占多大的内存?
bitmap.getAllocationByteCount(); 或者
bitmap.getByteCount();

加载Bitmap的时候,通常要做这几件事

一、根据分辨率适配与缩放图片

  1. 设置多套图片资源
    如果项目大小允许,至少在xh/xxh/xxxh中都放置图片,然后通过BitmapFactory.decodeResource获取

  2. 缩放图片比例进行压缩,按图片分辨率需求加载,用inSampleSize
    示例代码:

     BitmapFactory.Options opts = new BitmapFactory.Options();
     opts.inSampleSize = 4;
     Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);  
    

二、按需选择合适的解码方式:options.inPreferredConfig

优化原因:不同的图片解码方式对应的内存占用大小相差很大
知识点:

  1. ARGB_4444: 基本不用,因为图片比较失真

  2. 示例ARGB_8888: 32位的ARGB位图,由4个8位组成(即32位),每个像素占8位,即A=8,R=8,G=8,B=8。 8+8+8+8 = 32位= 4个字节。即1个像素占4个字节
    示例:若1张480*800的图片,格式是ARGB_8888,将占1536000字节,即1500KB的内存
    3.默认使用解码方式:ARGB_8888
    特别注意:PNG图片设置无效
    示例代码:

    BitmapFactory.Options options1 = new BitmapFactory.Options();
    options1.inPreferredConfig = Bitmap.Config.RGB_565;
    Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher,options1);

四、使用完毕后释放图片资源

优化原因:使用完毕后若不释放图片资源,容易造成内存泄漏,从而导致内存溢出
优化方案:
在 Android2.3.3(API 10)前,调用 Bitmap.recycle()方法
在 Android2.3.3(API 10)后,采用软引用(SoftReference)
示例代码:

    View view = findViewById(R.id.button);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher);
Drawable drawable = new BitmapDrawable(bitmap);
SoftReference drawableSoftReference = 
                                            new SoftReference(drawable);
    Drawable bgdrawable = drawableSoftReference.get();
if(bgdrawable != null) {
    view.setBackground(bgdrawable);
}

需要注意的是,在垃圾回收器对这个Java对象回收前,SoftReference类所提供的get方法会返回Java对象的强引用,一旦垃圾线程回收该Java对象之后,get方法将返回null。所以在获取软引用对象的代码中,一定要判断是否为null,以免出现NullPointerException异常导致应用崩溃

三、设置图片缓存

优化原因:重复加载图片资源消耗太多资源(cpu,内存与流量)
优化方案:使用三级缓存机制或者软引用
三级缓存机制:内存缓存 – 本地缓存(硬盘、数据库或文件) – 网络缓存

为什么要使用三级缓存:
假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。
特别是,当我们想要重复浏览一些图片时,如果每一次浏览都需要通过网络获取,流量的浪费可想而知

什么是三级缓存:

网络缓存, 不优先加载, 速度慢,浪费流量
本地缓存, 次优先加载, 速度快
内存缓存, 优先加载, 速度最快

三级缓存原理:
首次加载App时,通过网络交互来获取图片,之后将图片保存到本地SD卡和内存中
之后运行App时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的,如果SD卡中没有,则加载网络的
内存缓存示例代码
关键代码LruCache

/**
 * 三级缓存之内存缓存
 */
public class MemoryCacheUtils {

    // private HashMap mMemoryCache=new HashMap();//1.因为强引用,容易造成内存溢出,所以考虑使用下面弱引用的方法
    // private HashMap> mMemoryCache = new HashMap();//2.因为在Android2.3+后,系统会优先考虑回收弱引用对象,官方提出使用LruCache
    private LruCache mMemoryCache;

    public MemoryCacheUtils(){
        long maxMemory = Runtime.getRuntime().maxMemory()/8;//得到手机最大允许内存的1/8,即超过指定内存,则开始回收
        //需要传入允许的内存最大值,虚拟机默认内存16M,真机不一定相同
        mMemoryCache=new LruCache((int) maxMemory){
            //用于计算每个条目的大小
            @Override
            protected int sizeOf(String key, Bitmap value) {
                int byteCount = value.getByteCount();
                return byteCount;
            }
        };

    }

    /**
     * 从内存中读图片
     * @param url
     */
    public Bitmap getBitmapFromMemory(String url) {
        //Bitmap bitmap = mMemoryCache.get(url);//1.强引用方法
        /*2.弱引用方法
        SoftReference bitmapSoftReference = mMemoryCache.get(url);
        if (bitmapSoftReference != null) {
            Bitmap bitmap = bitmapSoftReference.get();
            return bitmap;
        }
        */
        Bitmap bitmap = mMemoryCache.get(url);
        return bitmap;

    }

    /**
     * 往内存中写图片
     * @param url
     * @param bitmap
     */
    public void setBitmapToMemory(String url, Bitmap bitmap) {
        //mMemoryCache.put(url, bitmap);//1.强引用方法
        /*2.弱引用方法
        mMemoryCache.put(url, new SoftReference(bitmap));
        */
        mMemoryCache.put(url,bitmap);
    }
}

图片三级缓存这个,现在基本都是用成熟的图片框架Glide或者picasso
针对Glide的skipMemoryCache(false)//启动内存缓存 diskCacheStrategy() //硬盘缓存策略

参考:https://www.jianshu.com/nb/21401973

作者 oshook
安卓技术, 安卓教程 6 月 21,2023

八、静态代理

定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。
代理对象:起到中介作用

好处:新增额外功能不用改变真实对象类,比如真实对象仅仅买了个礼物,现在打算新增包装礼物功能,就可以用静态代理方式,创建代理对象,里面封装真实对象买礼物,额外做包装礼物功能

缺点:

  1. 由于在客户端(使用时)和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢
  2. 1个静态代理只服务1种类型的目标对象,如果目标对象较多情况下,若采用静态代理,则会出现静态代理对象量多,代码量大,从而导致代码复杂问题

操作原理:创建抽象对象接口,即真实对象和代理对象都要实现的接口,然后在代理对象实现和真实对象实现一样的方法中,引用或者创建真实对象,调用真实对象的方法,然后代理对象额外做一些操作。

示例代码:
步骤1、创建抽象对象接口(Subject):声明你(真实对象)需要让代购(代理对象)帮忙做的事(买Mac)

interface Subject {
    void buyGift();
}

步骤2、创建真实对象

class RealSubject implements Subject {
    @Override
    public void buyGift() {
        System.out.println("我要买一份礼物");
    }
}

步骤3、创建代理对象,并通过代理类创建真实对象示例访问其方法,再额外加功能

class ProxySubject implements Subject {
    @Override
    public void buyGift() {
        RealSubject realSubject = new RealSubject();
        realSubject.buyGift();
        //额外操作
        System.out.println("礼物进行包装");
    }
}

步骤4、开始使用

public class StaticProxyDemo {

    public static void main(String[] args){
        Subject subject = new ProxySubject();
        subject.buyGift();
    }

}

参考:https://www.jianshu.com/p/a8aa6851e09e

作者 oshook
安卓技术 6 月 21,2023

Android开发Service

Service

生命周期

1、通过startService()方式,onCreate()–>onStartCommand()–>onDestroy()
注:

  1. 若一个Service被startService()多次启动,那么onCreate()也只会调用一次,即Service实例只有1个
  2. 整个生命周期的方法中,只有onStartCommand()可多次调用,其他只能调用一次
  3. onStartCommand调用次数=startService调用次数

特点:调用者退出后Service仍然存在

应用场景:服务不需与Activity和Service通信

示例代码:

tv_content.setOnClickListener {
        val intent = Intent(this@BActivity,StartServiceDemo::class.java)
        intent.putExtra("content","nihao")
        startService(Intent(intent))
    }

    tv_content_stop.setOnClickListener {
        val intent = Intent(this@BActivity,StartServiceDemo::class.java)
        stopService(Intent(intent))
    }

定时循环任务示例代码:

public class VersionCheckServices extends Service {
private static String TAG = VersionCheckServices.class.getName();
/**
 * 定时器对象
 */
private AlarmManager manager;
private PendingIntent pendingIntent;
private final int LOOP_TIME = 60;

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

@Override
public void onCreate() {
    super.onCreate();
    manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
}

@Override
public void onDestroy() {
    if (pendingIntent != null) {
        manager.cancel(pendingIntent);
    }
    super.onDestroy();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if ((ModifySetUtils.isCodeCheckVersion() || ModifySetUtils.isCheckVersion() || ModifySetUtils.isFourCheckVersion()) && !SPUtils.isCheckUser()) {
        if (!AppUtils.isAppForeground()) {
            new CheckVersionPassPresenter().dealCheckVersion(this,checkVersionCallBack);
        } else {
            next(LOOP_TIME);
        }
    } else {
        //非审核版本就不需要继续调用服务了
        stopSelf();
    }
    return super.onStartCommand(intent, flags, startId);
}


/**
 * 检查审核版本的回调
 */
private HttpOnNextListener checkVersionCallBack = new HttpOnNextListener() {


    @Override
    public void onError(ApiException e) {
        next(LOOP_TIME);
    }

    @Override
    public void onNext(CheckPassVersionResult checkPassVersionResult) {
        if(checkPassVersionResult.getData() != null && checkPassVersionResult.getData().isPass()){
            SPNoCleanUtil.saveCheckVersion(true);
            IntentUtils.startGuideActivity(VersionCheckServices.this);
        } else {
            next(LOOP_TIME);
            SPNoCleanUtil.saveCheckVersion(false);
        }
    }

    @Override
    public void onDealCodeNext(CheckPassVersionResult baseResult) {
        next(LOOP_TIME);
        SPNoCleanUtil.saveCheckVersion(false);
    }


};


/**
 * 设置定时器,准备下次调用
 *
 * @param time 调用时间,秒
 */
private void next(int time) {
    Intent intent = new Intent(this, VersionCheckServices.class);
    pendingIntent = PendingIntent.getService(this, 0, intent, 0);
    manager.set(AlarmManager.RTC, System.currentTimeMillis() + time * 1000, pendingIntent);
}}

2、通过bindService()方式,onCreate()–>onBind()–>onUnbind()–>onDestroy()
注:

  1. 客户端通过一个IBinder接口与服务进行通信
  2. 若一个Service被BindService()多次启动,那么onCreate()也只会调用一次,即Service实例只有1个
  3. 多个客户端可绑定到同一个服务上,当所有客户端都解绑后,系统会销毁服务

特点:调用者退出后,随着调用者销毁

应用场景:服务需与Activity和Service通信、需控制服务开始时刻

bindService()方式的示例代码:
先定义Service子类:

public class BindServiceDemo extends Service {

private String TAG = BindServiceDemo.class.getSimpleName();

private MyBinder mBinder = new MyBinder();

@Override
public void onCreate() {
    super.onCreate();
    Log.i(TAG,"onCreate");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(TAG,"onStartCommand");
    return super.onStartCommand(intent, flags, startId);
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    Log.i(TAG,"onBind");

    return mBinder;
}

@Override
public boolean onUnbind(Intent intent) {
    Log.i(TAG,"onUnbind");
    return super.onUnbind(intent);
}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.i(TAG,"onDestroy");
}

public class MyBinder extends Binder{
    public void service_connect_Activity() {
        Log.i(TAG,"Service关联了Activity,并在Activity执行了Service的方法");


    }
}}

在AndroidManifest.xml中注册
使用:

private var myBinder:BindServiceDemo.MyBinder?= null

//创建ServiceConnection的匿名类
private val connection = object : ServiceConnection {

    //重写onServiceConnected()方法和onServiceDisconnected()方法
    //在Activity与Service解除关联的时候调用
    override fun onServiceDisconnected(name: ComponentName) {}

    //在Activity与Service关联的时候调用
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        //实例化Service的内部类myBinder
        //通过向下转型得到了MyBinder的实例
        myBinder = service as BindServiceDemo.MyBinder
        //在Activity调用Service类的方法
        myBinder?.service_connect_Activity()
    }
}

tv_bind_service.setOnClickListener {
    val intent = Intent(this@BActivity,BindServiceDemo::class.java)
    bindService(intent,connection, Context.BIND_AUTO_CREATE)
}

tv_unbind_service.setOnClickListener {
    //仅此示例,正常要判是否已经绑定成功
    unbindService(connection)
}

IntentService

多线程IntentService
本质:Handler + HandlerThread
为什么要使用IntentService呢,
因为1、普通的Service不是一个单独的进程,它和它的应用程序在同一个进程中
2、Service不是一个线程,就意味着应该避免在Service中进行耗时操作

工作流程:客户端通过startService(Intent)来启动IntentService; 我们并不需要手动地区控制IntentService,当任务执行完后,IntentService会自动停止; 可以启动IntentService多次,每个耗时操作会以工作队列的方式在IntentService的 onHandleIntent回调方法中执行,并且每次只会执行一个工作线程,执行完一,再到二这样!

生命周期: onCreate() –> onStartCommand() –> onHandleIntent() –> onDestroy()

多次调用 startService(Intent)会多次调用onStartCommand()和onHandleIntent()

注意:
一. 工作任务队列 = 顺序执行
由于onCreate()只会调用一次 = 只会创建1个工作线程
二、不建议通过 bindService() 启动 IntentService
因为bindService()的生命周期是onCreate() –> onBind() –> onunBind() –> onDestory(),并不会调onStartCommand(),因此不会将消息发送到消息队列,那么onHandleIntent()将不会回调,即无法实现多线程操作

远程服务Service

远程服务与本地服务最大的区别:远程Service与调用者不在同一个进程里,而本地服务则是与调用者在同一个进程里

使用场景:
多个应用程序共享同一个后台服务(远程服务)
具体使用:
为了让远程Service与多个应用程序的组件(四大组件)进行跨进程通信(IPC),需要使用AIDL。
AIDL:Android接口定义语言
操作步骤:
一:先定义后台服务(服务器端)
步骤1:新建AIDL文件,创建提供给客户端的接口,定义好接口方法
步骤2:在Service子类中实现AIDL中定义的接口方法,即回调给客户端接口和客户端通信
关键通过AIDL_Service1.Stub mBinder = new AIDL_Service1.Stub()。生成binder,然后binder在客户端绑定成功后onServiceConnected传入改binder,然后在客户端通过AIDL_Service1.Stub.asInterface(binder)反转成AIDL_Service1实例,就可以拿到服务器端传过来的信息
示例代码:

@Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        aidl_service1 = AIDL_Service1.Stub.asInterface(service);

        try {
            aidl_service1.toDoService();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

步骤3:在AndroidManifest.xml中注册为android:exported=”true”的,还有加上,可以隐式intent绑定远程Service
二:客户端
步骤1:拷贝服务器端的aidl文件到main目录下,是整个aidl目录拷贝过来,不要做任何改动
步骤2:使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
步骤3:通过bindService()方法启动,intent指定服务器端的服务名称和包名,绑定远程Service

示例代码:
服务器
定义aidl接口(鼠标右键new里面有AIDL,创建完Rebuild Project):

service_aidl.png

定义Service子类:

public class AIDLServiceDemo extends Service {

private String TAG = AIDLServiceDemo.class.getSimpleName();

AIDL_Service1.Stub mBinder = new AIDL_Service1.Stub() {

    @Override
    public void toDoService() throws RemoteException {
        Log.i(TAG,"客户端通过AIDL与远程后台成功通信");

    }
};

@Override
public void onCreate() {
    super.onCreate();
    Log.i(TAG,"onCreate");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i(TAG,"onStartCommand");
    return super.onStartCommand(intent, flags, startId);
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
    Log.i(TAG,"onBind");

    return mBinder;
}

@Override
public boolean onUnbind(Intent intent) {
    Log.i(TAG,"onUnbind");
    return super.onUnbind(intent);
}

@Override
public void onDestroy() {
    super.onDestroy();
    Log.i(TAG,"onDestroy");
}}

在AndroidManifest.xml中注册:


        
            
        
    

客户端:
完全拷贝aidl文件过来:

service_client.png

绑定和使用:

public class MainActivity extends AppCompatActivity {

private TextView tvBindService;

private AIDL_Service1 aidl_service1;

private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        aidl_service1 = AIDL_Service1.Stub.asInterface(service);

        try {
            aidl_service1.toDoService();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

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

    tvBindService = findViewById(R.id.tv_bind_remote_service);

    tvBindService.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent("com.cong.myapplication.service.AIDL_Service1");
            intent.setPackage("com.cong.myapplication.service");
            bindService(intent,connection,BIND_AUTO_CREATE);
        }
    });
}}

Androidmanifest里Service的常见属性说明

android:name:Service的类名
android:permission:申明此Service的权限。注:有提供了该权限的应用才能控制或连接此服务
android:process:表示该服务是否在另一个进程中运行(远程服务)。注:不设置默认为本地服务;remote则设置成远程服务
android:exported:该服务是否能够被其他应用程序所控制或连接
注:不设置默认为false

参考:https://www.jianshu.com/p/d963c55c3ab9

作者 oshook
安卓技术 6 月 21,2023

Android开发补间动画、逐帧动画、属性动画

动画

参考:https://www.jianshu.com/p/2412d00a0ce4
分为三种:补间动画、逐帧动画、属性动画

一、补间动画(Tweened Animation)

作用对象:视图控件View
原理: 通过确定开始的视图样式以及结束的视图样式,中间的动画变化过程有系统补全来确定一个动画
优点:简单、方便,已封装好基础动画效果
缺点:仅控制整体实体效果,无法控制属性
动画样式: 平移动画(Translate)缩放动画(scale)旋转动画(rotate)透明度动画(alpha)
应用场景:视图中,标准、基础的动画效果。Activity,Fragment的切换效果。ViewGroup中子元素的出场效果
数值上的理解:从-100到100的值,以“%”结尾,表示百分比相对于自身;从-100到100的值,以“%p”结尾,表示百分比相对于父容器。例如平移开始位置在自身中间则是50%,如平时开始位置在父容器的则是50%p

1.平移动画(Translate)

TranslateAnimation类
示例代码:


如上代码中,虽然通过fillBefore,fillAfter设置了停留在动画之后,但是控件还是在原来位置,点击动画后的位置无反应。如果不设置fillBefore,fillAfter,动画结束后会还是显示在动画前的位置
Kotlin代码中使用

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_second)

    var translateAnim = AnimationUtils.loadAnimation(this,R.anim.view_anim)

    tv_content.startAnimation(translateAnim)

    tv_content.setOnClickListener {
         Toast.makeText(this, "好的", Toast.LENGTH_SHORT).show()
    }
}

纯Kotlin代码:

var translateAnim = TranslateAnimation(0f,500f,0f,0f)

translateAnim.duration = 3000

tv_content.startAnimation(translateAnim)  

2.缩放动画(Scale)

ScaleAnimation类
xml定义





注意:fromXScale和fromYScale都要写,pivotX只是改变开始缩放的始点,不加缩放的始点是view的左上角,
android:pivotX=”50″是控件view的x方向上加50像素
android:pivotX=”50%”是控件view的x方向上加view控件宽的50%
android:pivotX=”50%p”是控件view的x方向上加父布局控件宽的50%

3.旋转动画(Rotate)

RotateAnimation类



android:fromDegrees=”0″ // 动画开始时 视图的旋转角度(正数 = 顺时针,负数 = 逆时针)

4.透明度动画(Alpha)

AlphaAnimation类





Activity的切换效果

实现效果从右向左滑动
in_from_right.xml




  

out_to_left.xml





一般用在Activity的成对出现,但至少保持动画时间是一样的
首先要了解Activity的位置
x = 0%p是代表Activity刚好在屏幕中间
x = 100%p是代表Activity在屏幕的右侧
x = -100%p是代表Activity在屏幕的左侧

Fragment的切换效果(几乎用不到)

 // 采用`FragmentTransavtion`的 setCustomAnimations()进行设置

FragmentTransaction fragmentTransaction = mFragmentManager
.beginTransaction();

fragmentTransaction.setCustomAnimations(
R.anim.in_from_right,
R.anim.out_to_left);

// 此处的自定义动画效果同Activity,此处不再过多描述

组合动画

采用标签

监听动画

anim.addListener(new AnimatorListenerAdapter() {  
    // 向addListener()方法中传入适配器对象AnimatorListenerAdapter()
    // 由于AnimatorListenerAdapter中已经实现好每个接口
    // 所以这里不实现全部方法也不会报错
    @Override  
    public void onAnimationStart(Animator animation) {  
        // 如想只想监听动画开始时刻,就只需要单独重写该方法就可以
    }  
});  

二、逐帧动画(Frame Animation)

作用对象:视图控件View
原理:将动画拆分为帧的形式,且定义每一帧是一张图片,按顺序播放
特点:简单、方便
缺点:容易引起OOM,因会用大量以及尺寸较大的图片资源
应用场景: 较为复杂的个性化动画效果

三、属性动画(Property Animation)

作用对象:任意Java对象
原理: 在一定时间间隔内,通过不断改变值以及赋值给对象的属性,从而实现该对象在该属性上的动画效果
特点:作用对象进行了拓展,不只是View对象,甚至无对象也可以,不只是4种基本变换,还有其他动画效果
缺点:用起来稍微复杂点
应用场景: 与属性相关、更加复杂的动画效果,比如通过改变View的颜色属性达到的动画效果

逐帧动画和补间动画存在一定的缺点:

1:作用对象局限于View
即补间动画只能作用View上,没法对非View的对象进行操作。有些情况的动画效果只是视图的某个属性,比如通过视图的颜色动态变化,从而实现动画效果,而不是针对整个View视图进行动画操作
2:没有改变View的属性,只是改变视觉效果
如移动View,虽然看起来View的位置变了,但实际没变,还是原来的
3:动画效果单一
补间动画只能实现平移、旋转、缩放 & 透明度这些简单的动画需求,一旦遇到相对复杂的动画效果,即超出了上述4种动画效果,那么补间动画则无法实现

ValueAnimator类

原理:通过不断控制值的变化,再不断手动赋给对象的属性,从而实现动画效果
kotlin代码:正常流程

Log.i("tv_content",tv_content.width.toString()+"t"+tv_content.layoutParams.width)
    //value动画
    val valueAnimator = ValueAnimator.ofInt(tv_content.layoutParams.width,500)

    valueAnimator.duration = 2000

    valueAnimator.addUpdateListener {

        val currentValue:Int = it.animatedValue as Int
        println(currentValue)
        //获取不断改变的值给view相应的属性
        tv_content.layoutParams.width = currentValue
        tv_content.requestLayout()
    }
    valueAnimator.start()  

在xml中定义


kotlin用xml中定义的

//用xml的value动画
    var valueAnimator = AnimatorInflater.loadAnimator(this,R.animator.value_anim)
    valueAnimator.setTarget(tv_content)
    valueAnimator.start()

注:有点坑地方补间动画xml的文件夹名是anim,属性动画是animator。第二单纯这样setTarget(view)是没用的,它不知道改变什么属性。
所以用属性动画还是用java代码定义写方便
对象:ValueAnimator.ofObject()
作用:将初始值以对象的形式过渡到结束值,即通过操作对象实现动画效果
需我们自定义TypeEvaluator来告知系统如何进行从初始对象过渡到结束对象的逻辑
示例PointEvaluator.java

    // 实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {

    // 复写evaluate()
    // 在evaluate()里写入对象动画过渡的逻辑
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        
        // 将动画初始值startValue 和 动画结束值endValue 强制类型转换成Point对象
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;
        
        // 根据fraction来计算当前动画的x和y的值
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
        
        // 将计算后的坐标封装到一个新的Point对象中并返回
        Point point = new Point(x, y);
        return point;
    }

}

估值器(TypeEvaluator)

作用:设置动画 如何从初始值 过渡到 结束值 的逻辑

ObjectAnimator类

是ValueAnimator的子类
是通过反射找到相应的set函数
原理:通过不断控制值的变化,再不断自动赋给对象的属性,从而实现动画效果
示例代码:

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotationX",0,270,0);  
animator.setDuration(2000);  
animator.start();  

怎么知道哪些属性是能赋值改变形成动画的呢
1、要使用ObjectAnimator来构造对画,要操作的控件中,必须存在对应的属性的set方法
2、setter 方法的命名必须以骆驼拼写法命名,即set后每个单词首字母大写,其余字母小写,即类似于setPropertyName所对应的属性为propertyName
简单来说:先找View的set相应属性试试,有,就把属性开头大写改为小写即可
自定义ObjectAnimator属性

public class MyPointView extends View {

private PointRadius mPoint = new PointRadius(100);

public MyPointView(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mPoint != null) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(300, 300, mPoint.getRadius(), paint);
    }
}

void setPointRadius(int radius) {
    mPoint.setRadius(radius);
    invalidate();
}
}

关键setPointRadius方法
使用:

val objectAnimator = ObjectAnimator.ofInt(my_point_view,"pointRadius",0,300,200)
    objectAnimator.duration = 2000
    objectAnimator.start()

参考:https://www.jianshu.com/p/2412d00a0ce4

作者 oshook
安卓技术, 安卓教程 6 月 21,2023

六、同步和死锁

重点:

1、 资源共享时需要进行同步操作

2、 程序中过多的同步会产生死锁

卖火车票,加延迟票数就出问题了
因为加入延迟操作,那么一个线程有可能在还没有对票数进行减操作之前,其他线程就已经将票数减少了,这样一来就会出现票数为负的情况

要解决这样的问题,就必须使用同步操作

同步是指多个操作在同一个时间段内只能一个线程进行,其他线程要等此线程完成之后才可以继续执行

死锁

死锁一般情况下就是表示在互相等待

简单示例

public class MainActivity extends AppCompatActivity {

private String obj1 = "obj1";
private String obj2 = "obj2";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    new Thread(new Lock1()).start();
    new Thread(new Lock2()).start();
}

class Lock1 implements Runnable{

    @Override
    public void run() {
        try {
            synchronized (obj1){
                Log.i("MainActivity1","Lock1 lock obj1");
                Thread.sleep(3000);
                //这时想获得obj2锁,但被Lock2占用了
                synchronized (obj2){
                    Log.i("MainActivity1","Lock1 lock obj2");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

class Lock2 implements Runnable{

    @Override
    public void run() {
        try {
            synchronized (obj2){
                Log.i("MainActivity1","Lock2 lock obj2");
                Thread.sleep(3000);
                synchronized (obj1){
                    Log.i("MainActivity1","Lock2 lock obj1");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}}

注意:死锁不一定导致崩溃,只是线程一直被阻塞着,没法结束,就一直销毁这app的内存资源
手写死锁的关键:关键是Thread.sleep(3000) ,睡3s好让其他线程获取需要的锁资源

作者 oshook
安卓技术 6 月 21,2023

五、多线程

如果同时运行多个任务,则所有的系统资源将是共享的,被所有线程所公用,但是程序处理需要CPU资源

实现方式

1、继承Thread类
2、实现Runnable接口(因为java单继承,Thread有限制)

注意: 如果直接调用run()方法,则和传统一样按顺序执行,只有调用start(),才是真正的启动了线程,可以并发执行
如果要想启动线程则肯定依靠Thread类

问题1:为什么不调用run方法,而调用start方法。

因为源码中start方法中。核心是调用private native void start0()
方法。native关键字就是java程序调用本机的操作系统的函数完成特点的功能
证明如果要实现多线程,则肯定需要操作系统的支持,因为多线程操作牵扯到一个抢占CPU的情况

使用Runnable接口

Thread t1 = new Thread(new runnableImpl())
t1.start()

Thread类和Runnable接口的联系和区别

联系:Thread类也是实现Runnable接口的
区别:使用Thread类在操作多线程的时候无法达到资源共享的目的,而使用Runnable接口实现的多线程操作可以实现资源共享
示例代码:

public class MyThread extends Thread {
private int titck = 5;

@Override
public void run() {
    for (int i= 0;i 0) {
            System.out.println("卖票titck"+(titck--));
        }
    }
}
}  

public class MyRunnable implements Runnable {

private int titck = 5;

@Override
public void run() {
    for (int i= 0;i 0) {
            System.out.println("卖票titck"+(titck--));
        }
    }
}
}

实现Runnable接口比继承Thread类有如下的优点
1、适合多个相同程序代码的线程去处理同一资源
2、避免由于单继承局限的影响
3、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的

线程的状态

多线程在操作中也是有一个固定的操作状态的

  • 创建状态:准备好了一个多线程的对象:Thread t = new Thread()
  • 就绪状态:调用了start()方法,等待CPU进行调度
  • 运行状态:执行run()方法
  • 阻塞状态:暂时停止执行,可能将资源交给其他线程使用
  • 终止状态:线程全部执行完,不再使用了

示意图:

线程的状态.png

注意:并不是调用了start()方法就马上运行,需要等待CPU资源调度

========================================================

多线程2-常用操作方法

Java程序运行时启动了多少个线程

答:至少启动了两个线程,一个主线程Main,一个垃圾回收线程GC。因为Java本身具有垃圾收集机制

t.isAlive():线程是否正在运行

t.join():线程强制运行,获取CPU资源运行,其他线程无法运行,必须等待此线程完成之后才可以继续执行

Thead.sleep(500):线程休眠500毫秒

t.interrupt():线程中断其运行状态,要注意中断后return操作,否则还是会跑完run方法。如果线程运行完了,中断就没意思了

t.setDaemon(true):设置线程在后台运行

线程的优先级

有三个优先级,优先级越高越有可能先执行
t.setPriority(Thred.MAX_PRIORITY)
t.setPriority(Thred.NORM_PRIORITY)
t.setPriority(Thred.MIN_PRIORITY)

线程礼让:Thread.currentThead().yield()

将一个线程的操作暂时让给其他线程执行

Object类中,wait(),notify(),notifyAll()唤醒方法

用法:在类中super.wait(),super.notify()

notify(),notifyAll()区别:一般来说,所有等待的线程会按照顺序进行排列,如果现在使用notify()方法的,则会唤醒第一个等待的线程执行,如果使用notifyAll()方法,则会唤醒所有等待的线程,那个线程的优先级高,那个线程就可能先执行

========================================================

线程生命周期

suspend():暂时挂起线程
resume():恢复挂起的线程
stop():停止线程
因为以上三个方法都会产生死锁问题,所有已不建议使用

示意图:

new Thread()–start()–>运行–sleep()/wait()–>暂停

线程生命示意图.png

线程停止运行只能run方法结束

可以设置标志位
示例代码

public class ThreadLife implements Runnable {
//设置标志位
        private boolean flag = true;
        
        @Override
        public void run() {
            int i = 1;
            while (flag){
                System.out.println(i++);
            }
        }
        
        public void stop(){
            this.flag = false;
        }
}

class ThreadLifeDemo{

  public static void main(String[] args){
        ThreadLife threadLife = new ThreadLife();
        new Thread(threadLife).start();
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
            threadLife.stop();
   }
}
作者 oshook
安卓技术, 安卓教程 6 月 21,2023

四、动态代理

原理:

  1. 设计动态代理类(DymanicProxy)时,不需要显式实现与目标对象类(RealSubject)相同的接口,而是将这种实现推迟到程序运行时由JVM来实现。(即在使用时再创建动态代理类和实例)
  2. 通过Java反射机制method.invoke(),通过调用动态代理类对象方法,从而自动调用目标对象的方法

优点:
只需要1个动态代理类就可以解决创建多个静态代理的问题,避免重复,多余代码
缺点:

  1. 效率低
    相比静态代理中直接调用目标对象(真实对象)方法,动态代理则需要通过Java反射机制从而间接调用目标对象方法
  2. 应用场景局限
    因为Java的单继承特性(每个代理类都继承了Proxy类),即只能针对接口创建代理类,不能针对类创建代理类

注:即只能动态代理实现了接口的类,该类可以extends Buy,但是后面必须implements某个接口,代理的实例也是接口的实例。

实例:对礼物进行包装
步骤1:声明处理器类(关键)

//声明处理器类
class DynamicProxy implements InvocationHandler {

    private Object proxyObject;

    //生成动态代理对象
    public Object newProxyInstance(Object subject){
        this.proxyObject = subject;
        return Proxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),this);
    }

    //动态代理对象调用目标对象任何方法前,都会调用处理器类的invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        //做额外操作
        System.out.println("包装礼物");
        result = method.invoke(proxyObject,args);
        return result;
    }
}

步骤2:声明目标对象类的抽象接口

interface SubjectBuy{

    void buyGift();
}

步骤3:声明目标对象类Buy1和Buy2

class Buy1 extends Buy implements SubjectBuy {

    @Override
    public void buyGift() {
        System.out.println("我要买mac");
    }


}

class Buy2 implements SubjectBuy {

    @Override
    public void buyGift() {
        System.out.println("我要买手表");
    }
}

class Buy{
    public void buy(){
        System.out.println("我要买买买");
    }
}

步骤4:通过动态代理对象,调用目标对象的方法

public class DynamicProxyDemo {


    public static void main(String[] args){
        DynamicProxy dynamicProxy = new DynamicProxy();

        Buy1 buy1 = new Buy1();
        SubjectBuy subjectBuy = (SubjectBuy) (dynamicProxy.newProxyInstance(buy1));
        subjectBuy.buyGift();

        Buy2 buy2 = new Buy2();
        SubjectBuy subjectBuy2 = (SubjectBuy)dynamicProxy.newProxyInstance(buy2);
        subjectBuy2.buyGift();
    }

}  

步骤4中注意点:首先接口是特殊的类,newProxyInstance返回的object只能向上转型为接口的对象。因为该object本身是有subject.getClass().getInterfaces()所具有的接口生成的。

源码分析

一、如何创建动态代理类及其实例对象

方法:Proxy.newProxyInstance(subject.getClass().getClassLoader(),subject.getClass().getInterfaces(),this)
解析:Proxy.newProxyInstance()作用:根据指定的类装载器、一组接口 & 调用处理器 生成动态代理类实例,并最终返回

//作用:根据指定的类装载器、一组接口 & 调用处理器 生成动态代理类及其对象实例,并最终返回
  
public static Object newProxyInstance(ClassLoader loader, Class>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
  // 参数说明:
// 参数1:指定产生代理对象的类加载器,需要将其指定为和目标对象同一个类加载器
// 参数2:指定目标对象的实现接口
// 即要给目标对象提供一组什么接口。若提供了一组接口给它,那么该代理对象就默认实现了该接口,这样就能调用这组接口中的方法
// 参数3:指定InvocationHandler对象。即动态代理对象在调用方法时,会关联到哪个InvocationHandler对象

...  
// 仅贴出核心代码

    // 1. 通过 为Proxy类指定类加载器对象 & 一组interface,从而创建动态代理类
    // >>关注3
    Class cl = getProxyClass(loader, interfaces); 
    
    // 2. 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
    Constructor cons = cl.getConstructor(constructorParams); 
    
    // 3. 通过动态代理类的构造函数 创建 代理类实例(传入调用处理器对象
    return (Object) cons.newInstance(new Object[] { h }); 

// 特别注意 
// 1. 动态代理类继承 Proxy 类 & 并实现了在Proxy.newProxyInstance()中提供的一系列接口(接口数组)
// 2. Proxy 类中有一个映射表
  // 映射关系为:(,(,) )
  // 即:1级key = 类加载器,根据1级key 得到 2级key = 接口数组
  // 因此:1类加载器对象 + 1接口数组 = 确定了一个代理类实例
...


}

总结:通过调用处理器类对象的Proxy.newProxyInstance()创建动态代理类及其实例对象(需传入目标类对象)
具体过程:
1.通过为Proxy类指定类加载器和一组接口,从而创建动态代理类的字节码,再根据字节码创建动态代理类
代码:Class cl = getProxyClass(loader, interfaces);

  1. 通过反射机制获取动态代理类的构造函数,其参数类型是调用处理器接口类型
    代码:Constructor cons = cl.getConstructor(constructorParams);
    3.通过动态代理类的构造函数创建代理类实例(传入调用处理器对象)
    代码:(Object) cons.newInstance(new Object[] { h })

如何通过调用动态代理对象方法,从而调用目标对象方法?

Proxy.newProxyInstance()生成了一个动态代理类 及其实例。
该动态代理类记为 :Proxy0Proxy0源码:

    
// 继承:Java 动态代理机制的主类:java.lang.reflect.Proxy
// 实现:与目标对象一样的接口(即上文例子的Subject接口)
public final class $Proxy0 extends Proxy implements Subject {

    // 构造函数
        public ProxySubject(InvocationHandler invocationhandler)   
        {   
        super(invocationhandler);   
    }  
    
     // buybuybuy()是目标对象实现接口(Subject)中的方法
     // 即$Proxy0类必须实现
     // 所以在使用动态代理类对象时,才可以调用目标对象的同名方法(即上文的buybuybuy())
     public final void buybuybuy() {
        try {
            super.h.invoke(this, m3, null); 
            // 该方法的逻辑实际上是调用了父类Proxy类的h参数的invoke()
            // h参数即在Proxy.newProxyInstance(ClassLoader loader, Class>[]interfaces,InvocationHandler h)传入的第3个参数InvocationHandler对象
            // 即调用了调用处理器的InvocationHandler.invoke()
            // 而复写的invoke()利用反射机制:Object result=method.invoke(proxied,args)
            // 从而调用目标对象的的方法 ->>关注4
        return;
        } catch (Error e) {

        } catch (Throwable throwable) {
             throw new UndeclaredThrowableException(throwable);
    }
}

这里源码沾的有点乱,具体看别人的https://www.jianshu.com/p/5dc416ea58a2

此类源码能说明很多问题

  1. 动态代理类已经继承了Proxy父类,所以动态代理类没法再代理实体父类,只能代理接口,只能代理实现接口的类
  2. 动态代理类会实现目标类的所实现的接口,这样就具有目标类的相同实现的方法,然后在实现方法中调用的是super.h.invoke(this, m3, null)。刚好是处理器类重写的invoke方法,而复写的invoke()利用反射机制:Object result=method.invoke(proxied,args)—>从而调用了目标对象的方法

总结:

  1. 动态代理类实现了与目标类一样的接口,并实现了需要目标类对象需要调用的方法
  2. 该方法的实现逻辑 = 调用父类 Proxy类的 h.invoke()
  3. 在 InvocationHandler.invoke() 中通过反射机制,从而调用目标类对象的方法

参考:https://www.jianshu.com/p/5dc416ea58a2

作者 oshook

上一 1 2 3 … 7 下一个

Copyright © 2023 | 粤ICP备14006518号-3