ValueAnimator 和 ObjectAnimator 不能混用

本文介绍了一个使用ObjectAnimator实现动画效果的应用案例,在特定设备及Android版本下出现崩溃的问题及其解决方案。通过对代码进行调整,替换成ValueAnimator成功避免了在三星A7等设备上的异常。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在某个项目中,我需要一个动画。代码如下

  ObjectAnimator animator = new ObjectAnimator();
            float begin = layoutShare.getTranslationY();
            animator.setFloatValues(begin, 0);
            animator.setDuration(300);
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    layoutMask.setVisibility(View.VISIBLE);
                }
            });
            animator.addUpdateListener(animation -> {
                layoutShare.setTranslationY((Float) animation.getAnimatedValue());
            });
            animator.start();

在我的手机中运行完美。但是客户反映三星A7 会发生崩溃现象。日志如下

06-26 18:28:55.184: E/AndroidRuntime(12220): java.lang.NullPointerException
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.animation.PropertyValuesHolder.setupSetterAndGetter(PropertyValuesHolder.java:505)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.animation.ObjectAnimator.initAnimation(ObjectAnimator.java:487)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.animation.ValueAnimator.setCurrentPlayTime(ValueAnimator.java:517)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.animation.ValueAnimator.start(ValueAnimator.java:936)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.animation.ValueAnimator.start(ValueAnimator.java:946)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.animation.ObjectAnimator.start(ObjectAnimator.java:465)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at com.trs.myrb.activity.CustomShareActivity.lambda$beginAnimator$3$CustomShareActivity(CustomShareActivity.java:104)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at com.trs.myrb.activity.CustomShareActivity$$Lambda$2.run(Unknown Source)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.os.Handler.handleCallback(Handler.java:733)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.os.Handler.dispatchMessage(Handler.java:95)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.os.Looper.loop(Looper.java:146)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at android.app.ActivityThread.main(ActivityThread.java:5756)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at java.lang.reflect.Method.invokeNative(Native Method)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at java.lang.reflect.Method.invoke(Method.java:515)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)
06-26 18:28:55.184: E/AndroidRuntime(12220):    at dalvik.system.NativeStart.main(Native Method)

通过源码定位到Android 4.4.4 中PropertyValuesHolder的 505行
这里写图片描述

问题就出现在这个target ,这是ObjectAnimator 特有的。ObjectAnimator是ValueAnimator的子类,他本身就已经包含了时间引擎和值计算,所以它拥有为对象的某个属性设置动画的功能。这使得为任何对象设置动画更加的容易。你不再需要实现 ValueAnimator.AnimatorUpdateListener接口,因为ObjectAnimator动画自己会自动更新相应的属性值。
ObjectAnimator的实例和ValueAnimator是类似的,但是你需要描叙该对象,需要设置动画的属性的名字(一个字符串),以及动画属性值的变化范围:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, “alpha”, 0f, 1f);
anim.setDuration(1000);
anim.start();
而在我的代码中,我是通过AnimatorUpdateListener自己更新的属性。所以没有设置target。也不需要设置target。于是我把ObjectAnimator 换成 ValueAnimator 就不能在Android4.4.4 中崩溃了。

至于为什么在我的手机上没有崩溃(Android 8.0) 看源码就知道了。添加了对空的检查。

通过这件事我明白,写代码脑子一定要清晰。对每一个类的认识要深刻。否则的话。也许效果出来了,但那不是你写的好。而是别人代码比较健壮。最后的坑还是得自己填。

