每日一博 | 明修

1,347次阅读
没有评论

作者:vivo 互联网大前端团队- Zhao Kaiping

本文从一例业务中遇到的问题出发,以FLAG_ACTIVITY_NEW_TASK这一flag作为切入点,带大家探究Activity启动前的一项重要的工作——栈校验。

文中列举一系列业务中可能遇到的异常状况,详细描述了使用FLAG_ACTIVITY_NEW_TASK时可能遇到的“坑”,并从源码中探究其根源。只有合理使用flag、launchMode,才能避免因为栈机制的特殊性,导致一系列与预期不符的启动问题。

一、问题及背景

应用间相互联动、相互跳转,是实现系统整体性、体验一致性的重要手段,也是最简单的一种方法。

当我们用最常用的方法去startActivity时,竟也会遇到失败的情况。在真实业务中,就遇到了这样一例异常:用户点击某个按钮时,想要“简简单单”跳转另一个应用,却没有任何反应。

经验丰富的你,脑海中是否涌现出了各种猜想:是不是目标Activity甚至目标App不存在?是不是目标Activty没有对外开放?是不是有权限的限制或者跳转的action/uri错了……

真实的原因被flag、launchMode、Intent等特性层层藏匿,可能超出你此时的思考。

本文将从源码出发,探究前因后果,展开讲讲在startActivity()真正准备启动一个Activity前,需要经过哪些“磨难”,怎样有据可依地解决由栈问题导致的启动异常。

1.1 业务中遇到的问题

业务中的场景是这样的,存在A、B、C三个应用。

(1)从应用A-Activity1跳转至应用B-Activity2;

(2)应用B-Activity2继续跳转到应用C-Activity3;

(3)C内某个按钮,会再次跳转B-Activity2,但点击后没有任何反应。如果不经过前面A到B的跳转,C直接跳到B是可以的。

每日一博 | 明修

1.2 问题代码

3个Activity的Androidmanifest配置如下,均可通过各自的action拉起,launchMode均为标准模式。

<!--应用A--> <activity android:name=".Activity1" android:exported="true"> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_A_PAGE1" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!--应用B--> <activity android:name=".Activity2" android:exported="true"> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_B_PAGE2" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> <!--应用C--> <activity android:name=".Activity3" android:exported="true"> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_C_PAGE3" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>

A-1到B-2的代码,指定flag为

FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);}

B-2到C-3的代码,未指定flag

private void jumpTo_C_Activity3_ByAction_NoTask() { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_C_PAGE3"); startActivity(intent);}

C-3到B-2的代码,与A-1到B-2的完全一致,指定flag为 FLAG_ACTIVITY_NEW_TASK

private void jumpTo_B_Activity2_ByAction_NewTask() { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);}

1.3 代码初步分析

仔细查看问题代码,在实现上非常简单,有两个特征:

(1)如果直接通过C-3跳B-2,没有任何问题,但A-1已经跳过B-2后,C-3就失败了。

(2)在A-1和C-3跳到B-2时,都设置了flag为FLAG_ACTIVITY_NEW_TASK。

依据经验,我们推测与栈有关,尝试将跳转前栈的状态打印出来,如下图。

每日一博 | 明修

由于A-1跳到B-2时设置了FLAG_ACTIVITY_NEW_TASK,B-2跳到C-3时未设置,所以1在独立栈中,2、3在另一个栈中。示意如下图。

每日一博 | 明修

C-3跳转B-2一般有3种可能的预期,如下图:预想1,新建一个Task,在新Task中启动一个B-2;预想2,复用已经存在的B-2;预想3,在已有Task中新建一个实例B-2。

每日一博 | 明修

但实际上3种预期都没有实现,所有Activity的任何声明周期都没有变化,界面始终停留在C-3。

看一下FLAG_ACTIVITY_NEW_TASK的官方注释和代码注释,如下图:

每日一博 | 明修

每日一博 | 明修

重点关注这一段:

When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in.

使用此flag时,如果你正在启动的Activity已经在一个Task中运行,那么一个新Activity不会被启动;相反,当前Task将简单地显示在界面的前面,并显示其最后的状态。

——显然,官方文档与代码注释的表述与我们的异常现象是一致的,目标Activity2已经在Task中存在,则不会被启动;Task直接显示在前面,并展示最后的状态。由于目标Activty3就是来源Activity3,所以页面没有任何变化。

