Lsposed 技术原理探讨 && 基本安装使用

Posted r0ysue_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lsposed 技术原理探讨 && 基本安装使用相关的知识,希望对你有一定的参考价值。

目前市场上主流的Hook框架有两款,一个是Frida,另一个是Xposed。他们之间各有优缺点,简单总结来说:Frida快,但是不稳定;Xposed稳定,但是操作繁琐,减缓了分析的操作的速度。

1.1 Xposed && Lsposed

1.1.1 Xposed 系列工具发展历程

本章,先对Xposed展开讲解。那Lsposed是什么呢?和xposed有什么联系呢?既然大家购买了这本书,相信大家的技术水平已经不是停留在小白的水平了。这里只讲述核心的知识点。

对于Xposed、Edposed、Lsposed,非常有必要说明下它们的发展历程,方便于读者的理解。Xposed是早期的Hook框架,并且有成熟的社区以及API来支撑,但是它的作者在2017年就停止了项目的维护,如图1-1所示,Github的上Xposed的最新版本是v89,这个是后来增加的,最稳定的新版是v82版本,我们现在的框架依赖依然使用v82的版本。


图1-1 Xposed API

现对于2017年,已经过去5年的时间。技术是在不停的迭代升级的,虽然Xposed还能是使用,但是它本身繁琐的操作,每次编写完Hook代码后,需要重启手机,这样大大的减缓了分析的过程,并且浪费我们的生命。由于技术的升级,Xposed的特征也越来越多,被反调成了常有的事情。Xposed的作者对Xposed停止维护后,此框架依然起着很大的作用,后来出现了Edposed,并接管了Xposed的位置。但是Edposed的存在期间很短,框架本身也有很多弊病。于是,对于Edposed的改良框架Lspoded脱颖而出。Edposed我们在后面也不会提及,因为它只是一个过渡版本。

Lsposed是在Edposed的基础上进行改良的新框架。并且接管了Xposed的API,可以很好的兼容Xposed的API。所以我们后面的开发工作都是基于Xposed的API进行开发,再配合上Lsposed的优秀特性,体验感十分良好。

对于Xposed的弊端,这里有必要说明一下:Xposed会对所有的应用都进行注入,也就是全局模式,导致应用启动变得非常的慢,这个在Lsposed上有了很大的改良。在Lsposed上,我们可以对目标app选择注入,并且支持多选。这项改进也不算是重大的技术升级,说到底就是引导用户正确的使用Xposed,确保Xposed框架和模块不会做额外的事情。


图1-2 Lsposed github official declare

图1-2是Lsposed的官方声明,下面是对介绍的翻译:

Riru / Zygisk 模块试图提供一个 ART Hook 框架,该框架利用 LSPlant 挂钩框架提供与 OG Xposed 一致的 API。

Xposed 是一个模块框架,可以在不触及任何 APK 的情况下改变系统和应用程序的行为。这很棒,因为这意味着模块可以在不同版本甚至 ROM 上工作而无需任何更改(只要原始代码没有太大更改)。它也很容易撤消。由于所有更改都在内存中完成,您只需停用模块并重新启动即可恢复原始系统。还有许多其他优点,但这里只是一个优点:多个模块可以对系统或应用程序的同一部分进行更改。对于修改后的 APK,您必须选择一个。没有办法组合它们,除非作者用不同的组合构建了多个 APK。

从图1-2中还能看到它支持android 8.1-13 的系统版本。补充说明下,Xposed旧版的API只支持的Android7,后来更新的出来的一些版本,如v89,是支持Android 8的版本的。

1.1.2 Xposed && Lsposed 框架原理

Xposed的Hook原理是从整个Android的启动流程入手而设计出来的框架,看懂Android的启动流程,也是从从按了开机键后,从硬件到软件,到底做了什么事情,我们才能更好的理解Xposed框架。

1.1.2.1 Android 启动流程