<think>嗯,用户问的是在C#中,awaitResult能不能混用。之前他们提到过使用HttpClient时用await会卡住,但用.Result却能正常得到结果,现在又进一步问这两个是否可以混用。首先,我需要回顾之前的回答,确保这次回答保持一致性,同时更具体地针对混用的问题。 用户可能是在实际开发中遇到了混用的情况,导致了一些问题,比如死锁或者性能问题。他们可能没有完全理解为什么有时候用Result有效,而用await却不行,或者在不同上下文中混用两者的后果。需要解释清楚为什么混用会导致问题,尤其是同步上下文中的死锁问题。 接下来,我需要确认用户的理解程度。他们可能已经知道await是异步的,而Result是同步的,但可能不清楚在同一个调用链中混用两者的风险。比如,在异步方法中调用另一个异步方法时,如果中间用了Result,就会阻塞线程,导致死锁,尤其是在有同步上下文的环境里,比如UI线程或旧版ASP.NET。 需要举例说明不同的混用场景,比如直接在同步方法中调用异步方法并阻塞,或者在异步方法中不必要地使用Result。同时,要强调正确的做法是始终使用async/await链式调用,避免阻塞,并适当使用ConfigureAwait(false)来避免上下文恢复带来的问题。 还要注意用户可能的误区,比如认为在控制台应用中没有问题,但其实控制台应用的Main方法如果是同步的,调用异步方法时使用Wait()或Result也可能导致死锁,特别是当异步操作内部没有正确配置ConfigureAwait(false)时。 此外,需要强调混用可能导致的稳定性问题,比如死锁难以调试,或者性能下降,甚至应用程序无响应。给出具体的解决方案,如重构代码为全异步,使用ConfigureAwait,以及正确设计入口方法等。 最后,总结关键点,帮助用户记住核心原则,避免混用,并推荐最佳实践。确保回答结构清晰,分点说明,用例子辅助理解,并指出潜在的风险解决方法。</think>在 C# 中,`await` `.Result`(或 `.Wait()`)**不能混用**,尤其是在需要同步上下文(如 UI 线程或旧版 ASP.NET 请求上下文)的场景中。这种混用是异步编程中常见的死锁根源。以下是详细分析: --- ### 一、为什么不能混用? #### 1. **同步上下文(SynchronizationContext)的陷阱** - **`await` 的默认行为**:当使用 `await` 时,默认会尝试在原始上下文(如 UI 线程)中恢复后续代码的执行。 - **`.Result` 的同步阻塞**:调用 `.Result` 会强制**阻塞当前线程**,直到异步任务完成。 #### 2. **死锁的形成条件** - 如果以下条件同时满足,必然发生死锁: 1. 代码运行在**有同步上下文的线程**(如 UI 线程、旧版 ASP.NET 请求线程)。 2. 在同步上下文中**调用异步方法并直接使用 `.Result` 或 `.Wait()` 阻塞**。 3. 异步方法内部**未使用 `ConfigureAwait(false)`**,导致它尝试返回到原始同步上下文。 ```csharp // 典型死锁场景:在 UI 线程中混用 await .Result public void Button_Click(object sender, EventArgs e) { // 错误:在 UI 线程同步阻塞异步任务! var data = GetDataAsync().Result; // 死锁! } public async Task<string> GetDataAsync() { using (var client = new HttpClient()) { // 默认尝试返回到 UI 线程 → 但 UI 线程已被阻塞! return await client.GetStringAsync("https://example.com"); } } ``` #### 3. **死锁的详细流程** - **主线程(UI 线程)**:调用 `GetDataAsync().Result`,阻塞自己。 - **异步任务**:在后台线程池中执行 `GetStringAsync`。 - **任务完成时**:尝试通过 `await` 返回到原始 UI 线程继续执行后续代码。 - **矛盾点**:UI 线程已被 `.Result` 阻塞,无法处理返回请求 → 永远等待,形成死锁。 --- ### 二、哪些场景可以“混用”? #### 1. **无同步上下文的场景** - **控制台应用程序**(默认无同步上下文): ```csharp static void Main(string[] args) { var data = GetDataAsync().Result; // 可能不会死锁,但仍是错误写法! } ``` - **风险**:如果异步方法内部依赖同步上下文(如更新 UI),仍可能出问题。 - **正确做法**:使用 `async Task Main` 并 `await`。 - **线程池线程**: ```csharp Task.Run(() => { var data = GetDataAsync().Result; // 可能不会死锁,但不推荐! }); ``` #### 2. **异步方法内部已使用 `ConfigureAwait(false)`** - 如果异步方法显式放弃上下文恢复,可避免死锁: ```csharp public async Task<string> GetDataAsync() { using (var client = new HttpClient()) { return await client.GetStringAsync("https://example.com") .ConfigureAwait(false); // 禁止返回到原始上下文 } } ``` - **此时**:即使外部调用 `.Result`,异步任务完成后不会尝试返回原始线程 → 避免死锁。 --- ### 三、正确实践原则 #### 1. **全异步化调用链** - 从代码入口(如事件处理函数)开始,全程使用 `async/await`,禁止混用 `.Result`: ```csharp // 正确写法:全异步化 public async void Button_Click(object sender, EventArgs e) { var data = await GetDataAsync(); // 非阻塞等待 } ``` #### 2. **库代码强制使用 `ConfigureAwait(false)`** - 在通用库代码中,除非明确需要上下文,否则始终添加 `ConfigureAwait(false)`: ```csharp public async Task<string> ReadDataAsync() { return await SomeAsyncOperation() .ConfigureAwait(false); // 明确放弃上下文恢复 } ``` #### 3. **禁止在同步方法中调用异步代码** - 如果某个方法本身是同步的,不要在其中调用异步方法并阻塞: - **错误**: ```csharp public string GetData() { return GetDataAsync().Result; // 高风险! } ``` - **正确**: ```csharp public async Task<string> GetDataAsync() { // 异步实现 } ``` --- ### 四、常见问题解答 #### 1. **为什么控制台程序有时可以混用?** - 控制台程序默认无同步上下文,因此 `await` 不会尝试返回到特定线程。 - **但风险仍在**:如果异步方法内部依赖同步上下文(如 WPF 的 `Dispatcher`),仍可能死锁。 #### 2. **为什么旧版 ASP.NET 容易死锁?** - 旧版 ASP.NET 使用 `SynchronizationContext` 管理请求上下文,混用 `await` `.Result` 会阻塞请求线程,导致线程饥饿死锁。 #### 3. **如何调试死锁?** - 检查调用堆栈:主线程是否在等待异步任务,而异步任务在等待主线程? - 使用 `Debugger.Break()` 或分析线程状态。 --- ### 五、总结 - **禁止混用**:`await` `.Result` 在同步上下文中混用必然导致死锁。 - **全异步化**:从代码入口开始,全程使用 `async/await`。 - **库代码规范**:始终使用 `ConfigureAwait(false)`,除非明确需要上下文。 - **同步方法改造**:将同步方法逐步重构为异步方法,避免阻塞调用。 遵循这些原则,可彻底避免死锁并编写高性能、可维护的异步代码。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值