diff --git a/doric-android/devkit/src/main/AndroidManifest.xml b/doric-android/devkit/src/main/AndroidManifest.xml index d5e29b36..55f66ac0 100644 --- a/doric-android/devkit/src/main/AndroidManifest.xml +++ b/doric-android/devkit/src/main/AndroidManifest.xml @@ -12,6 +12,8 @@ android:theme="@style/Theme.Design.Light.NoActionBar" /> - + diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDev.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDev.java index 9310c121..ed26d78c 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDev.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDev.java @@ -21,6 +21,7 @@ import pub.doric.DoricContextManager; import pub.doric.DoricNativeDriver; import pub.doric.devkit.ui.DoricDevActivity; import pub.doric.devkit.util.SimulatorUtil; +import pub.doric.performance.DoricPerformanceProfile; import pub.doric.utils.DoricLog; public class DoricDev { @@ -51,6 +52,12 @@ public class DoricDev { private DoricDev() { this.isRunningInEmulator = SimulatorUtil.isSimulator(Doric.application()); DoricNativeDriver.getInstance().getRegistry().registerMonitor(new DoricDevMonitor()); + DoricNativeDriver.getInstance().getRegistry().setGlobalPerformanceAnchorHook(new DoricPerformanceProfile.AnchorHook() { + @Override + public void onAnchor(String name, long prepare, long start, long end) { + + } + }); } public static DoricDev getInstance() { @@ -240,7 +247,7 @@ public class DoricDev { if (reloadingContexts.get(context.getContextId()) == null) { reloadingContexts.put(context.getContextId(), context); } - + for (StatusCallback callback : callbacks) { callback.onReload(context, script); } diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java new file mode 100644 index 00000000..15829f66 --- /dev/null +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/DoricDevPerformanceAnchorHook.java @@ -0,0 +1,30 @@ +/* + * Copyright [2021] [Doric.Pub] + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package pub.doric.devkit; + +import pub.doric.performance.DoricPerformanceProfile; + +/** + * @Description: pub.doric.devkit + * @Author: pengfei.zhou + * @CreateDate: 2021/7/20 + */ +public class DoricDevPerformanceAnchorHook implements DoricPerformanceProfile.AnchorHook { + @Override + public void onAnchor(String name, long prepare, long start, long end) { + + } +} diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java index c45c95ed..bde66b58 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevActivity.java @@ -417,7 +417,6 @@ public class DoricDevActivity extends AppCompatActivity implements DoricDev.Stat Intent intent = new Intent(holder.itemView.getContext(), DoricDevPerfActivity.class); intent.putExtra(DORIC_CONTEXT_ID_KEY, context.getContextId()); v.getContext().startActivity(intent); - ((Activity) v.getContext()).finish(); } }); } @@ -428,7 +427,6 @@ public class DoricDevActivity extends AppCompatActivity implements DoricDev.Stat Intent intent = new Intent(holder.itemView.getContext(), DoricShowNodeTreeActivity.class); intent.putExtra(DORIC_CONTEXT_ID_KEY, context.getContextId()); v.getContext().startActivity(intent); - ((Activity) v.getContext()).finish(); } }); final String[] items = actionMap.keySet().toArray(new String[0]); diff --git a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java index 2e96eab0..c77b9f14 100644 --- a/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java +++ b/doric-android/devkit/src/main/java/pub/doric/devkit/ui/DoricDevPerfActivity.java @@ -16,9 +16,26 @@ package pub.doric.devkit.ui; +import android.graphics.Color; import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import pub.doric.devkit.R; /** * @Description: pub.doric.devkit.ui @@ -29,5 +46,88 @@ public class DoricDevPerfActivity extends DoricDevBaseActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_doricdev_perf); + TextView textView = findViewById(R.id.tv_title); + textView.setText(String.format("Doric %s <%s>", doricContext.getSource(), doricContext.getContextId())); + RecyclerView recyclerView = findViewById(R.id.list); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + recyclerView.setAdapter(new MyAdapter()); + } + + private static class PerfCellHolder extends RecyclerView.ViewHolder { + private TextView tvName; + private LinearLayout layoutWaterfall; + private View waterfallPrepared; + private View waterfallWorked; + private LinearLayout layoutExpand; + private TextView tvFuncName; + private TextView tvParameter; + private TextView tvCost; + + public PerfCellHolder(@NonNull View itemView) { + super(itemView); + } + } + + private static class AnchorNode { + String name; + long prepare; + } + + private class MyAdapter extends RecyclerView.Adapter { + private List anchorNodes = new ArrayList<>(); + + private MyAdapter() { + Map anchorMap = doricContext.getPerformanceProfile().getAnchorMap(); + for (String key : anchorMap.keySet()) { + if (key.endsWith("#prepare")) { + Long prepare = anchorMap.get(key); + if (prepare != null) { + AnchorNode anchorNode = new AnchorNode(); + anchorNode.name = key.substring(0, key.lastIndexOf("#prepare")); + anchorNode.prepare = prepare; + anchorNodes.add(anchorNode); + } + } + } + Collections.sort(anchorNodes, new Comparator() { + @Override + public int compare(AnchorNode o1, AnchorNode o2) { + return (int) (o1.prepare - o2.prepare); + } + }); + } + + @Override + public PerfCellHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View cell = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_doricdev_perf, parent, false); + PerfCellHolder cellHolder = new PerfCellHolder(cell); + cellHolder.tvName = cell.findViewById(R.id.tv_name); + cellHolder.layoutWaterfall = cell.findViewById(R.id.layout_waterfall); + cellHolder.waterfallPrepared = cell.findViewById(R.id.waterfall_prepared); + cellHolder.waterfallWorked = cell.findViewById(R.id.waterfall_worked); + cellHolder.layoutExpand = cell.findViewById(R.id.layout_expand); + cellHolder.tvFuncName = cell.findViewById(R.id.tv_func_name); + cellHolder.tvParameter = cell.findViewById(R.id.tv_parameter); + cellHolder.tvCost = cell.findViewById(R.id.tv_cost); + return cellHolder; + } + + @Override + public void onBindViewHolder(@NonNull PerfCellHolder holder, int position) { + holder.itemView.setBackgroundColor(position % 2 == 0 ? Color.parseColor("#ecf0f1") : Color.WHITE); + holder.layoutExpand.setVisibility(View.GONE); + AnchorNode anchorNode = anchorNodes.get(position); + if (anchorNode.name.startsWith("Call")) { + holder.tvName.setText("Call"); + } else { + holder.tvName.setText(anchorNode.name); + } + } + + @Override + public int getItemCount() { + return anchorNodes.size(); + } } } diff --git a/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml b/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml new file mode 100644 index 00000000..b0818566 --- /dev/null +++ b/doric-android/devkit/src/main/res/layout/activity_doricdev_perf.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/doric-android/devkit/src/main/res/layout/item_doricdev_perf.xml b/doric-android/devkit/src/main/res/layout/item_doricdev_perf.xml new file mode 100644 index 00000000..8ed30631 --- /dev/null +++ b/doric-android/devkit/src/main/res/layout/item_doricdev_perf.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doric-android/doric/src/main/java/pub/doric/DoricContext.java b/doric-android/doric/src/main/java/pub/doric/DoricContext.java index b3678d90..31b31ffc 100644 --- a/doric-android/doric/src/main/java/pub/doric/DoricContext.java +++ b/doric-android/doric/src/main/java/pub/doric/DoricContext.java @@ -108,6 +108,10 @@ public class DoricContext { this.source = source; this.extra = extra; this.performanceProfile = new DoricPerformanceProfile(contextId); + DoricPerformanceProfile.AnchorHook anchorHook = getDriver().getRegistry().getGlobalPerformanceAnchorHook(); + if (anchorHook != null) { + this.performanceProfile.addAnchorHook(anchorHook); + } } public DoricPerformanceProfile getPerformanceProfile() { diff --git a/doric-android/doric/src/main/java/pub/doric/DoricRegistry.java b/doric-android/doric/src/main/java/pub/doric/DoricRegistry.java index 6d3962de..d2a5dfae 100644 --- a/doric-android/doric/src/main/java/pub/doric/DoricRegistry.java +++ b/doric-android/doric/src/main/java/pub/doric/DoricRegistry.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import pub.doric.engine.DoricJSEngine; +import pub.doric.performance.DoricPerformanceProfile; import pub.doric.plugin.AnimatePlugin; import pub.doric.plugin.CoordinatorPlugin; import pub.doric.plugin.DoricJavaPlugin; @@ -246,4 +247,16 @@ public class DoricRegistry { public void setDefaultErrorDrawable(Drawable defaultErrorDrawable) { this.defaultErrorDrawable = defaultErrorDrawable; } + + + private DoricPerformanceProfile.GlobalAnchorHook globalPerformanceAnchorHook; + + public void setGlobalPerformanceAnchorHook(DoricPerformanceProfile.GlobalAnchorHook anchorHook) { + globalPerformanceAnchorHook = anchorHook; + } + + public DoricPerformanceProfile.GlobalAnchorHook getGlobalPerformanceAnchorHook() { + return globalPerformanceAnchorHook; + } + } diff --git a/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java b/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java index 8feaaa88..f21dbcca 100644 --- a/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java +++ b/doric-android/doric/src/main/java/pub/doric/engine/DoricJSEngine.java @@ -67,6 +67,10 @@ public class DoricJSEngine implements Handler.Callback, DoricTimerExtension.Time public DoricJSEngine() { mDoricRegistry = new DoricRegistry(this); + DoricPerformanceProfile.AnchorHook anchorHook = mDoricRegistry.getGlobalPerformanceAnchorHook(); + if (anchorHook != null) { + globalProfile.addAnchorHook(anchorHook); + } globalProfile.prepare(DoricPerformanceProfile.PART_INIT); handlerThread = new HandlerThread(this.getClass().getSimpleName()); handlerThread.start(); diff --git a/doric-android/doric/src/main/java/pub/doric/performance/DoricPerformanceProfile.java b/doric-android/doric/src/main/java/pub/doric/performance/DoricPerformanceProfile.java index 981ed116..7016984d 100644 --- a/doric-android/doric/src/main/java/pub/doric/performance/DoricPerformanceProfile.java +++ b/doric-android/doric/src/main/java/pub/doric/performance/DoricPerformanceProfile.java @@ -57,6 +57,10 @@ public class DoricPerformanceProfile { void onAnchor(String name, long prepare, long start, long end); } + public interface GlobalAnchorHook extends AnchorHook { + void onAnchor(DoricPerformanceProfile profile, String name, long prepare, long start, long end); + } + public DoricPerformanceProfile(String name) { this.name = name; } @@ -113,10 +117,6 @@ public class DoricPerformanceProfile { print(anchorName); } - public Map getAnchorMap() { - return this.anchorMap; - } - private void print(final String anchorName) { if (!enable) { return; @@ -139,7 +139,11 @@ public class DoricPerformanceProfile { Log.d(TAG, String.format("%s: %s prepared %dms, cost %dms.", name, anchorName, start - prepare, end - start)); for (AnchorHook hook : hooks) { - hook.onAnchor(anchorName, prepare, start, end); + if (hook instanceof GlobalAnchorHook) { + ((GlobalAnchorHook) hook).onAnchor(DoricPerformanceProfile.this, anchorName, prepare, start, end); + } else { + hook.onAnchor(anchorName, prepare, start, end); + } } } }); diff --git a/doric-iOS/Pod/Classes/DoricContextManager.m b/doric-iOS/Pod/Classes/DoricContextManager.m index f9611dde..ba8fdc8d 100644 --- a/doric-iOS/Pod/Classes/DoricContextManager.m +++ b/doric-iOS/Pod/Classes/DoricContextManager.m @@ -54,6 +54,9 @@ + (instancetype)instance { - (void)createContext:(DoricContext *)context script:(NSString *)script source:(NSString *)source { context.contextId = [NSString stringWithFormat:@"%ld", (long) ++self.counter]; context.performanceProfile = [[DoricPerformanceProfile alloc] initWithName:context.contextId]; + if (context.driver.registry.globalPerformanceAnchorHook) { + [context.performanceProfile addAnchorHook:context.driver.registry.globalPerformanceAnchorHook]; + } dispatch_sync(self.mapQueue, ^() { [self.contextMapTable setObject:context forKey:context.contextId]; }); diff --git a/doric-iOS/Pod/Classes/DoricRegistry.h b/doric-iOS/Pod/Classes/DoricRegistry.h index 28b05901..1cbc1f2f 100644 --- a/doric-iOS/Pod/Classes/DoricRegistry.h +++ b/doric-iOS/Pod/Classes/DoricRegistry.h @@ -27,10 +27,12 @@ NS_ASSUME_NONNULL_BEGIN @class DoricLibrary; @class DoricJSEngine; +@protocol DoricPerformanceGlobalAnchorHookProtocol; @interface DoricRegistry : NSObject @property(nonatomic, strong) UIImage *defaultPlaceHolderImage; @property(nonatomic, strong) UIImage *defaultErrorImage; +@property(nonatomic, strong) id globalPerformanceAnchorHook; - (instancetype)initWithJSEngine:(DoricJSEngine *)jsEngine; diff --git a/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m b/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m index 11650a62..fbd91362 100644 --- a/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m +++ b/doric-iOS/Pod/Classes/Engine/DoricJSEngine.m @@ -58,7 +58,11 @@ @implementation DoricJSEngine - (instancetype)init { if (self = [super init]) { _initialized = NO; + _registry = [[DoricRegistry alloc] initWithJSEngine:self]; _profile = [[DoricPerformanceProfile alloc] initWithName:@"JSEngine"]; + if (_registry.globalPerformanceAnchorHook) { + [_profile addAnchorHook:_registry.globalPerformanceAnchorHook]; + } [_profile prepare:@"Init"]; _jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil]; [_jsThread start]; @@ -95,7 +99,6 @@ - (instancetype)init { @"localeLanguage": [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode] ?: @"", @"localeCountry": [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] ?: @"", }.mutableCopy; - self.registry = [[DoricRegistry alloc] initWithJSEngine:self]; [self ensureRunOnJSThread:^() { [self.profile start:@"Init"]; self.timers = [[NSMutableDictionary alloc] init]; diff --git a/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h index 20659ac0..9f2190d5 100644 --- a/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h +++ b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.h @@ -31,9 +31,17 @@ NS_ASSUME_NONNULL_BEGIN end:(NSNumber *)end; @end +@class DoricPerformanceProfile; + +@protocol DoricPerformanceGlobalAnchorHookProtocol +- (void)onAnchorName:(NSString *)name + prepare:(NSNumber *)prepare + start:(NSNumber *)start + end:(NSNumber *)end + in:(DoricPerformanceProfile *)profile; +@end @interface DoricPerformanceProfile : NSObject -@property(nonatomic, strong) NSMutableDictionary *anchorMap; - (instancetype)initWithName:(NSString *)name; diff --git a/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m index 0fba8acf..f0b2ad42 100644 --- a/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m +++ b/doric-iOS/Pod/Classes/Performance/DoricPerformanceProfile.m @@ -28,6 +28,7 @@ @interface DoricPerformanceProfile () @property(nonatomic, strong) dispatch_queue_t anchorQueue; @property(nonatomic, assign) BOOL enable; @property(nonatomic, strong) NSHashTable> *hooks; +@property(nonatomic, strong) NSMutableDictionary *anchorMap; @end @implementation DoricPerformanceProfile @@ -114,7 +115,11 @@ - (void)print:(NSString *)anchorName { @(end.integerValue - start.integerValue) ); for (id hook in self.hooks) { - [hook onAnchorName:anchorName prepare:prepare start:start end:end]; + if ([hook conformsToProtocol:@protocol(DoricPerformanceGlobalAnchorHookProtocol)]) { + [hook onAnchorName:anchorName prepare:end start:end end:end in:self]; + } else { + [hook onAnchorName:anchorName prepare:prepare start:start end:end]; + } } }); }