如图1-3所示:是Android启动的整个流程图,下面我们一一对每个节点进行介绍。


图1-3 Android 启动流程图

Android整系统分为四层,分别为kernel、Native、FrameWork、应用层(APP),loader也可以单独算一层,是硬件的启动加载预置项。

  1. 首先当我们长按开机键(电源按钮)开机,此时会引导芯片开始从固化到ROM中的预设代码处执行,然后加载引导程序到RAM。然后启动加载的引导程序,引导程序主要做一些基本的检查,包括RAM的检查,初始化硬件的参数。

  2. 到达内核层的流程后,这里初始化一些进程管理、内存管理、加载各种Driver等相关操作,如Camera Driver、Binder Driver 等。下一步就是内核线程,如软中断线程、内核守护线程。下面一层就是Native层,这里额外提一点知识,层于层之间是不可以直接通信的,所以需要一种中间状态来通信。Native层和Kernel层之间通信用的是syscall,Native层和Java层之间的通信是JNI。

  3. 在Native层会初始化init进程,也就是用户组进程的祖先进程。init中加载配置文件init.rc,init.rc中孵化出ueventd、logd、healthd、installd、lmkd等用户守护进程。开机动画启动等操作。核心的一步是孵化出Zygote进程,此进程是所有APP的父进程,这也是Xposed注入的核心,同时也是Android的第一个Java进程(虚拟机进程)。

  4. 进入框架层后,加载zygote init类,注册zygote socket套接字,通过此套接字来做进程通信,并加载虚拟机、类、系统资源等。zygote第一个孵化的进程是system_server进程,负责启动和管理整个Java Framework,包含ActivityManager、PowerManager等服务。

  5. 应用层的所有APP都是从zygote孵化而来

1.1.2.2 Xposed 注入源码剖析

上述是Android的大致流程,接下来我们从代码执行的角度来看执行链。

从图1-3中,我们可以总结出一条启动链:

init => init.rc => app_process => zygote => ...

一个应用的启动,核心的步骤是Framework层的zygote启动,zygote是所有进程的父进程,也就是说,所有APP应用进程都是由zygote孵化而来。为什么要从Native层开始说明呢?这是因为Native层开始就是Android源码的运行过程,Xposed的注入也就是从Native层开始的。

根据Android启动流程图可知,zygote是由app_process初始化产生的。app_process是一个二进制可执行文件,它的表现形式是一个bin文件,它位于/system/bin/app_process,如图1-4所示:


图1-4 Android app_process

app_process是由app_main.cpp编译而来,它的源码路径位于/frameworks/base/cmds/app_process/app_main.cpp,Xposed就是将XposedInstaller.jar包替换原始的app_process来实现全局注入,所以我们每次编写完Hook代码后,需要重启手机才能生效。

  • init

从Android启动的流程图可知,首先进行的是init进程的启动,它在源码中对应着/system/core/init/init.cpp文件,如图1-5所示:


图1-5 /system/core/init/init.cpp

C++文件的启动从main开始,所以我们从main开始追,核心代码如下所示:

int main(int argc, char** argv) 
    ...

    if (bootscript.empty())             // 第一次开机
        parser.ParseConfig("/init.rc");  // 解析配置文件 init.rc
        parser.set_is_system_etc_init_loaded(
                parser.ParseConfig("/system/etc/init"));
        parser.set_is_vendor_etc_init_loaded(
                parser.ParseConfig("/vendor/etc/init"));
        parser.set_is_odm_etc_init_loaded(parser.ParseConfig("/odm/etc/init"));
     else 
        parser.ParseConfig(bootscript);
        parser.set_is_system_etc_init_loaded(true);
        parser.set_is_vendor_etc_init_loaded(true);
        parser.set_is_odm_etc_init_loaded(true);
    

    ...

    while (true)  ... 

    return 0;

