欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > Android U 多任务启动分屏——system_server流程(更新中)

Android U 多任务启动分屏——system_server流程(更新中)

2025/2/24 19:26:15 来源:https://blog.csdn.net/yimelancholy/article/details/144694952  浏览:    关键词:Android U 多任务启动分屏——system_server流程(更新中)

前文

Android U 多任务启动分屏——SystemUI流程
前文说到Transitions的startTransition方法中,通过mOrganizer.startNewTransition(type, wct);提交WindowContainerTransaction相关事务到system_server侧,继续跟踪其流程。

system_server侧分屏处理流程

systemui跨进程通信到system_server

代码路径:frameworks/base/core/java/android/window/WindowOrganizer.java

    /*** Starts a new transition, don't use this to start an already created one.* @param type The type of the transition. This is ignored if a transitionToken is provided.* @param t The set of window operations that are part of this transition.* @return A token identifying the transition. This will be the same as transitionToken if it*         was provided.* @hide*/@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)@NonNullpublic IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {try {return getWindowOrganizerController().startNewTransition(type, t);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}static IWindowOrganizerController getWindowOrganizerController() {return IWindowOrganizerControllerSingleton.get();}

这里可以看出getWindowOrganizerController()就是获取IWindowOrganizerController对象,调用其startNewTransition(type, t)方法,其中参数type为systemui侧传递的TRANSIT_TO_FRONT(值为3),t则是systemui侧传递的WindowContainerTransaction对象。

代码路径:frameworks/base/core/java/android/window/IWindowOrganizerController.aidl

interface IWindowOrganizerController {....../*** Starts a new transition.* @param type The transition type.* @param t Operations that are part of the transition.* @return a token representing the transition.*/IBinder startNewTransition(int type, in @nullable WindowContainerTransaction t);

找到其aidl接口,接下来找到其实现类

/*** Server side implementation for the interface for organizing windows* @see android.window.WindowOrganizer*/
class WindowOrganizerController extends IWindowOrganizerController.Stubimplements BLASTSyncEngine.TransactionReadyListener {

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java

    @Overridepublic IBinder startNewTransition(int type, @Nullable WindowContainerTransaction t) {return startTransition(type, null /* transitionToken */, t);}

最终调用到WindowOrganizerController的startNewTransition方法,该方法就是调用一个startTransition方法,这个方法中传递了type(值为TRANSIT_TO_FRONT,即3)、transitionToken(值为null)以及WindowContainerTransaction对象。

处理动画并提交事务

    private IBinder startTransition(@WindowManager.TransitionType int type,@Nullable IBinder transitionToken, @Nullable WindowContainerTransaction t) {//检查MANAGE_ACTIVITY_TASKS权限enforceTaskPermission("startTransition()");//获取调用方法的pid和uidfinal CallerInfo caller = new CallerInfo();final long ident = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {//transitionToken为空,因此transition为空Transition transition = Transition.fromBinder(transitionToken);//transition为空不满足条件,因此不走该逻辑if (mTransitionController.getTransitionPlayer() == null && transition == null) {Slog.w(TAG, "Using shell transitions API for legacy transitions.");if (t == null) {throw new IllegalArgumentException("Can't use legacy transitions in"+ " compatibility mode with no WCT.");}//如果mTransitionController.getTransitionPlayer()和transition//均为空,则直接调用applyTransaction方法applyTransaction(t, -1 /* syncId */, null, caller);return null;}//判断当前传递过来的WindowContainerTransaction对象t是否为空//不为空则赋值给wct,为空则重新创建一个WindowContainerTransaction对象。final WindowContainerTransaction wct =t != null ? t : new WindowContainerTransaction();//transition为空,走此处逻辑if (transition == null) {if (type < 0) {throw new IllegalArgumentException("Can't create transition with no type");}// This is a direct call from shell, so the entire transition lifecycle is// contained in the provided transaction if provided. Thus, we can setReady// immediately after apply.final boolean needsSetReady = t != null;final Transition nextTransition = new Transition(type, 0 /* flags */,mTransitionController, mService.mWindowManager.mSyncEngine);nextTransition.calcParallelCollectType(wct);mTransitionController.startCollectOrQueue(nextTransition,//deferred表示这个Transition的启动是否被推迟,//startCollectOrQueue方法中传递的值为false(deferred) -> {//动画状态为STATE_STARTEDnextTransition.start();nextTransition.mLogger.mStartWCT = wct;applyTransaction(wct, -1 /* syncId */, nextTransition, caller,deferred);if (needsSetReady) {nextTransition.setAllReady();}});return nextTransition.getToken();}// The transition already started collecting before sending a request to shell,// so just start here.//如果transition.isCollecting()和transition.isForcePlaying()均为falseif (!transition.isCollecting() && !transition.isForcePlaying()) {Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"+ " means Shell took too long to respond to a request. WM State may be"+ " incorrect now, please file a bug");applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller);return transition.getToken();}//动画状态为STATE_STARTEDtransition.start();transition.mLogger.mStartWCT = wct;applyTransaction(wct, -1 /*syncId*/, transition, caller);// Since the transition is already provided, it means WMCore is determining the// "readiness lifecycle" outside the provided transaction, so don't set ready here.return transition.getToken();}} finally {Binder.restoreCallingIdentity(ident);}}

我们先顺理一下传递过来的参数值,这有助于我们排查后续的逻辑顺序。
@WindowManager.TransitionType int type:TRANSIT_TO_FRONT(3)
@Nullable IBinder transitionToken:null,空值
@Nullable WindowContainerTransaction t:systemui侧传递过来的WindowContainerTransaction对象,包含bounds、task等相关操作指令。

因此从入参我们可以清楚该方法的逻辑走向

  1. 如果mTransitionController.getTransitionPlayer()和transition均为空,则直接调用applyTransaction(t, -1 /* syncId */, null, caller);
  2. 如果transition为空,则创建一个Transition对象,然后通过mTransitionController.startCollectOrQueue调用 applyTransaction(wct, -1 /* syncId */, nextTransition, caller, deferred);
  3. 如果transition.isCollecting()和transition.isForcePlaying()均为false,则直接调用applyTransaction(t, -1 /* syncId */, null, caller);
  4. 如果上述条件均不满足的情况,则直接通过transition.start()修改动画状态为STATE_STARTED,后续调用applyTransaction(wct, -1 /*syncId*/, transition, caller);

注:这里我们不关注Shell动画的逻辑,仅关注applyTransaction方法的调用逻辑。

结合传递过来的参数发现,transitionToken为空,因此Transition transition = Transition.fromBinder(transitionToken);这里transition值也为空,所以我们这里走的是第二步逻辑,也就是applyTransaction(wct, -1 /* syncId */, nextTransition, caller, deferred);
其中参数:

  • wct,systemui侧传递过来的WindowContainerTransaction对象

  • -1,一个syncId

  • nextTransition,新创建的shell动画

  • caller,是CallerInfo对象,这个主要就是用来记录Binder调用方的pid和uid

        static class CallerInfo {final int mPid;final int mUid;CallerInfo() {mPid = Binder.getCallingPid();mUid = Binder.getCallingUid();}}
    

    我们这里调用方是SystemUI,因此caller记录的就是SystemUI的pid和uid。

  • deferred,表示这个Transition的启动是否被推迟,我们可以从mTransitionController.startCollectOrQueue方法中知道该值为false

        /** Returns {@code true} if it started collecting, {@code false} if it was queued. */boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) {......//首先将当前Transition的状态标记为STATE_COLLECTING,接着通过BLASTSyncEngine.startSyncSet方法,创建一个SyncGroup,用来收集动画的参与者moveToCollecting(transit);//传递deferred值为falseonStartCollect.onCollectStarted(false /* deferred */);return true;}
    

这里我们继续跟踪applyTransaction方法。

    private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,@Nullable Transition transition, @NonNull CallerInfo caller, boolean deferred) {if (deferred) {try {return applyTransaction(t, syncId, transition, caller);} catch (RuntimeException e) {// If the transaction is deferred, the caller could be from TransitionController// #tryStartCollectFromQueue that executes on system's worker thread rather than// binder thread. And the operation in the WCT may be outdated that violates the// current state. So catch the exception to avoid crashing the system.Slog.e(TAG, "Failed to execute deferred applyTransaction", e);}return TRANSACT_EFFECTS_NONE;}return applyTransaction(t, syncId, transition, caller);}

这里根据前面传递的参数deferred,决定了走哪个分支,deferred仅表示这个Transition的启动是否被推迟,对我们这里分析并无影响(涉及shell动画流程),最终都会走到以下方法中:

    private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,@Nullable Transition transition, @NonNull CallerInfo caller) {return applyTransaction(t, syncId, transition, caller, null /* finishTransition */);}

继上述参数的传递多了一个finishTransition参数,这里的值就是null,继续跟踪这个重载的applyTransaction方法。

处理事务

    /*** @param syncId If non-null, this will be a sync-transaction.* @param transition A transition to collect changes into.* @param caller Info about the calling process.* @param finishTransition The transition that is currently being finished.* @return The effects of the window container transaction.*/private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId,@Nullable Transition transition, @NonNull CallerInfo caller,@Nullable Transition finishTransition) {int effects = TRANSACT_EFFECTS_NONE;ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId);mService.deferWindowLayout();mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */);try {if (transition != null) {transition.applyDisplayChangeIfNeeded();}final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();final int hopSize = hops.size();final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =t.getChanges().entrySet().iterator();while (entries.hasNext()) {final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());if (wc == null || !wc.isAttached()) {Slog.e(TAG, "Attempt to operate on detached container: " + wc);continue;}// Make sure we add to the syncSet before performing// operations so we don't end up splitting effects between the WM// pending transaction and the BLASTSync transaction.if (syncId >= 0) {addToSyncSet(syncId, wc);}if (transition != null) transition.collect(wc);if ((entry.getValue().getChangeMask()& WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {// Disable entering pip (eg. when recents pretends to finish itself)if (finishTransition != null) {finishTransition.setCanPipOnFinish(false /* canPipOnFinish */);} else if (transition != null) {transition.setCanPipOnFinish(false /* canPipOnFinish */);}}// A bit hacky, but we need to detect "remove PiP" so that we can "wrap" the// setWindowingMode call in force-hidden.boolean forceHiddenForPip = false;if (wc.asTask() != null && wc.inPinnedWindowingMode()&& entry.getValue().getWindowingMode() == WINDOWING_MODE_UNDEFINED) {// We are in pip and going to undefined. Now search hierarchy ops to determine// whether we are removing pip or expanding pip.for (int i = 0; i < hopSize; ++i) {final WindowContainerTransaction.HierarchyOp hop = hops.get(i);if (hop.getType() != HIERARCHY_OP_TYPE_REORDER) continue;final WindowContainer hopWc = WindowContainer.fromBinder(hop.getContainer());if (!wc.equals(hopWc)) continue;forceHiddenForPip = !hop.getToTop();}}if (forceHiddenForPip) {wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);// When removing pip, make sure that onStop is sent to the app ahead of// onPictureInPictureModeChanged.// See also PinnedStackTests#testStopBeforeMultiWindowCallbacksOnDismisswc.asTask().ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);wc.asTask().mTaskSupervisor.processStoppingAndFinishingActivities(null /* launchedActivity */, false /* processPausingActivities */,"force-stop-on-removing-pip");}int containerEffect = applyWindowContainerChange(wc, entry.getValue(),t.getErrorCallbackToken());effects |= containerEffect;if (forceHiddenForPip) {wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);}// Lifecycle changes will trigger ensureConfig for everything.if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0&& (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {haveConfigChanges.add(wc);}}// Hierarchy changesif (hopSize > 0) {final boolean isInLockTaskMode = mService.isInLockTaskMode();for (int i = 0; i < hopSize; ++i) {effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,isInLockTaskMode, caller, t.getErrorCallbackToken(),t.getTaskFragmentOrganizer(), finishTransition);}}// Queue-up bounds-change transactions for tasks which are now organized. Do// this after hierarchy ops so we have the final organized state.entries = t.getChanges().entrySet().iterator();while (entries.hasNext()) {final Map.Entry<IBinder, WindowContainerTransaction.Change> entry = entries.next();final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());if (wc == null || !wc.isAttached()) {Slog.e(TAG, "Attempt to operate on detached container: " + wc);continue;}final Task task = wc.asTask();final Rect surfaceBounds = entry.getValue().getBoundsChangeSurfaceBounds();if (task == null || !task.isAttached() || surfaceBounds == null) {continue;}if (!task.isOrganized()) {final Task parent = task.getParent() != null ? task.getParent().asTask() : null;// Also allow direct children of created-by-organizer tasks to be// controlled. In the future, these will become organized anyways.if (parent == null || !parent.mCreatedByOrganizer) {throw new IllegalArgumentException("Can't manipulate non-organized task surface " + task);}}final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();final SurfaceControl sc = task.getSurfaceControl();sft.setPosition(sc, surfaceBounds.left, surfaceBounds.top);if (surfaceBounds.isEmpty()) {sft.setWindowCrop(sc, null);} else {sft.setWindowCrop(sc, surfaceBounds.width(), surfaceBounds.height());}task.setMainWindowSizeChangeTransaction(sft);}if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);// Already calls ensureActivityConfigmService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);mService.mRootWindowContainer.resumeFocusedTasksTopActivities();} else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {haveConfigChanges.valueAt(i).forAllActivities(r -> {r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);});}}if (effects != 0) {mService.mWindowManager.mWindowPlacerLocked.requestTraversal();}} finally {mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);mService.continueWindowLayout();}return effects;}

Task事务处理

接WindowOrganizerController.java中
private int applyTransaction(@NonNull WindowContainerTransaction t, int syncId, @Nullable Transition transition, @NonNull CallerInfo caller, @Nullable Transition finishTransition)

Task区域大小bounds变化处理

int containerEffect = applyWindowContainerChange(wc, entry.getValue(),t.getErrorCallbackToken());

Task相关操作处理

该方法中applyHierarchyOp方法对象task操作进行相关处理

effects |= applyHierarchyOp(hops.get(i), effects, syncId, transition,isInLockTaskMode, caller, t.getErrorCallbackToken(),t.getTaskFragmentOrganizer(), finishTransition);

applyHierarchyOp方法中对task有很多种不同的操作,这里我们主要来看Task的启动、移除、重排序和重定向。
下面结合WindowContainerTransaction侧的构建的层级结构和applyHierarchyOp侧的实现来说明。

启动Task

WindowContainerTransaction

代码路径:frameworks/base/core/java/android/window/WindowContainerTransaction.java

    /*** Starts a task by id. The task is expected to already exist (eg. as a recent task).* @param taskId Id of task to start.* @param options bundle containing ActivityOptions for the task's top activity.* @hide*/@NonNullpublic WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));return this;}/*** Holds information about a reparent/reorder operation in the hierarchy. This is separate from* Changes because they must be executed in the same order that they are added.* @hide*/public static final class HierarchyOp implements Parcelable {    	....../** Create a hierarchy op for launching a task. */public static HierarchyOp createForTaskLaunch(int taskId, @Nullable Bundle options) {final Bundle fullOptions = options == null ? new Bundle() : options;fullOptions.putInt(LAUNCH_KEY_TASK_ID, taskId);return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_LAUNCH_TASK).setToTop(true).setLaunchOptions(fullOptions).build();}
applyHierarchyOp

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java

    private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,int syncId, @Nullable Transition transition, boolean isInLockTaskMode,@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,@Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) {final int type = hop.getType();switch (type) {......case HIERARCHY_OP_TYPE_LAUNCH_TASK: {mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,"launchTask HierarchyOp");final Bundle launchOpts = hop.getLaunchOptions();final int taskId = launchOpts.getInt(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);final SafeActivityOptions safeOptions =SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(caller.mPid, caller.mUid, taskId, safeOptions));break;}......}return effects;}