看起来官方还是很靠谱的,但实际效果真的能一直与官方描述一致吗?我们通过几个场景来看一下。

二、场景拓展与验证

2.1 场景拓展

在笔者依据官方描述进行调整、复现的过程中,发现了几个比较有意思的场景。

PS:上面业务的案例中,B-2和C-3在不同应用内,又在相同的Task内,但实际上是否是同一个应用,对结果的影响并不大。为了避免不同应用和不同Task造成阅读混乱,同一个栈的跳转,我们都在本应用内进行,故业务中的场景等价于下面的【场景0】

【场景0】把业务中B-2到C-3的应用间跳转改为B-2到B-3的应用内跳转

// B-2跳转B-3public static void jumpTo_B_3_ByAction_Null(Context context) { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3"); context.startActivity(intent);}

如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,最终设置NEW_TASK想跳转B-2。虽然跳C-3改为了跳B-3,但与之前问题的表现一致,没有反应,停留在B-3。

每日一博 | 明修

有的读者会指出这样的问题:如果同一个应用内使用NEW_TASK跳转,而不指定目标的taskAffinity属性,实际是无法在新Task中启动的。请大家忽略该问题,可以认为笔者的操作是已经加了taskAffinity的,这对最终结果并没有影响。

【场景1】如果目标Task和来源Task不是同一个,情况是否会如官方文档所说复用已有的Task并展示最近状态?我们改为B-3启动一个新Task的新Activity C-4,再通过C-4跳回B-2

// B-3跳转C-4public static void jumpTo_C_4_ByAction_New(Context context) { Intent intent = new Intent("com.zkp.task.ACTION_TO_C_PAGE4"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);}// C-4跳转B-2public static void jumpTo_B_2_ByAction_New(Context context) { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE2"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);}

如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-2。

每日一博 | 明修

预想的结果是:不会跳到B-2,而是跳到它所在Task的顶层B-3。

实际的结果是:与预期一致,确实是跳到了B-3。

【场景2】把场景1稍做修改:C-4到B-2时,我们不通过action来跳,改为通过setClassName跳转

// C-4跳转B-2public static void jumpTo_B_2_ByPath_New(Context context) { Intent intent = new Intent(); intent.setClassName("com.zkp.b", "com.zkp.b.Activity2"); // 直接设置classname,不通过action intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);}

如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-2。

每日一博 | 明修

预想的结果是:与场景0一致,会跳到B-2所在Task的已有顶层B-3。

实际的结果是:在已有的Task2中,产生了一个新的B-2实例。

仅仅是改变了一下重新跳转B-2的方式,效果就完全不一样了!这与官方文档中提到该flag与”singleTask” launchMode值产生的行为并不一致!

【场景3】把场景1再做修改:这次C-4不跳栈底的B-2,改为跳转B-3,且还是通过action方式。

// C-4跳转B-3public static void jumpTo_B_3_ByAction_New(Context context) { Intent intent = new Intent(); intent.setAction("com.zkp.task.ACTION_TO_B_PAGE3"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent);}

如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-3。

每日一博 | 明修

预想的结果是:与场景0一致,会跳到B-2所在Task的顶层B-3。

实际的结果是:在已有的Task2中,产生了一个新的B-3实例。

不是说好的,Activity已经存在时,展示其所在Task的最新状态吗?明明Task2中已经有了B-3,并没有直接展示它,而是生成了新的B-3实例。

【场景4】既然Activity没有被复用,那Task一定会被复用吗?把场景3稍做修改,直接给B-3指定一个单独的affinity。

<activity android:name=".Activity3" android:exported="true" android:taskAffinity="b3.task"><!--指定了亲和性标识--> <intent-filter> <action android:name="com.zkp.task.ACTION_TO_B_PAGE3" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter></activity>

如下图,A-1设置NEW_TASK跳转B-2,再跳转B-3,再设置NEW_TASK跳转C-4,最终设置NEW_TASK想跳转B-3。

每日一博 | 明修

——这次,连Task也不会再被复用了……Activity3在一个新的栈中被实例化了。

再回看官方的注释,就会显得非常不准确,甚至会让开发者对该部分的认知产生严重错误!稍微改变过程中的某个毫无关联的属性(如跳转目标、跳转方式……),就会产生很大差异。