可以看到,第一次开机后,它会解析init.rc配置文件,此配置文件中会创建文件,并做一些用户组分配、权限赋予的操作,它位于/system/core/rootdir/init.rc,如图1-6所示:


图1-6 /system/core/rootdir/init.rc

此外,还会有一些触发器的操作,源码如下所示:

# Now we can start zygote for devices with file based encryption
trigger zygote-start

zygote的生成就是由触发器生成的,在稍高一点的Android版本中,会分为32位和64位的zygote,如图1-7所示:


图1-7 不同位数的zygote

这里以init.zygote32.rc为例做说明,其它东西都是一样的,就是架构有区别。在init.zygote32.rc配置文件中,有如下代码:

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    class main
    priority -20
    user root
    group root readproc
    socket zygote stream 660 root system
    onrestart write /sys/android_power/request_state wake
    onrestart write /sys/power/state on
    onrestart restart audioserver
    onrestart restart cameraserver
    onrestart restart media
    onrestart restart netd
    onrestart restart wificond
    writepid /dev/cpuset/foreground/tasks

我们只看第一句命令,这个是启动zygote进程的核心。这里加载了app_process文件,那么这个文件做了什么呢?app_process对应着Android源码中的app_main.cpp,他的位置如图1-8所示:


图1-8 app_main.cpp 位置

同样,我们还是看它的main函数,只不过这里要看他后面跟的参数是什么含义,参数匹配如图1-9所示:


图1-9 app_main.cpp main 参数匹配

代码中,可以看到对zygote做了标记,那我们继续追踪,看看哪里引用了这个bool变量,全局搜索后,最后匹配到了文件的末尾,代码如图1-10所示:


图1-10 zygote 引用

runtime.start加载了com.android.internal.os.ZygoteInit类,继续追踪runtime.start,最后定位到/frameworks/base/core/jni/AndroidRuntime.cpp,核心代码如下所示:

void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)

    ...

    // 初始化 JNI 接口
    /* start the virtual machine */
    JniInvocation jni_invocation;
    jni_invocation.Init(NULL);
	
    // 创建 env 指针
    JNIEnv* env;
    // 
    if (startVm(&mJavaVM, &env, zygote) != 0) 
        return;
    
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) 
        ALOGE("Unable to register all android natives\\n");
        return;
    

    /*
     * We want to call main() with a String array with arguments in it.
     * At present we have two arguments, the class name and an option string.
     * Create an array to hold them.
     */
    jclass stringClass;
    jobjectArray strArray;
    jstring classNameStr;

    stringClass = env->FindClass("java/lang/String");
    assert(stringClass != NULL);
    strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);
    assert(strArray != NULL);
    classNameStr = env->NewStringUTF(className);
    assert(classNameStr != NULL);
    env->SetObjectArrayElement(strArray, 0, classNameStr);

    for (size_t i = 0; i < options.size(); ++i) 
        jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());
        assert(optionsStr != NULL);
        env->SetObjectArrayElement(strArray, i + 1, optionsStr);
    

    /*
      * Start VM.  This thread becomes the main thread of the VM, and will
      * not return until the VM exits.
      */
    // 开启 VM 虚拟机,这个线程变成 VM 主线程,直到 VM 退出才结束
    // className => com.android.internal.os.ZygoteInit
    char* slashClassName = toSlashClassName(className != NULL ? className : "");
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) 
        ALOGE("JavaVM unable to locate class '%s'\\n", slashClassName);
        /* keep going */
     else   // 把 main 函数调起来
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) 
            ALOGE("JavaVM unable to find main() in '%s'\\n", className);
            /* keep going */
         else 
            env->CallStaticVoidMethod(startClass, startMeth, strArray);

#if 0   // 条件编译
            if (env->ExceptionCheck())
                threadExitUncaughtException(env);
#endif
        
    
    free(slashClassName);
    ...