移除Task

WindowContainerTransaction

代码路径:frameworks/base/core/java/android/window/WindowContainerTransaction.java

    /*** Finds and removes a task and its children using its container token. The task is removed* from recents.* @param containerToken ContainerToken of Task to be removed*/@NonNullpublic WindowContainerTransaction removeTask(@NonNull WindowContainerToken containerToken) {mHierarchyOps.add(HierarchyOp.createForRemoveTask(containerToken.asBinder()));return this;}/*** Holds information about a reparent/reorder operation in the hierarchy. This is separate from* Changes because they must be executed in the same order that they are added.* @hide*/public static final class HierarchyOp implements Parcelable {....../** create a hierarchy op for deleting a task **/public static HierarchyOp createForRemoveTask(@NonNull IBinder container) {return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK).setContainer(container).build();}
applyHierarchyOp

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java

    private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,int syncId, @Nullable Transition transition, boolean isInLockTaskMode,@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,@Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) {final int type = hop.getType();switch (type) {case HIERARCHY_OP_TYPE_REMOVE_TASK: {final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());if (wc == null || wc.asTask() == null || !wc.isAttached()) {Slog.e(TAG, "Attempt to remove invalid task: " + wc);break;}final Task task = wc.asTask();task.remove(true, "Applying remove task Hierarchy Op");break;}......}return effects;}