在看flag相关注释时,我们要树立一个意识:Task和Activity跳转的实际效果,是launchMode、taskAffinity、跳转方式、Activity在Task中的层级等属性综合作用的结果,不要相信“一面之词”。

回到问题本身,究竟是哪些原因造就了上面的不同效果呢?只有源码最值得信赖了。

三、场景分析与源码探索

本文以Android 12.0源码为基础,进行探究。上述场景在不同Android版本上的表现是一致的。

3.1 源码调试注意事项

源码的调试方法,许多文章已经有了详细的教学,本文不再赘述。此处只简单总结其中需要注意的事项

  1. 下载模拟器时,不要使用Google Play版本,该版本类似user版本,无法选择system_process进程进行断点。

  2. 即使是Google官方模拟器和源码,在断点时,也会有行数严重不对应的情况(比如:模拟器实际会运行到方法A,但在源码中打断点时,实际不能定位到方法A的对应行数),该问题并没有很好的处理方法,只能尽量规避,如使模拟器版本与源码版本保持一致、多打一些断点增加关键行数被定位到的几率。

3.2 初步断点,明确启动结果

以【场景0】为例,我们初步确认一下,为什么B-3跳转B-2会无反应,系统是否告知了原因。

3.2.1 明确启动结果及其来源

在Android源码的断点调试中,常见的有两类进程:应用进程和system_process进程。

在应用进程中,我们能获取到应用启动结果的状态码result,这个result用来告诉我们启动是否成功。涉及堆栈如下图(标记1)所示:

Activity类::startActivity() 

→ startActivityForResult() 

→ Instrumentation类::execStartActivity(),

返回值result则是ATMS

(ActivityTaskManagerService)执行的结果。

每日一博 | 明修

如上图(标记2)标注,ATMS类::startActivity()方法,返回了result=3。

在system_process进程中,我们看一下这个result=3是怎样被赋值的。略去详细断点步骤,实际堆栈如下图(标注1)所示:

ATMS类::startActivity() → startActivityAsUser() 

→ ActivityStarter类::execute() 

→ executeRequest() 

→ startActivityUnchecked() 

→ startActivityInner() 

→ recycleTask(),在recycleTask()中返回了结果。

每日一博 | 明修

如上图(标注2)所示,result在mMovedToFront=false时被赋值,即result=START_DELIVERED_TO_TOP=3,而START_SUCCESS=0才代表创建成功。

看一下源码中对START_DELIVERED_TO_TOP的说明,如下图:

每日一博 | 明修

Result for IActivityManaqer.startActivity: activity wasn’t really started, but the given Intent was given to the existing top activity.

(IActivityManaqer.startActivityActivity的结果:Activity并未真正启动,但给定的Intent已提供给现有的顶层Activity。)

“Activity并未真正启动”——是的,因为可以复用

“给定的Intent已提供给现有的顶层Activity”——实际没有,顶层Activity3并没有收到任何回调,onNewIntent()未执行,甚至尝试通过Intent::putExtra()传入新的参数,Activity3也没有收到。官方文档又带给了我们一个疑问点?我们把这个问题记录下来,在后面分析。

满足什么条件,才会造成

START_DELIVERED_TO_TOP的结果呢?笔者的思路是,通过与正常启动流程对比,找出差异点。

3.3 过程断点,探索启动流程

一般来说,在定位问题时,我们习惯通过结果反推原因,但反推的过程只能关注到与问题强关联的代码分支,并不能能使我们很好地了解全貌。

所以,本节内容我们通过顺序阅读的方法,正向介绍startActivity过程中与上述【场景01234】强相关的逻辑。再次简述一下:

  1. 【场景0】同一个Task内,从顶部B-3跳转B-2——停留在B-3

  2. 【场景1】从另一个Task内的C-4,跳转B-2——跳转到B-3

  3. 【场景2】把场景1中,C-4跳转B-2的方式改为setClassName()——创建新B-2实例

  4. 【场景3】把场景1中,C-4跳转B-2改为跳转B-3——创建新B-3实例

  5. 【场景4】给场景3中的B-3,指定taskAffinity——创建新Task和新B-3实例

3.3.1 流程源码概览