在后半段代码可以看出,会把com.android.internal.os.ZygoteInit类,通过Native层反射调用起来。根据类名,我们找到此文件的位置,

public static void main(String argv[]) 
    try 
		
        ...
		
        // socket 通信注册
        registerZygoteSocket(socketName);
        
        // 预加载所需资源到VM中,如class、resource、OpenGL、公用Library等;
        // 所有fork的子进程共享这份空间而无需重新加载,减少了应用程序的启动时间,
        // 但也增加了系统的启动时间,Android启动最耗时的部分之一。
        preload();
        
        // 初始化gc,只是通知VM进行垃圾回收,具体回收时间、怎么回收,由VM内部算法决定。
        // gc()需在fork前完成,这样将来复制的子进程才能有尽可能少的垃圾内存没释放;
        gcAndFinalize();

        // 启动system_server,即fork一个Zygote子进程
        if (startSystemServer) 
            Runnable r = forkSystemServer(abiList, socketName, zygoteServer);

			// @code r == null in the parent (zygote) process, and @code r != null in the
			// child (system_server) process.
			if (r != null) 
				r.run();
				return;
			
        

        // 进入循环模式,获取客户端连接并处理
        runSelectLoop(abiList);
        
        // 关闭和清理zygote socket
        closeServerSocket();
     catch (MethodAndArgsCaller caller) 
        caller.run();
     catch (RuntimeException ex) 
        Log.e(TAG, "Zygote died with exception", ex);
        closeServerSocket();
        throw ex;
    

这里是zygote的初始化,首先开启了socket的通信。在proload中预加载一些资源到VM中,所有fork后的子进程都可以共享这份资源,而无须重新启动。与此同时,增加了系统启动时间,这个环节是整个Android启动链条上耗时最长的部分。

核心在于forkSystemServer,通过此函数fork系统服务进程,代码如下所示:

private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) 

    ...

    int pid;

    try 
        parsedArgs = new ZygoteConnection.Arguments(args);
        ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
        ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

        /* Request to fork the system server process */
        pid = Zygote.forkSystemServer(
                parsedArgs.uid, parsedArgs.gid,
                parsedArgs.gids,
                parsedArgs.debugFlags,
                null,
                parsedArgs.permittedCapabilities,
                parsedArgs.effectiveCapabilities);
     catch (IllegalArgumentException ex) 
        throw new RuntimeException(ex);
    

    /* For child process */
    if (pid == 0) 
        if (hasSecondZygote(abiList)) 
            waitForSecondaryZygote(socketName);
        

        zygoteServer.closeServerSocket();
        return handleSystemServerProcess(parsedArgs);
    

以上就是zygote的启动流程追踪,相信大家到这里都明白了。那么xposed如何Hook zygote,进而实现应用程序的Hook呢?这也是根据上述流程来的,上面说了,核心是替换app_process,app_process.cpp对应的文件是app_main.cpp,我们翻看Github上的Xposed源码,如图1-11所示:


图1-11 xposed app_main.cpp

在图片中,我们看到有两个app_main.cpp出现,那它们之间有什么区别的,这就需要看编译文件了,编译配置文件就是Android.mk,进去之后,最开始就看到它们是如何编译的,如图1-12所示:


图1-12 Android.mk

PLATFORM_SDK_VERSION是SDK的版本,大于等于21以上使用app_main2.cpp,而SDK21对应着Android5,如图1-13所示:


图1-13 Android 版本

通过Xposed的编译配置文件,可以得出Xposed定制了app_process文件,我们直接看app_main2.cpp,目前大家使用的手机型号都来到了Android 8及以上。在app_main2.cpp文件中,我们同样查看被标记的zygote代码,如图1-14所示:


图1-14 xposed app_main2.cpp 核心

它使用Xposed.initialize进行初始化,我们追进去看它的实现,源码位于xposed.cpp中,源码如下所示:

/** Initialize Xposed (unless it is disabled). */
bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) 

	// 参数接管
    xposed->zygote = zygote;
    xposed->startSystemServer = startSystemServer;
    xposed->startClassName = className;
    xposed->xposedVersionInt = xposedVersionInt;

	// XposedBridge.jar 加载到 ClassPath 中
	return addJarToClasspath();

初始化完成后进入魔改的runtimeStart:

runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote);

调用 XPOSED_CLASS_DOTS_ZYGOTE,即 xposedBridge 类的 main 方法,如图1-15所示:


图1-15 XposedBridge

查看xposedBridge类中的main方法,源码如下所示:

protected static void main(String[] args) 
	// Initialize the Xposed framework and modules
	try 
		if (!hadInitErrors()) 
			initXResources();

			SELinuxHelper.initOnce();
			SELinuxHelper.initForProcess(null);

			runtime = getRuntime();
			XPOSED_BRIDGE_VERSION = getXposedVersion();

			// 初始化
			if (isZygote) 
				XposedInit.hookResources();
				XposedInit.initForZygote();
			

			XposedInit.loadModules();  // 加载 Xposed 模块
		 else 
			Log.e(TAG, "Not initializing Xposed because of previous errors");
		
	 catch (Throwable t) 
		Log.e(TAG, "Errors during Xposed initialization", t);
		disableHooks = true;
	

	// Call the original startup code  => 原始执行链
	if (isZygote) 
		ZygoteInit.main(args);
	 else 
		RuntimeInit.main(args);
	

从源码得知,它会在此加载Xposed的资源文件,以此完成后续的Hook操作。

1.1.2.3 zygisk 技术原理

而新的Lsposed是基于Magisk的插件zigisk完成的,它也是在Android启动链上入手,实现Hook。

zygisk相当于zygote的magisk,它会在zygote进程中运行magisk的一部分,这也使得Magisk模块更加强大。我们对于Magisk的使用需求一般是获取手机的root权限。使用zygisk可以实现动态替换app_process。

1.2 Lsposed 环境搭建

测试机硬件条件

  • 机器型号:Nexux 5X
  • 系统:Android 8.1.0
  • 镜像下载:Android 8.1.0
    可以到https://developers.google.com/android/images#bullhead下载
  1. root手机,使用Magisk root即可。在官网下载Magisk-v25.1.apk,安装到手机上。

  2. 对下载好的镜像进行boot.img提取,如图1-16所示:


图1-16 boot.img 提取

  1. 按照官网的方式进行boot.img patch,如图1-17所示:


图1-17 boot.img patch

patch完成后,会生成一个patch后的boot.img文件,带有magisk的标识,如图1-18所示:


图1-18 patch 后的boot.img

  1. 将patch后的boot.img在bootloader模式使用fastboot flash boot boot.img命令刷入手机,至此Magisk刷入完成。

  2. 在Magisk app 设置中打开zigisk

  3. 新版的Magisk仓库没有插件,需要手动进行下载,我们到Github上去下载,如图1-19所示:


图1-19 Lsposed安装包

  1. 本地安装,点击模块后,有一个本地安装的按钮,如图1-20所示:


图1-20 Lsposed安装

本地安装完成后就可以使用Lsposed了

1.2.1 Lsposed Hook 环境搭建

Lsposed的开发环境同Xposed的一致。

  1. app级build.gradle加入图1-21所示的配置


图1-21 build.gradle

  1. AndroidManifest.xml 配置,如图1-22所示


图1-22 AndroidManifest.xml

  1. 入口文件建立

在main下新建asserts资源目录,如图1-23所示:


图1-23 asserts

并在下一级建立xposed_init文件,如图1-24所示:


图1-24 xposed_init文件

  1. 添加入口类,如图1-25所示


图1-25 入口类

HookTest是新建的一个类,用来测试我们的demo

  1. 编写测试代码


图1-26 测试代码

测试代码的功能是打印目标app的包名