Task重定向和重排序

WindowContainerTransaction

代码路径:frameworks/base/core/java/android/window/WindowContainerTransaction.java

    /*** Reparents a container into another one. The effect of a {@code null} parent can vary. For* example, reparenting a stack to {@code null} will reparent it to its display.** @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to*              the bottom.*/@NonNullpublic WindowContainerTransaction reparent(@NonNull WindowContainerToken child,@Nullable WindowContainerToken parent, boolean onTop) {mHierarchyOps.add(HierarchyOp.createForReparent(child.asBinder(),parent == null ? null : parent.asBinder(),onTop));return this;}/*** Reorders a container within its parent.** @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to*              the bottom.*/@NonNullpublic WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {mHierarchyOps.add(HierarchyOp.createForReorder(child.asBinder(), onTop));return this;}/*** Holds information about a reparent/reorder operation in the hierarchy. This is separate from* Changes because they must be executed in the same order that they are added.* @hide*/public static final class HierarchyOp implements Parcelable {......public static HierarchyOp createForReparent(@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT).setContainer(container).setReparentContainer(reparent).setToTop(toTop).build();}public static HierarchyOp createForReorder(@NonNull IBinder container, boolean toTop) {return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REORDER).setContainer(container).setReparentContainer(container).setToTop(toTop).build();}