源码中,整个启动流程很长,涉及的方法和逻辑也很多,为了便于大家理清方法调用顺序,方便后续内容的阅读,笔者将本文涉及到的关键类及方法调用关系整理如下。

后续阅读中如果不清楚调用关系,可以返回这里查看:

// ActivityStarter.java ActivityStarter::execute() { executeRequest(intent) { startActivityUnchecked() { startActivityInner(); } } ActivityStarter::startActivityInner() { setInitialState(); computeLaunchingTaskFlags(); Task targetTask = getReusableTask(){ findTask(); } ActivityRecord targetTaskTop = targetTask.getTopNonFinishingActivity(); if (targetTaskTop != null) { startResult = recycleTask() { setTargetRootTaskIfNeeded(); complyActivityFlags(); if (mAddingToTask) { return START_SUCCESS; //【场景2】【场景3】从recycleTask()返回 } resumeFocusedTasksTopActivities() return mMovedToFront ? START_TASK_TO_FRONT : START_DELIVERED_TO_TOP;//【场景1】【场景0】从recycleTask()返回 } } else { mAddingToTask = true; } if (startResult != START_SUCCESS) { return startResult;//【场景1】【场景0】从startActivityInner()返回 } deliverToCurrentTopIfNeeded(); resumeFocusedTasksTopActivities(); return startResult; }

3.3.2 关键流程分析

(1)初始化

startActivityInner()是最主要的方法,如下列几张图所示,该方法会率先调用setInitialState(),初始化各类全局变量,并调用reset(),重置ActivityStarter中各种状态。

在此过程中,我们记下两个关键变量mMovedToFront和mAddingToTask,它们均在此被重置为false。

其中,mMovedToFront代表当Task可复用时,是否需要将目标Task移动到前台;mAddingToTask代表是否要将Activity加入到Task中。

每日一博 | 明修

每日一博 | 明修

每日一博 | 明修

(2)计算确认启动时的flag

该步骤会通过computeLaunchingTaskFlags()方法,根据launchMode、来源Activity的属性等进行初步计算,确认LaunchFlags。

此处重点处理来源Activity为空的各类场景,与我们上文中的几种场景无关,故不再展开讲解。

(3)获取可以复用的Task

该步骤通过调用getReusableTask()实现,用来查找有没有可以复用的Task。

先说结论:场景0123中,都能获取到可以复用的Task,而场景4中,未获取到可复用的Task。

为什么场景4不可以复用?我们看一下getReusableTask()的关键实现。

每日一博 | 明修

上图(标注1)中,putIntoExistingTask代表是否能放入已经存在的Task。当flag含有NEW_TASK且不含MULTIPLE_TASK时,或指定了singleInstance或singleTask的launchMode等条件,且没有指定Task或要求返回结果 时,场景01234均满足了条件。

然后,上图(标注2)通过findTask()查找可以复用的Task,并将过程中找到的栈顶Activity赋值给intentActivity。最终,上图(标注3)将intentActivity对应的Task作为结果。

findTask()是怎样查找哪个Task可以复用呢?

每日一博 | 明修

主要是确认两种结果mIdealRecord——“理想的ActivityRecord”  和 mCandidateRecord——”候选的ActivityRecord”,作为intentActivity,并取intentActivity对应的Task作为复用Task。

什么ActivityRecord才是理想或候选的ActivityRecord呢?

在mTmpFindTaskResult.process()中确认。

每日一博 | 明修

程序会将当前系统中所有的Task进行遍历,在每个Task中,进行如上图所示的工作——将Task的底部Activity realActivity与目标Activity cls进行对比。

场景012中,我们想跳转Activity2,即cls是Activity2,与Task底部的realActivity2相同,则将该Task顶部的Activity3 r作为“理想的Activity”;

场景3中,我们想跳转Activity3,即cls是Activity3,与Task底部的realActivity2不同,则进一步判断task底部Activity2与目标Activity3的栈亲和行,具有相同亲和性,则将Task的顶部Activity3作为“候选Activity”;

场景4中,所有条件都不满足,最终没能找到可复用的Task。在执行完getReusableTask()后将mAddingToTask赋值为true

由此,我们就能解释【场景4】中,新建了Task的现象。

(4)确定是否需要将目标Task移动到前台