1.2.2 基本使用

  1. 激活模块

运行Hook代码后,打开Lsposed,模块第一次启动还未激活,如图1-27所示:


图1-27 未激活的模块

  1. 选择应用

激活模块后,我们可以选择待Hook的应用,应用是可以多选的,如图1-28所示:


图1-28 应用选择

  1. 重启应用,Hook代码即可生效,如图1-29所示:


图1-29 Hook 生效

Android 动画技术总结分享

前言

  • 动画的使用 是 Android 开发中常用的知识
  • 本次分享探讨 Android动画,包括动画的种类、使用、原理等,以及支持跨平台动画库--Lottie 

目录

 

1. 动画类型

Android动画主要分为分为两大类(三种):

  1. 视图动画:补间动画、逐帧动画
  2. 属性动画
  • 下面。我们一起探讨下三种动画的使用 & 原理

 

2. 视图动画(View Animation)

  • 作用对象:视图(View),不可用于View的属性
  • 具体分类:补间动画 & 逐帧动画

下面会详细介绍这两种视图动画:

  2.1 逐帧动画

                       

    2.1.1  作用对象 : 视图控件(View

  • AndroidTextView、Button等等
  • 不可作用于View的属性,如:颜色、背景、长度等等

     2.1.2  原理

  • 将动画拆分为 帧 的形式,且定义每一帧 = 每一张图片
  • 逐帧动画的本质:按序播放一组预先定义好的图片

   2.1.3  具体使用

  • 步骤1. 将动画资源(即每张图片资源)放到 drawable文件夹里
  • 步骤2. 在 res/anim/xxx.xml 的文件夹里创建 设置动画资源(图片资源)

  • 步骤3:在Java代码中载入 & 启动动画

   2.1.4  特点 : 

  • 优点:使用简单、方便
  • 缺点:容易引起 OOM,因为会使用大量 & 尺寸较大的图片资源
  • 使用场景:较为复杂的个性化动画效果。

2.2 补间动画(Tween Animation)

  •    简介如下:

    

  • 使用如:位移变化: 

     1.  在 Java 代码中设置 : 

        Button mButton = (Button) findViewById(R.id.Button);
        Animation translateAnimation = new TranslateAnimation(0,500,0,500);
        translateAnimation.setDuration(3000);
        mButton.startAnimation(translateAnimation);

     1.  在XML 代码中设置,在Java代码中创建Animation对象并播放 :

       Button mButton = (Button) findViewById(R.id.Button);
        Animation translateAnimation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
        mButton.startAnimation(translateAnimation)

  • 补间动画显示效果如下:

    

 

3. 属性动画

  • 属性动画(Property Animation)是在 Android 3.0API 11)后才提供的一种全新动画模式

    

 3.1 插值器 & 估值器

    插值器(Interpolator)

  • 定义:一个接口
  • 作用:设置 属性值 从初始值过渡到结束值 的变化规律
  1. 如匀速、加速 & 减速 等等
  2. 即确定了 动画效果变化的模式,如匀速变化、加速变化 等等
  • Android内置了 9 种内置的插值器实现:

    accelerate_interpolator (动画加速进行),accelerate_decelerate_interpolato (先加速后减速),

    anticipate_interpolator  (先退后再加速前进), anticipate_overshoot_interpolator (先退后再加速前进,超出终点后再回终点),  bounce_interpolator (最后阶段弹球效果),cycle_interpolator (周期运动),decelerate_interpolator (减速) ,linear_interpolator    (匀速),overshoot_interpolator (快速完成动画,超出再回到结束样式)

如果上述9个插值器无法满足需求,还可以自定义插值器:

c. 自定义插值器

  • 本质:根据动画的进度(0%-100%)计算出当前属性值改变的百分比
  • 具体使用:自定义插值器需要实现 Interpolator / TimeInterpolator接口 & 复写getInterpolation()方法