applyHierarchyOp

代码路径:frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java

    private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,int syncId, @Nullable Transition transition, boolean isInLockTaskMode,@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,@Nullable ITaskFragmentOrganizer organizer, @Nullable Transition finishTransition) {final int type = hop.getType();switch (type) {......case HIERARCHY_OP_TYPE_REORDER:case HIERARCHY_OP_TYPE_REPARENT: {final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());if (wc == null || !wc.isAttached()) {Slog.e(TAG, "Attempt to operate on detached container: " + wc);break;}// There is no use case to ask the reparent operation in lock-task mode now, so keep// skipping this operation as usual.if (isInLockTaskMode && type == HIERARCHY_OP_TYPE_REPARENT) {Slog.w(TAG, "Skip applying hierarchy operation " + hop+ " while in lock task mode");break;}if (isLockTaskModeViolation(wc.getParent(), wc.asTask(), isInLockTaskMode)) {break;}if (syncId >= 0) {addToSyncSet(syncId, wc);}if (transition != null) {transition.collect(wc);if (hop.isReparent()) {if (wc.getParent() != null) {// Collect the current parent. It's visibility may change as// a result of this reparenting.transition.collect(wc.getParent());}if (hop.getNewParent() != null) {final WindowContainer parentWc =WindowContainer.fromBinder(hop.getNewParent());if (parentWc == null) {Slog.e(TAG, "Can't resolve parent window from token");break;}transition.collect(parentWc);}}}effects |= sanitizeAndApplyHierarchyOp(wc, hop);break;}......}return effects;}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词