如果存在可复用的Task,场景0123会执行recycleTask(),该方法中会相继进行几个操作:setTargetRootTaskIfNeeded()、

complyActivityFlags()。

首先,程序会执行

setTargetRootTaskIfNeeded(),用来确定是否需要将目标Task移动到前台,使用mMovedToFront作为标识。

每日一博 | 明修

每日一博 | 明修

在【场景123】中,来源Task和目标Task是不同的,differentTopTask为true,再经过一系列Task属性对比,能够得出mMovedToFront为true;

而场景0中,来源Task和目标Task相同,differentTopTask为false,mMovedToFront保持初始的false。

由此,我们就能解释【场景0】中,Task不会发生切换的现象。

(5)通过对比flag、Intent、Component等确认是否要将Activity加入到Task中

还是在【场景0123】中,recycleTask()会继续执行complyActivityFlags(),用来确认是否要将Activity加入到Task中,使用mAddingToTask作为标识。

该方法会对FLAG_ACTIVITY_NEW_TASK、

FLAG_ACTIVITY_CLEAR_TASK、

FLAG_ACTIVITY_CLEAR_TOP等诸多flag、Intent信息进行一系列判断。

每日一博 | 明修

上图(标注1)中,会先判断后续是否需要重置Task,resetTask,判断条件则是FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,显然,场景0123的resetTask都为false。继续执行。

接着,会有多种条件判断按顺序执行。

在【场景3】中,目标Component(mActivityComponent)是B-3,目标Task的realActivity则是B-2,两者不相同,进入了resetTask相关的判断(标注2)。

之前resetTask已经是false,故【场景3】的mAddingToTask脱离原始值,被置为true。

在【场景012】中,相对比的两个Activity都是B-2(标注3),可以进入下一级判断——isSameIntentFilter()。

每日一博 | 明修

每日一博 | 明修

每日一博 | 明修

这一步判断的内容就很明显了,目标Activity2的已有Intent 与 新的Intent做对比。很显然,场景2中由于改为了setClassName跳转,Intent自然不一样了。

故【场景2】的mAddingToTask脱离原始值,被置为true。

总结看一下:

【场景123】的mMovedToFront最先被置为true,而【场景0】经历重重考验,保持初始值为false。

——这意味着当有可复用Task时,【场景0】不需要把Task切换到前列;【场景123】需要切换到目标Task。

【场景234】的mAddingToTask分别在不同阶段被置为true,而【场景01】,始终保持初始值false。

——这意味着,【场景234】需要将Activity加入到Task中,而【场景01】不再需要。

(6)实际启动Activity或直接返回结果

被启动的各个Activity会通过resumeFocusedTasksTopActivities()等一系列操作,开始真正的启动与生命周期的调用。

我们关于上述各个场景的探索已经得到答案,后续流程便不再关注。

四、问题修复及遗留问题解答

4.1 问题修复

既然前面总结了这么多必要条件,我们只需要破坏其中的某些条件,就可以修复业务中遇到的问题了,简单列举几个的方案。

  • 方案一:修改flag。B-3跳转B-2时,增加FLAG_ACTIVITY_CLEAR_TASK或FLAG_ACTIVITY_CLEAR_TOP,或者直接不设置flag。经验证可行。

  • 方案二:修改intent属性,即【场景2】。A-1通过action方式隐式跳转B-2,则B-3可以通过setClassName方式,或修改action内属性的方式跳转B-2。经验证可行。

  • 方案三:提前移除B-2。B-2跳转B-3时,finish掉B-2。需要注意的是,finish()要在startActivity()之前执行,以避免遗留的ActivityRecord和Intent信息对后续跳转的影响。尤其是当你把B-2作为自己应用的deeplink分发Activity时,更值得警惕。

4.2 遗留问题

还记得我们在文章开端的某个疑惑吗,为什么没有回调onNewIntent()?

onNewIntent() 会通过deliverNewIntent()触发,而deliverNewIntent()仅通过以下两个方法调用。

每日一博 | 明修

complyActivityFlags()就是上文3.3.1.5中我们着重探讨的方法,可以发现complyActivityFlags()中所有可能调用deliverNewIntent()的条件均被完美避开了。

而deliverToCurrentTopIfNeeded()方法则如下图所示。

每日一博 | 明修