估值器(TypeEvaluator

  • 定义:一个接口
  • 作用:设置 属性值 从初始值过渡到结束值 的变化具体数值

b. 自定义估值器

  • 本质:根据 插值器计算出当前属性值改变的百分比 & 初始值 & 结束值 来计算 当前属性具体值

  两者区别:

  1. 插值器(Interpolator)决定 值 的变化模式(匀速、加速blabla)
  2. 估值器(TypeEvaluator)决定 值 的具体变化数值

3.2 属性动画两个核心类: 

  •  ValueAnimator类 与 ObjectAnimator类

   ValueAnimator类

  • 实现动画的原理:通过不断控制 值 的变化,再不断 手动 赋给对象的属性,从而实现动画效果。

 ValueAnimator类中有3个重要方法:

  1. ValueAnimator.ofInt(int values): (作用:将初始值 以整型数值的形式 过渡到结束值)
  2. ValueAnimator.ofFloat(float values):(作用:将初始值 以浮点型数值的形式 过渡到结束值)
  3. ValueAnimator.ofObject(int values): (作用:将初始值 以对象的形式 过渡到结束值)

  以 ValueAnimator.ofInt为例  

  • 实现的动画效果:按钮的宽度从 150px 放大到 500px

   

   ObjectAnimator类

  • 实现动画的原理:通过不断控制 值 的变化,再不断 自动 赋给对象的属性,从而实现动画效果。是 直接对象属性进行操作;
  • 继承自ValueAnimator类,即底层的动画实现机制是基于ValueAnimator
  • 如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的set() & get()

    由于 系统实现的四种动画Alpha,Translation,Scale,Rotation 对应属性set&get(),所以我们直接可以用使用:

   如下:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view,"rotation",0f,180f,0f);
objectAnimator.setDuration(1000);
objectAnimator.setRepeatCount(3);
objectAnimator.start();

4. 动画开源库--- Lottie

4.1 Lottie简介 :

  •   Lottie 是Airbnb公司开源的一个面向 iOS、Android、React Native 的动画库, 能分析 Adobe After Effects 导出的动画并且能让原生 App 像使用静态素材一样使用这些动画,完美实现动画效果。

4.2  环境搭建 :

  • Adobe After Effects 
  • Bodymovin插件

     

4.3  兼容性

  • Web : 调用Bodymovin提供的js库 — bodymovin.js
  • Android :支持API 16及以上
  • iOS/MacOS  :支持iOS 8+和MacOS 10.10+
  • React Native : 要求Android支持库版本为26,即compileSdkVersion 26

4.4  Lottie动画库使用

  •  compile 'com.airbnb.android:lottie:2.1.0'   引入依赖
  • 将我们所需要的动画文件XX.json保存在app/src/main/assets文件里
  • 布局中添加 LottieAnimationView :

     

  •    代码中运行:

        animationView.setAnimation("love.json");
        animationView.loop(true);
        animationView.playAnimation();

  •     运行效果如下:

   

 

4.5 文件校验 及 官方社区精选动画下载

  • 在线校验:针对UE提供没有图片资源的JSON动画文件校验。

    https://www.lottiefiles.com/preview

4.6、解析绘制

4.7、性能

  1. 在未开启硬件加速的情况下,帧率、内存,CPU都比属性动画差,开启硬件加速后,性能差不多。
  2. 如果没有遮罩、阴影和蒙版,那么性能和内存非常好,没有bitmap创建,大部分操作都是简单的cavas绘制。

以上是关于Lsposed 技术原理探讨 && 基本安装使用的主要内容,如果未能解决你的问题,请参考以下文章

Android 动画技术总结分享

Java基础——HashMap设计原理&实现分析

Java基础——HashMap设计原理&实现分析

Java基础——HashMap设计原理&实现分析

面具更新riru-lsposed为啥下载失败

Cydia安装插件时报错code(1)和code(2)&解决方法探讨