作者:韩茹
公司:程序咖(北京)科技有限公司
鸿蒙巴士专栏作家
一、线程治理概述
不同利用在各自独立的过程中运行。当利用以任何模式启动时,零碎为其创立过程,该过程将继续运行。当过程实现当前任务处于期待状态,且系统资源有余时,零碎主动回收。
在启动利用时,零碎会为该利用创立一个称为“主线程”的执行线程。该线程随着利用创立或隐没,是利用的外围线程。UI界面的显示和更新等操作,都是在主线程上进行。主线程又称UI线程,默认状况下,所有的操作都是在主线程上执行。如果须要执行比拟耗时的工作(如下载文件、查询数据库),可创立其余线程来解决。
二、工作散发TaskDispatcher
如果利用的业务逻辑比较复杂,可能须要创立多个线程来执行多个工作。这种状况下,代码简单难以保护,工作与线程的交互也会更加繁冗。要解决此问题,开发者能够应用“TaskDispatcher”来散发不同的工作。
TaskDispatcher是一个工作散发器,它是Ability散发工作的根本接口,暗藏工作所在线程的实现细节。
- TaskDispatcher是一个工作散发器将工作公布到任务调度器的根本接口。
- TaskDispatcher暗藏了底层线程实现的细节,包含创立、销毁和重用。
- TaskDispatcher反对在简直所有场景中执行同步、异步、串行和并行任务。
为保障利用有更好的响应性,咱们须要设计工作的优先级。在UI线程上运行的工作默认以高优先级运行,如果某个工作无需期待后果,则能够用低优先级。
优先级 | 详细描述 |
---|---|
HIGH | 最高工作优先级,比默认优先级、低优先级的工作有更高的几率失去执行。 |
DEFAULT | 默认工作优先级, 比低优先级的工作有更高的几率失去执行。 |
LOW | 低工作优先级,比高优先级、默认优先级的工作有更低的几率失去执行。 |
TaskDispatcher具备多种实现,每种实现对应不同的工作散发器。在散发工作时能够指定工作的优先级,由同一个工作散发器分收回的工作具备雷同的优先级。零碎提供的工作散发器有GlobalTaskDispatcher、ParallelTaskDispatcher、SerialTaskDispatcher 、SpecTaskDispatcher。
- GlobalTaskDispatcher
全局并发工作散发器,由Ability执行getGlobalTaskDispatcher()获取。实用于工作之间没有分割的状况。一个利用只有一个GlobalTaskDispatcher,它在程序完结时才被销毁。
TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
- ParallelTaskDispatcher
并发工作散发器,由Ability执行createParallelTaskDispatcher()创立并返回。与GlobalTaskDispatcher不同的是,ParallelTaskDispatcher不具备全局唯一性,能够创立多个。开发者在创立或销毁dispatcher时,须要持有对应的对象援用 。
String dispatcherName = "parallelTaskDispatcher";TaskDispatcher parallelTaskDispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
- SerialTaskDispatcher
串行工作散发器,由Ability执行createSerialTaskDispatcher()创立并返回。由该散发器散发的所有的工作都是按程序执行,然而执行这些工作的线程并不是固定的。如果要执行并行任务,应应用ParallelTaskDispatcher或者GlobalTaskDispatcher,而不是创立多个SerialTaskDispatcher。如果工作之间没有依赖,应应用GlobalTaskDispatcher来实现。它的创立和销毁由开发者本人治理,开发者在应用期间须要持有该对象援用。
String dispatcherName = "serialTaskDispatcher";TaskDispatcher serialTaskDispatcher = createSerialTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
- SpecTaskDispatcher
专有工作散发器,绑定到专有线程上的工作散发器。目前已有的专有线程为UI线程,通过UITaskDispatcher进行工作散发。
UITaskDispatcher:绑定到利用主线程的专有工作散发器, 由Ability执行getUITaskDispatcher()创立并返回。 由该散发器散发的所有的工作都是在主线程上按程序执行,它在应用程序完结时被销毁。
TaskDispatcher uiTaskDispatcher = getUITaskDispatcher();
该接口中罕用办法:
public interface TaskDispatcher { void syncDispatch(Runnable var1); //分派任务,并在以后线程中期待直到工作实现。 Revocable asyncDispatch(Runnable var1); //异步分派任务 Revocable delayDispatch(Runnable var1, long var2); void syncDispatchBarrier(Runnable var1); void asyncDispatchBarrier(Runnable var1); Group createDispatchGroup(); Revocable asyncGroupDispatch(Group var1, Runnable var2); boolean groupDispatchWait(Group var1, long var2); void groupDispatchNotify(Group var1, Runnable var2); void applyDispatch(Consumer<Long> var1, long var2);}
三、开发步骤
3.1 同步散发
syncDispatch
同步派发工作:派发工作并在以后线程期待工作执行实现。在返回前,以后线程会被阻塞。
咱们在ability_main.xml文件中增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> <Button ohos:id="$+id:btn1" ohos:height="match_content" ohos:width="match_content" ohos:text="同步散发syncDispatch" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
而后在MainAbilitySlice中咱们来解决按钮的点击事件,通过观察日期,来理解工作散发:
package com.example.hanrutaskdispatcher.slice;import com.example.hanrutaskdispatcher.ResourceTable;import ohos.aafwk.ability.AbilitySlice;import ohos.aafwk.content.Intent;import ohos.app.dispatcher.TaskDispatcher;import ohos.app.dispatcher.task.TaskPriority;import ohos.hiviewdfx.HiLog;import ohos.hiviewdfx.HiLogLabel;public class MainAbilitySlice extends AbilitySlice { private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x00201, "MY_TAG"); @Override protected void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); initComponent(); } public void initComponent(){ findComponentById(ResourceTable.Id_btn1).setClickedListener(component ->syncDispatch() ); } // 1.同步散发 public void syncDispatch(){ // 1.获取一个工作散发器,全局的 TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT); // 2.同步散发,在以后线程中期待直到工作实现。 globalTaskDispatcher.syncDispatch(new Runnable() { @Override public void run() { HiLog.info(LABEL,"->同步工作1,在运行ing。。"); } }); HiLog.info(LABEL,"->同步工作1,前面。。"); // 也能够简写:Lambda表达式 globalTaskDispatcher.syncDispatch(() -> { HiLog.info(LABEL,"-->同步工作2,在运行ing。。"); }); HiLog.info(LABEL,"-->同步工作2,前面。。"); globalTaskDispatcher.syncDispatch(() -> { HiLog.info(LABEL,"--->同步工作3,在运行ing。。"); }); HiLog.info(LABEL,"--->同步工作3,前面。。"); }}
运行程序, 并点击按钮1:
阐明
如果对syncDispatch使用不当, 将会导致死锁。如下情景可能导致死锁产生:
- 在专有线程上,利用该专有工作散发器进行syncDispatch。
- 在被某个串行工作散发器(dispatcher_a)派发的工作中,再次利用同一个串行工作散发器(dispatcher_a)对象派发工作。
- 在被某个串行工作散发器(dispatcher_a)派发的工作中,通过数次派发工作,最终又利用该(dispatcher_a)串行工作散发器派发工作。例如:dispatcher_a派发的工作应用dispatcher_b进行工作的派发,在dispatcher_b派发的工作中又利用dispatcher_a进行派发工作。
- 串行工作散发器(dispatcher_a)派发的工作中利用串行工作散发器(dispatcher_b)进行同步派发工作,同时dispatcher_b派发的工作中利用串行工作散发器(dispatcher_a)进行同步派发工作。在特定的线程执行程序下将导致死锁。
3.2 异步散发
asyncDispatch
异步派发工作:派发工作,并立刻返回,返回值是一个可用于勾销工作的接口。
在ability_main.xml中再增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> ... <Button ohos:id="$+id:btn2" ohos:height="match_content" ohos:width="match_content" ohos:text="异步散发asyncDispatch" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
在MainAbilitySlice中解决第二个按钮的异步散发:
// 2.异步散发 public void asyncDispatch(){ // 1.获取一个工作散发器,全局的 TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT); // 2.异步散发工作,返回值是一个Revocable,能够用于勾销工作 Revocable revocable=globalTaskDispatcher.asyncDispatch(() -> { HiLog.info(LABEL,"->异步工作1,在运行ing。。"); }); HiLog.info(LABEL,"->异步工作1,前面。。"); globalTaskDispatcher.asyncDispatch(() -> { HiLog.info(LABEL,"-->异步工作2,在运行ing。。"); }); HiLog.info(LABEL,"-->异步工作2,前面。。"); globalTaskDispatcher.asyncDispatch(() -> { HiLog.info(LABEL,"--->异步工作3,在运行ing。。"); }); HiLog.info(LABEL,"--->异步工作3,前面。。"); }public void initComponent(){ findComponentById(ResourceTable.Id_btn1).setClickedListener(component ->syncDispatch() ); findComponentById(ResourceTable.Id_btn2).setClickedListener(component ->asyncDispatch() ); }
运行后果每次可能都不太一样:
3.3 提早散发
delayDispatch
异步提早派发工作:异步执行,函数立刻返回,外部会在延时指定工夫后将工作派发到相应队列中。延时工夫参数仅代表在这段时间当前工作散发器会将工作退出到队列中,工作的理论执行工夫可能晚于这个工夫。具体比这个数值晚多久,取决于队列及外部线程池的忙碌状况。
在ability_main.xml中再增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> ... <Button ohos:id="$+id:btn3" ohos:height="match_content" ohos:width="match_content" ohos:text="提早散发delayDispatch" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
在MainAbilitySlice中解决第三个按钮的异步散发:
public void initComponent(){ findComponentById(ResourceTable.Id_btn1).setClickedListener(component ->syncDispatch() ); findComponentById(ResourceTable.Id_btn2).setClickedListener(component ->asyncDispatch() ); findComponentById(ResourceTable.Id_btn3).setClickedListener(component ->delayDispatch() ); } // 3.提早散发 public void delayDispatch(){ // 获取零碎以后的工夫 final long callTime = System.currentTimeMillis(); // 设置要提早的工夫 final long delayTime = 50L; // 创立一个全局的工作散发器 TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT); // 提早散发,返回一个Revocable能够用于勾销工作 Revocable revocable = globalTaskDispatcher.delayDispatch(new Runnable() { @Override public void run() { HiLog.info(LABEL, "->提早工作1,在运行ing。。"); final long actualDelay = System.currentTimeMillis() - callTime;// 理论延迟时间 HiLog.info(LABEL, "actualDelayTime:%{public}d ,delayTime:%{public}d , actualDelayTime >= delayTime: %{public}b",actualDelay,delayTime, (actualDelay >= delayTime)); } }, delayTime); HiLog.info(LABEL, "->提早工作1,之后。。"); }
运行后果:
3.4 工作组
Group
工作组:示意一组工作,且该组工作之间有肯定的分割,由TaskDispatcher执行createDispatchGroup创立并返回。将工作退出工作组,返回一个用于勾销工作的接口。
在ability_main.xml中再增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> ... <Button ohos:id="$+id:btn4" ohos:height="match_content" ohos:width="match_content" ohos:text="工作组Group" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
在MainAbilitySlice中解决第四个按钮的异步散发:
public void initComponent() { findComponentById(ResourceTable.Id_btn1).setClickedListener(component -> syncDispatch()); findComponentById(ResourceTable.Id_btn2).setClickedListener(component -> asyncDispatch()); findComponentById(ResourceTable.Id_btn3).setClickedListener(component -> delayDispatch()); findComponentById(ResourceTable.Id_btn4).setClickedListener(component -> testGroup()); } // 4.工作组 // 将一系列相关联的下载工作放入一个工作组,执行完下载工作后敞开利用。 public void testGroup() { String dispatcherName = "parallelTaskDispatcher"; //并行任务分配器 TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT); // 创立工作组: Group group = dispatcher.createDispatchGroup(); // 将工作1退出工作组,返回一个用于勾销工作的接口。 dispatcher.asyncGroupDispatch(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "--->下载工作1,正在运行。。"); } }); // 将与工作1相关联的工作2退出工作组。 dispatcher.asyncGroupDispatch(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "--->下载工作2,正在运行。。"); } }); // 在工作组中的所有工作执行实现后执行指定工作。 dispatcher.groupDispatchNotify(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "工作组中的所有工作都执行结束后,执行这个敞开工作 ing。。。"); } }); }
多执行几次,可能运行后果是不一样的,这里我点击了3次按钮:
3.5 勾销工作
Revocable
勾销工作:Revocable是勾销一个异步工作的接口。异步工作包含通过 asyncDispatch、delayDispatch、asyncGroupDispatch 派发的工作。如果工作曾经在执行中或执行实现,则会返回勾销失败。
在ability_main.xml中再增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> ... <Button ohos:id="$+id:btn5" ohos:height="match_content" ohos:width="match_content" ohos:text="勾销工作Revocable" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
在MainAbilitySlice中解决第五个按钮的异步散发:
public void initComponent() { findComponentById(ResourceTable.Id_btn1).setClickedListener(component -> syncDispatch()); findComponentById(ResourceTable.Id_btn2).setClickedListener(component -> asyncDispatch()); findComponentById(ResourceTable.Id_btn3).setClickedListener(component -> delayDispatch()); findComponentById(ResourceTable.Id_btn4).setClickedListener(component -> testGroup()); findComponentById(ResourceTable.Id_btn5).setClickedListener(component -> revocableTask()); } // 5.勾销工作 public void revocableTask(){ TaskDispatcher dispatcher = getUITaskDispatcher(); Revocable revocable = dispatcher.delayDispatch(new Runnable() { @Override public void run() { HiLog.info(LABEL, "提早散发。。"); } }, 10); boolean revoked = revocable.revoke(); // 撤销 HiLog.info(LABEL, "%{public}b", revoked); }
执行后果可能是true,也可能是 false:
3.6 同步设置屏障工作
syncDispatchBarrier
同步设置屏障工作:在工作组上设立工作执行屏障,同步期待工作组中的所有工作执行实现,再执行指定工作。
阐明
在全局并发工作散发器(GlobalTaskDispatcher)上同步设置工作屏障,将不会起到屏障作用。
在ability_main.xml中再增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> ... <Button ohos:id="$+id:btn6" ohos:height="match_content" ohos:width="match_content" ohos:text="同步设置屏障工作syncDispatchBarrier" ohos:multiple_lines="true" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
在MainAbilitySlice中解决第六个按钮的异步散发:
public void initComponent() { findComponentById(ResourceTable.Id_btn1).setClickedListener(component -> syncDispatch()); findComponentById(ResourceTable.Id_btn2).setClickedListener(component -> asyncDispatch()); findComponentById(ResourceTable.Id_btn3).setClickedListener(component -> delayDispatch()); findComponentById(ResourceTable.Id_btn4).setClickedListener(component -> testGroup()); findComponentById(ResourceTable.Id_btn5).setClickedListener(component -> revocableTask()); findComponentById(ResourceTable.Id_btn6).setClickedListener(component -> syncDispatchBarrier()); } // 6.同步设置屏障工作 public void syncDispatchBarrier(){ String dispatcherName = "parallelTaskDispatcher"; TaskDispatcher dispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT); // 创立工作组。 Group group = dispatcher.createDispatchGroup(); // 将工作退出工作组,返回一个用于勾销工作的接口。 dispatcher.asyncGroupDispatch(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "异步工作1,在运行ing。。"); // 1 } }); dispatcher.asyncGroupDispatch(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "异步工作2,在运行ing。。"); // 2 } }); dispatcher.syncDispatchBarrier(new Runnable() { @Override public void run() { HiLog.info(LABEL, "屏障工作"); // 3 } }); HiLog.info(LABEL, "同步设置屏障工作后。。"); // 4 }
多执行几次,可能运行后果是不一样的:1和2的执行程序不定;3和4总是在1和2之后按程序执行。
3.7 异步设置屏障工作
asyncDispatchBarrier
异步设置屏障工作:在工作组上设立工作执行屏障后间接返回,指定工作将在工作组中的所有工作执行实现后再执行。
阐明
在全局并发工作散发器(GlobalTaskDispatcher)上异步设置工作屏障,将不会起到屏障作用。能够应用并发工作散发器(ParallelTaskDispatcher)拆散不同的工作组,达到宏观并行、宏观串行的行为。
在ability_main.xml中再增加一个按钮:
<?xml version="1.0" encoding="utf-8"?><DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_parent" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="vertical"> ... <Button ohos:id="$+id:btn7" ohos:height="match_content" ohos:width="match_content" ohos:text="异步设置屏障工作asyncDispatchBarrier" ohos:multiple_lines="true" ohos:text_size="25fp" ohos:background_element="#EEEEEE" ohos:padding="10vp" ohos:margin="10vp" /></DirectionalLayout>
在MainAbilitySlice中解决第七个按钮的异步散发:
public void initComponent() { findComponentById(ResourceTable.Id_btn1).setClickedListener(component -> syncDispatch()); findComponentById(ResourceTable.Id_btn2).setClickedListener(component -> asyncDispatch()); findComponentById(ResourceTable.Id_btn3).setClickedListener(component -> delayDispatch()); findComponentById(ResourceTable.Id_btn4).setClickedListener(component -> testGroup()); findComponentById(ResourceTable.Id_btn5).setClickedListener(component -> revocableTask()); findComponentById(ResourceTable.Id_btn6).setClickedListener(component -> syncDispatchBarrier()); findComponentById(ResourceTable.Id_btn7).setClickedListener(component -> asyncDispatchBarrier()); } // 7.异步设置屏障工作 public void asyncDispatchBarrier(){ TaskDispatcher dispatcher = createParallelTaskDispatcher("dispatcherName", TaskPriority.DEFAULT);// 创立工作组。 Group group = dispatcher.createDispatchGroup();// 将工作退出工作组,返回一个用于勾销工作的接口。 dispatcher.asyncGroupDispatch(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "异步工作1,在运行ing。。"); // 1 } }); dispatcher.asyncGroupDispatch(group, new Runnable() { @Override public void run() { HiLog.info(LABEL, "异步工作2,在运行ing。。"); // 2 } }); dispatcher.asyncDispatchBarrier(new Runnable() { @Override public void run() { HiLog.info(LABEL, "屏障工作"); // 3 } }); HiLog.info(LABEL, "异步设置屏障工作后。。"); // 4 // 1和2的执行程序不定,但总在3之前执行;4不须要期待1、2、3执行实现。 }
多执行几次,可能运行后果是不一样的:1和2的执行程序不定,但总在3之前执行;4不须要期待1、2、3执行实现。
3.8 执行屡次工作
applyDispatch
执行屡次工作:对指定工作执行屡次。
在ability_main.xml中再增加一个按钮,这里按钮太多了,一屏高度曾经摆放不下了,所以咱们外层嵌套一个ScrollView,滚动起来
<?xml version="1.0" encoding="utf-8"?><ScrollView xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_content" ohos:width="match_parent" > <DirectionalLayout ohos:height="match_parent" ohos:width="match_parent" > ... <Button ohos:id="$+id:btn8" ohos:height="match_content" ohos:width="match_content" ohos:background_element="#EEEEEE" ohos:margin="10vp" ohos:multiple_lines="true" ohos:padding="10vp" ohos:text="执行屡次工作applyDispatch" ohos:text_size="25fp" /> </DirectionalLayout></ScrollView>
在MainAbilitySlice中解决第七个按钮的异步散发:
public void initComponent() { findComponentById(ResourceTable.Id_btn1).setClickedListener(component -> syncDispatch()); findComponentById(ResourceTable.Id_btn2).setClickedListener(component -> asyncDispatch()); findComponentById(ResourceTable.Id_btn3).setClickedListener(component -> delayDispatch()); findComponentById(ResourceTable.Id_btn4).setClickedListener(component -> testGroup()); findComponentById(ResourceTable.Id_btn5).setClickedListener(component -> revocableTask()); findComponentById(ResourceTable.Id_btn6).setClickedListener(component -> syncDispatchBarrier()); findComponentById(ResourceTable.Id_btn7).setClickedListener(component -> asyncDispatchBarrier()); findComponentById(ResourceTable.Id_btn8).setClickedListener(component -> applyDispatch()); } // 8.执行屡次工作:对指定工作执行屡次。 public void applyDispatch(){ final int total = 10; // 一种同步辅助工具,容许一个或多个线程期待一组在其余线程中执行的操作实现。 final CountDownLatch latch = new CountDownLatch(total); final List<Long> indexList = new ArrayList<>(total); TaskDispatcher dispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT); // 执行工作 total 次。 dispatcher.applyDispatch((index) -> { indexList.add(index); latch.countDown(); }, total); // 设置工作超时。 try { latch.await(); } catch (InterruptedException exception) { HiLog.error(LABEL, "latch exception"); } HiLog.info(LABEL, "list size matches, %{public}b", (total == indexList.size())); }
执行:
更多内容:
1、社区:鸿蒙巴士https://www.harmonybus.net/
2、公众号:HarmonyBus
3、技术交换QQ群:714518656
4、视频课:https://www.chengxuka.com