mLaunchFlags和mLaunchMode,无法满足条件,导致dontStart为false,无缘

deliverNewIntent()。

至此,onNewIntent()的问题得到解答。

五、结语

通过一系列场景假设,我们发现了许多出乎意料的现象:

  1. 文档提到FLAG_ACTIVITY_NEW_TASK等价于singleTask,与事实并不完全如此,只有与其他flag搭配才能达到相似的效果。这一flag的注释非常片面,甚至会引发误解,单一因素无法决定整体表现。

  2. 官方文档提到

    START_DELIVERED_TO_TOP会将新的Intent传递给顶层Activity,但事实上,并不是每一种START_DELIVERED_TO_TOP都会把新的Intent重新分发。

  3. 同一个栈底Activity,前后两次都通过action或都通过setClassName跳转到时,第二次跳转竟然会失败,而两次用不同方式跳转时,则会成功。

  4. 单纯使用FLAG_ACTIVITY_NEW_TASK时,跳栈底Activity和跳同栈内其他Activity的效果大相径庭。

业务中遇到的问题,归根结底就是对Android栈机制不够了解造成的。

在面对栈相关的编码时,开发者务必要想清楚,承担新开应用栈的Activty在应用全局承担怎样的使命,要对Task历史、flag属性、launchMode属性、Intent内容等全面评估,谨慎参考官方文档,才能避免栈陷阱,达成理想可靠的效果。

END

猜你喜欢

本文分享自微信公众号 – vivo互联网技术(vivoVMIC)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

Read More 

正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)

文心AIGC

2023 年 4 月
 12
3456789
10111213141516
17181920212223
24252627282930
文心AIGC
文心AIGC
人工智能ChatGPT,AIGC指利用人工智能技术来生成内容,其中包括文字、语音、代码、图像、视频、机器人动作等等。被认为是继PGC、UGC之后的新型内容创作方式。AIGC作为元宇宙的新方向,近几年迭代速度呈现指数级爆发,谷歌、Meta、百度等平台型巨头持续布局
文章搜索
热门文章
潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026

潞晨尤洋:日常办公没必要上私有模型,这三类企业才需要 | MEET2026 Jay 2025-12-22 09...
面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25

面向「空天具身智能」,北航团队提出星座规划新基准丨NeurIPS’25 鹭羽 2025-12-13 22:37...
商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1

商汤Seko2.0重磅发布,合作短剧登顶抖音AI短剧榜No.1 十三 2025-12-15 14:13:14 ...
跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026 一水 2025-1...
10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了 一水 2025-12-12 13:56:19 ...
最新评论
ufabet ufabet มีเกมให้เลือกเล่นมากมาย: เกมเดิมพันหลากหลาย ครบทุกค่ายดัง
tornado crypto mixer tornado crypto mixer Discover the power of privacy with TornadoCash! Learn how this decentralized mixer ensures your transactions remain confidential.
ดูบอลสด ดูบอลสด Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Pretty! This has been a really wonderful post. Many thanks for providing these details.
ดูบอลสด ดูบอลสด Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
Obrazy Sztuka Nowoczesna Obrazy Sztuka Nowoczesna Thank you for this wonderful contribution to the topic. Your ability to explain complex ideas simply is admirable.
ufabet ufabet Hi there to all, for the reason that I am genuinely keen of reading this website’s post to be updated on a regular basis. It carries pleasant stuff.
ufabet ufabet You’re so awesome! I don’t believe I have read a single thing like that before. So great to find someone with some original thoughts on this topic. Really.. thank you for starting this up. This website is something that is needed on the internet, someone with a little originality!
ufabet ufabet Very well presented. Every quote was awesome and thanks for sharing the content. Keep sharing and keep motivating others.
热评文章
跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026

跳过“逐字生成”!蚂蚁集团赵俊博:扩散模型让我们能直接修改Token | MEET2026 一水 2025-1...
10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了

10亿美元OpenAI股权兑换迪士尼版权!米老鼠救Sora来了 一水 2025-12-12 13:56:19 ...
IDC MarketScape: 容联云位居“中国AI赋能的联络中心”领导者类别

IDC MarketScape: 容联云位居“中国AI赋能的联络中心”领导者类别

IDC MarketScape: 容联云位居“中国AI赋能的联络中心”领导者类别 量子位的朋友们 2025-1...