diff --git a/designer-base/src/com/fr/start/SplashStrategy.java b/designer-base/src/com/fr/start/SplashStrategy.java new file mode 100644 index 000000000..8f7673d5c --- /dev/null +++ b/designer-base/src/com/fr/start/SplashStrategy.java @@ -0,0 +1,34 @@ +package com.fr.start; + +/** + * 启动动画策略接口 + * + * @author vito + * @date 2018/6/1 + */ +public interface SplashStrategy { + + /** + * 显示启动动画窗口 + */ + void show(); + + /** + * 隐藏启动动画窗口 + */ + void hide(); + + /** + * 设置模块加载信息 + * + * @param text 更新的文字 + */ + void updateModuleLog(String text); + + /** + * 设置感谢文字 + * + * @param text 更新的文字 + */ + void updateThanksLog(String text); +} diff --git a/designer-realize/src/com/fr/start/Designer.java b/designer-realize/src/com/fr/start/Designer.java index ae382d2fa..770810127 100644 --- a/designer-realize/src/com/fr/start/Designer.java +++ b/designer-realize/src/com/fr/start/Designer.java @@ -43,10 +43,13 @@ import com.fr.general.ComparatorUtils; import com.fr.general.Inter; import com.fr.module.Module; import com.fr.module.ModuleContext; +import com.fr.stable.OperatingSystem; import com.fr.stable.ProductConstants; import com.fr.stable.StableUtils; import com.fr.stable.StringUtils; import com.fr.stable.xml.XMLTools; +import com.fr.start.fx.SplashFx; +import com.fr.start.jni.SplashMac; import com.fr.start.module.StartupArgs; import javax.swing.JComponent; @@ -85,7 +88,8 @@ public class Designer extends BaseDesigner { * @param args 参数 */ public static void main(String[] args) { - + SplashContext.getInstance().registerSplash(createSplash()); + SplashContext.getInstance().show(); Module designerRoot = ModuleContext.parseRoot("designer-startup.xml"); //传递启动参数 designerRoot.setSingleton(StartupArgs.class, new StartupArgs(args)); @@ -94,6 +98,17 @@ public class Designer extends BaseDesigner { // 预启动一下 StartServer.start(); } + + } + + private static SplashStrategy createSplash() { + // 这里可以开接口加载自定义启动画面 + if (OperatingSystem.isWindows()) { + return new SplashFx(); + } else if (OperatingSystem.isMacOS()) { + return new SplashMac(); + } + return new SplashFx(); } public Designer(String[] args) { diff --git a/designer-realize/src/com/fr/start/SplashContext.java b/designer-realize/src/com/fr/start/SplashContext.java new file mode 100644 index 000000000..f8a1a3f67 --- /dev/null +++ b/designer-realize/src/com/fr/start/SplashContext.java @@ -0,0 +1,142 @@ +package com.fr.start; + +import com.fr.base.FRContext; +import com.fr.design.mainframe.bbs.BBSConstants; +import com.fr.general.Inter; +import com.fr.stable.StringUtils; +import com.fr.stable.module.ModuleAdapter; +import com.fr.stable.module.ModuleListener; + +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 启动动画策略 + * + * @author vito + * @date 2018/6/5 + */ +public class SplashContext { + + private static final SplashContext SPLASH_CONTEXT = new SplashContext(); + + private SplashStrategy splashStrategy; + + private String moduleID = ""; + private int loadingIndex = 0; + private String[] loading = new String[]{"..", "....", "......"}; + + private static final String GUEST = getRandomUser(); + + private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + private ModuleListener listener; + + + public static SplashContext getInstance() { + return SPLASH_CONTEXT; + } + + private SplashContext() { + } + + /** + * 注册具体的启动动画 + */ + public void registerSplash(SplashStrategy splashStrategy) { + this.splashStrategy = splashStrategy; + } + + /** + * 注册监听 + */ + public ModuleListener getModuleListener() { + initListener(); + return listener; + } + + /** + * 展示启动动画 + */ + public void show() { + splashStrategy.show(); + } + + /** + * 隐藏启动动画 + */ + public void hide() { + splashStrategy.hide(); + // 窗口关闭后取消定时获取模块信息的timer + scheduler.shutdown(); + // 一次性 + splashStrategy = null; + } + + private void initListener() { + scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + loadingIndex++; + updateModuleLog(moduleID.isEmpty() ? StringUtils.EMPTY : moduleID + loading[loadingIndex % 3]); + } + }, 0, 300, TimeUnit.MILLISECONDS); + + listener = new ModuleAdapter() { + @Override + public void onStartBefore(String moduleName, String moduleI18nName) { + moduleID = moduleI18nName; + loadingIndex++; + updateModuleLog(moduleID.isEmpty() ? StringUtils.EMPTY : moduleID + loading[loadingIndex % 3]); + + } + }; + showThanks(); + } + + private void updateModuleLog(String text) { + splashStrategy.updateModuleLog(text); + } + + private void updateThanksLog(String text) { + splashStrategy.updateThanksLog(text); + } + + /** + * 获取随机感谢人员 + */ + private static String getRandomUser() { + String[] allGuest = BBSConstants.getAllGuest(); + if (allGuest.length == 0) { + return StringUtils.EMPTY; + } + int num = new Random().nextInt(allGuest.length); + return StringUtils.BLANK + allGuest[num]; + } + + /** + * 展示感谢信息 + */ + private void showThanks() { + if (shouldShowThanks()) { + updateThanksLog(Inter.getLocText("FR-Designer_Thanks-To") + GUEST); + } + } + + /** + * 是否显示鸣谢面板 + */ + private boolean shouldShowThanks() { + Locale[] hideLocales = {Locale.CHINA, Locale.TAIWAN}; + for (Locale loc : hideLocales) { + if (FRContext.getLocale().equals(loc)) { + return true; + } + } + return false; + } + +} diff --git a/designer-realize/src/com/fr/start/fx/FastGifImage.java b/designer-realize/src/com/fr/start/fx/FastGifImage.java new file mode 100644 index 000000000..9049850c3 --- /dev/null +++ b/designer-realize/src/com/fr/start/fx/FastGifImage.java @@ -0,0 +1,210 @@ +package com.fr.start.fx; + +import com.sun.imageio.plugins.gif.GIFImageReader; +import com.sun.imageio.plugins.gif.GIFImageReaderSpi; +import com.sun.javafx.tk.ImageLoader; +import com.sun.javafx.tk.PlatformImage; +import javafx.animation.KeyFrame; +import javafx.animation.Timeline; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.scene.image.WritableImage; +import javafx.util.Duration; + +import javax.imageio.stream.FileImageInputStream; +import java.io.File; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.regex.Pattern; + +/** + * 边加载边播放的gif加载器 + * + * @author daniel + */ +public class FastGifImage extends WritableImage { + private String url; + private int gifCount; + + public FastGifImage(String url, int w, int h) { + super(w, h); + this.url = validateUrl(url); + seekCount(); + initialize(); + } + + /** + * 给出gif帧数,加快加载速度 + * + * @param url gif url + * @param gifCount gif帧数 + * @param w 宽 + * @param h 高 + */ + public FastGifImage(String url, int gifCount, int w, int h) { + super(w, h); + this.url = validateUrl(url); + this.gifCount = gifCount; + initialize(); + } + + private void seekCount() { + try { + GIFImageReaderSpi spi = new GIFImageReaderSpi(); + GIFImageReader gifReader = (GIFImageReader) spi.createReaderInstance(); + gifReader.setInput(new FileImageInputStream(new File(new URI(url)))); + gifCount = gifReader.getNumImages(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static final Pattern URL_QUICKMATCH = Pattern.compile("^\\p{Alpha}[\\p{Alnum}+.-]*:.*$"); + + private static String validateUrl(final String url) { + if (url == null) { + throw new NullPointerException("URL must not be null"); + } + + if (url.trim().isEmpty()) { + throw new IllegalArgumentException("URL must not be empty"); + } + + try { + if (!URL_QUICKMATCH.matcher(url).matches()) { + final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + URL resource; + if (url.charAt(0) == '/') { + resource = contextClassLoader.getResource(url.substring(1)); + } else { + resource = contextClassLoader.getResource(url); + } + if (resource == null) { + throw new IllegalArgumentException("Invalid URL or resource not found"); + } + return resource.toString(); + } + // Use URL constructor for validation + return new URL(url).toString(); + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid URL" + e.getMessage()); + } catch (final MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL" + e.getMessage()); + } + } + + + private void finishImage(Exception e) { + e.printStackTrace(); + } + + private void finishImage(ImageLoader loader) { + final Exception loadingException = loader.getException(); + if (loadingException != null) { + finishImage(loadingException); + return; + } + initializeAnimatedImage(loader); + } + + // Generates the animation Timeline for multiframe images. + private void initializeAnimatedImage(ImageLoader loader) { + + animation = new Animation(this, loader); + animation.start(); + } + + // Support for animated images. + private Animation animation; + + private static final class Animation { + final WeakReference imageRef; + final Timeline timeline; + final ImageLoader loader; + + public Animation(final FastGifImage image, final ImageLoader loader) { + this.loader = loader; + imageRef = new WeakReference(image); + timeline = new Timeline(); + timeline.setCycleCount(Timeline.INDEFINITE); + + final int frameCount = loader.getFrameCount(); + int duration = 0; + + for (int i = 0; i < frameCount; ++i) { + addKeyFrame(i, duration); + duration = duration + loader.getFrameDelay(i); + } + + // Note: we need one extra frame in the timeline to define how long + // the last frame is shown, the wrap around is "instantaneous" + addKeyFrame(0, duration); + } + + public void start() { + timeline.play(); + } + + public void stop() { + timeline.stop(); + } + + private void updateImage(final int frameIndex) { + final FastGifImage image = imageRef.get(); + if (image != null) { + image.setPlatformImagePropertyImpl( + loader.getFrame(frameIndex)); + } else { + timeline.stop(); + } + } + + private void addKeyFrame(final int index, final double duration) { + timeline.getKeyFrames().add( + new KeyFrame(Duration.millis(duration), + new EventHandler() { + @Override + public void handle(Event event) { + updateImage(index); + } + } + )); + } + } + + private static Method method; + + static { + try { + method = FastGifImage.class.getSuperclass().getSuperclass().getDeclaredMethod("platformImagePropertyImpl"); + method.setAccessible(true); + } catch (Exception e) { + + } + } + + private void setPlatformImagePropertyImpl(PlatformImage image) { + try { + Object o = method.invoke(this); + Method method = o.getClass().getDeclaredMethod("set", Object.class); + method.setAccessible(true); + method.invoke(o, image); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + + + private void initialize() { + finishImage(new PrismImageLoader2(url, gifCount, (int) getRequestedWidth(), (int) getRequestedHeight(), isPreserveRatio(), isSmooth())); + } + +} diff --git a/designer-realize/src/com/fr/start/fx/PrismImageLoader2.java b/designer-realize/src/com/fr/start/fx/PrismImageLoader2.java new file mode 100644 index 000000000..e943df011 --- /dev/null +++ b/designer-realize/src/com/fr/start/fx/PrismImageLoader2.java @@ -0,0 +1,211 @@ +package com.fr.start.fx; + +import com.sun.javafx.iio.ImageFrame; +import com.sun.javafx.iio.ImageLoadListener; +import com.sun.javafx.iio.ImageLoader; +import com.sun.javafx.iio.ImageMetadata; +import com.sun.javafx.iio.ImageStorageException; +import com.sun.javafx.iio.common.ImageTools; +import com.sun.javafx.iio.gif.GIFImageLoaderFactory; +import com.sun.javafx.tk.PlatformImage; +import com.sun.prism.Image; +import com.sun.prism.impl.PrismSettings; +import sun.util.logging.PlatformLogger; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 边加载边播放的gif加载器 + * + * @author daniel + */ +class PrismImageLoader2 implements com.sun.javafx.tk.ImageLoader { + + private static PlatformLogger imageioLogger = null; + + private Image[] images; + private int[] delayTimes; + private int width; + private int height; + private int gifCount = 1; + private Exception exception; + + public PrismImageLoader2(final String url, int gifCount, final int width, final int height, + final boolean preserveRatio, final boolean smooth) { + this.gifCount = gifCount; + images = new Image[gifCount]; + delayTimes = new int[gifCount]; + this.width = width; + this.height = height; + new Thread() { + @Override + public void run() { + InputStream inputStream = null; + try { + inputStream = ImageTools.createInputStream(url); + loadAll(inputStream, width, height, preserveRatio, smooth); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + }.start(); + + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public int getFrameCount() { + return gifCount; + } + + @Override + public PlatformImage getFrame(int index) { + while (images[index] == null) { + synchronized (this) { + if (images[index] == null) { + try { + this.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + return images[index]; + } + + + @Override + public int getFrameDelay(int index) { +// while (images[0] == null) { +// synchronized (this) { +// if(images[0] == null) { +// try { +// this.wait(); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// } +// } +// return 0; +// } +// return delayTimes[0]; + // 直接使用第一帧的时间 + return 40; + } + + @Override + public int getLoopCount() { + return 0; + } + + @Override + public Exception getException() { + return exception; + } + + + private void loadAll(InputStream stream, int w, int h, + boolean preserveRatio, boolean smooth) { + ImageLoadListener listener = new PrismLoadListener(); + + try { + ImageLoader loader = null; + loader = GIFImageLoaderFactory.getInstance().createImageLoader(stream); + loader.addListener(listener); + + for (int i = 0; i < gifCount; i++) { + ImageFrame imageFrame = loader.load(i, w, h, preserveRatio, smooth); + images[i] = convert(imageFrame); + synchronized (this) { + this.notify(); + } + } + } catch (ImageStorageException e) { + handleException(e); + } catch (Exception e) { + handleException(e); + } + } + + private void handleException(final ImageStorageException isException) { + // unwrap ImageStorageException if possible + final Throwable exceptionCause = isException.getCause(); + if (exceptionCause instanceof Exception) { + handleException((Exception) exceptionCause); + } else { + handleException((Exception) isException); + } + } + + private void handleException(final Exception exception) { + if (PrismSettings.verbose) { + exception.printStackTrace(System.err); + } + this.exception = exception; + } + + private Image convert(ImageFrame imgFrames) { + ImageFrame frame = imgFrames; + Image image = Image.convertImageFrame(frame); + ImageMetadata metadata = frame.getMetadata(); + if (metadata != null) { + Integer delay = metadata.delayTime; + if (delay != null) { + delayTimes[0] = delay.intValue(); + } + } + return image; + } + + /** + * Returns the PlatformLogger for logging imageio-related activities. + */ + private static synchronized PlatformLogger getImageioLogger() { + if (imageioLogger == null) { + imageioLogger = PlatformLogger.getLogger("imageio"); + } + + return imageioLogger; + } + + private class PrismLoadListener implements ImageLoadListener { + @Override + public void imageLoadWarning(ImageLoader loader, String message) { + getImageioLogger().warning(message); + } + + @Override + public void imageLoadProgress(ImageLoader loader, + float percentageComplete) { + // progress only matters when backgroundLoading=true, but + // currently we are relying on AbstractRemoteResource for tracking + // progress of the InputStream, so there's no need to implement + // this for now; eventually though we might want to consider + // moving away from AbstractRemoteResource and instead use + // the built-in support for progress in the javafx-iio library... + } + + @Override + public void imageLoadMetaData(ImageLoader loader, ImageMetadata metadata) { + // We currently have no need to listen for ImageMetadata ready. + } + } + +} diff --git a/designer-realize/src/com/fr/start/fx/SplashFx.java b/designer-realize/src/com/fr/start/fx/SplashFx.java new file mode 100644 index 000000000..34990509d --- /dev/null +++ b/designer-realize/src/com/fr/start/fx/SplashFx.java @@ -0,0 +1,49 @@ +package com.fr.start.fx; + +import com.fr.start.SplashStrategy; +import javafx.application.Application; +import javafx.application.Platform; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * JavaFx方式启动启动动画。这种方式在mac下与 + * swing一起启动会会出现线程死锁,jvm等问题, + * 所以这个方式仅用于windows上。 + * + * @author vito + * @date 2018/6/4 + * @see com.fr.start.jni.SplashMac + */ +public class SplashFx implements SplashStrategy { + + private SplashFxWindow test; + private static final ExecutorService SERVICE = Executors.newSingleThreadExecutor(); + + @Override + public void show() { + SERVICE.execute(new Runnable() { + @Override + public void run() { + Application.launch(SplashFxWindow.class); + } + }); + test = SplashFxWindow.waitForStartUpTest(); + } + + @Override + public void hide() { + Platform.exit(); + } + + @Override + public void updateModuleLog(String text) { + test.updateModuleInfo(text); + } + + @Override + public void updateThanksLog(String text) { + test.updateThanks(text); + } +} diff --git a/designer-realize/src/com/fr/start/fx/SplashFxWindow.java b/designer-realize/src/com/fr/start/fx/SplashFxWindow.java new file mode 100644 index 000000000..0685943d9 --- /dev/null +++ b/designer-realize/src/com/fr/start/fx/SplashFxWindow.java @@ -0,0 +1,133 @@ +package com.fr.start.fx; + +import com.bulenkov.iconloader.util.JBUI; +import com.fr.base.FRContext; +import com.fr.stable.OperatingSystem; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +import java.util.concurrent.CountDownLatch; + + +/** + * JavaFx启动动画窗口 + * + * @author vito + */ +public class SplashFxWindow extends Application { + + private static float JBUI_INIT_SCALE = JBUI.scale(1f); + + private static final String ARIAL_FONT_NAME = "Arial"; + private static final String PF_FONT_NAME = "PingFang"; + private static final String YAHEI_FONT_NAME = "Microsoft YaHei"; + private static final int MODULE_INFO_LEFT_MARGIN = 36; + private static final int MODULE_INFO_BOTTOM_MARGIN = 28; + private static final int THINKS_BOTTOM_RIGHT = 35; + private static final int THINKS_BOTTOM_MARGIN = 27; + private static final int WINDOW_WIDTH = 640; + private static final int WINDOW_HEIGHT = 360; + private static final int FONT = 12; + private static final String THINKS_COLOR = "#82b1ce"; + + private static final CountDownLatch LATCH = new CountDownLatch(1); + private static SplashFxWindow app = null; + + private Text moduleInfo; + private Text thanks; + + private static int uiScale(int i) { + return (int) (i * JBUI_INIT_SCALE); + } + + /** + * 获取当前运行实例。黑科技 + * + * @return 运行实例 + */ + public static SplashFxWindow waitForStartUpTest() { + try { + LATCH.await(); + } catch (InterruptedException e) { + FRContext.getLogger().error(e.getMessage(), e); + } + return app; + } + + private static void setApp(SplashFxWindow window) { + app = window; + LATCH.countDown(); + } + + public SplashFxWindow() { + setApp(this); + } + + @Override + public void start(Stage primaryStage) { + AnchorPane root = new AnchorPane(); + primaryStage.initStyle(StageStyle.TRANSPARENT); + long t = System.currentTimeMillis(); + Image image = new FastGifImage("com/fr/base/images/oem/splash_10.gif", 254, WINDOW_WIDTH, WINDOW_HEIGHT); + + ImageView gif = new ImageView(image); + + AnchorPane.setBottomAnchor(gif, 0d); + AnchorPane.setTopAnchor(gif, 0d); + AnchorPane.setLeftAnchor(gif, 0d); + AnchorPane.setRightAnchor(gif, 0d); + Font font; + if (OperatingSystem.isWindows()) { + font = new Font(YAHEI_FONT_NAME, uiScale(FONT)); + } else if (OperatingSystem.isMacOS()) { + font = new Font(PF_FONT_NAME, uiScale(FONT)); + } else { + font = new Font(ARIAL_FONT_NAME, uiScale(FONT)); + } + + moduleInfo = new Text(); + moduleInfo.setFont(font); + moduleInfo.setFill(Color.WHITE); + AnchorPane.setLeftAnchor(moduleInfo, (double) uiScale(MODULE_INFO_LEFT_MARGIN)); + AnchorPane.setBottomAnchor(moduleInfo, (double) uiScale(MODULE_INFO_BOTTOM_MARGIN)); + thanks = new Text(); + thanks.setFont(font); + thanks.setFill(Color.valueOf(THINKS_COLOR)); + AnchorPane.setRightAnchor(thanks, (double) uiScale(THINKS_BOTTOM_RIGHT)); + AnchorPane.setBottomAnchor(thanks, (double) uiScale(THINKS_BOTTOM_MARGIN)); + + root.getChildren().add(gif); + root.getChildren().add(moduleInfo); + root.getChildren().add(thanks); + + Scene scene = new Scene(root, WINDOW_WIDTH, WINDOW_HEIGHT, null); + primaryStage.setScene(scene); + primaryStage.show(); + } + + /** + * 更新模块信息 + * + * @param s 文字 + */ + public void updateModuleInfo(String s) { + moduleInfo.setText(s); + } + + /** + * 更新欢迎信息 + * + * @param s 文字 + */ + public void updateThanks(String s) { + thanks.setText(s); + } +} diff --git a/designer-realize/src/com/fr/start/jni/SplashJNI.java b/designer-realize/src/com/fr/start/jni/SplashJNI.java new file mode 100644 index 000000000..e71a44ca2 --- /dev/null +++ b/designer-realize/src/com/fr/start/jni/SplashJNI.java @@ -0,0 +1,69 @@ +package com.fr.start.jni; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +/** + * Splash JNI调用。jni类改名或者移包之后 + * 必须重新编译动态库 + * + * @author vito + * @date 2018/6/4 + */ +public class SplashJNI { + + static { + try { + System.setProperty("java.library.path", "."); + System.loadLibrary("splash"); + } catch (UnsatisfiedLinkError e) { + loadLibraryFromJar("/com/fr/start/jni/splash.dylib"); + } + } + + /** + * 显示启动动画窗口 + */ + public native void show(String path); + + /** + * 隐藏启动动画窗口 + */ + public native void hide(); + + /** + * 设置模块加载信息 + */ + public native void updateModuleLog(String text); + + /** + * 设置感谢文字 + */ + public native void updateThanksLog(String text); + + /** + * 从jar中加载动态库 + * + * @param path 路径,如/com/a/b + * @throws UnsatisfiedLinkError 没有找到合适的动态库 + */ + private static void loadLibraryFromJar(String path) throws UnsatisfiedLinkError { + try (InputStream inputStream = SplashJNI.class.getResourceAsStream(path)) { + File tempLib = File.createTempFile(path, ""); + + byte[] buffer = new byte[1024]; + int read = -1; + + try (FileOutputStream fileOutputStream = new FileOutputStream(tempLib)) { + while ((read = inputStream.read(buffer)) != -1) { + fileOutputStream.write(buffer, 0, read); + } + } + + System.load(tempLib.getAbsolutePath()); + } catch (Exception e) { + throw new UnsatisfiedLinkError("Unable to open " + path + " from jar file."); + } + } +} diff --git a/designer-realize/src/com/fr/start/jni/SplashMac.java b/designer-realize/src/com/fr/start/jni/SplashMac.java new file mode 100644 index 000000000..16ff8fc50 --- /dev/null +++ b/designer-realize/src/com/fr/start/jni/SplashMac.java @@ -0,0 +1,87 @@ +package com.fr.start.jni; + +import com.fr.stable.ProductConstants; +import com.fr.stable.StableUtils; +import com.fr.start.SplashContext; +import com.fr.start.SplashStrategy; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * mac上使用jni方式绘制gif。不使用javafx有两个原因: + * 1.mac上javafx和swing同时启动会导致卡死; + * 2.platform.exit会导致设计器崩溃 + * + * @author vito + * @see com.fr.start.fx.SplashFx + */ +public class SplashMac implements SplashStrategy { + + private static final String SPLASH_CACHE_NAME = "splash_10.gif"; + private static final String SPLASH_PATH = "/com/fr/base/images/oem/splash_10.gif"; + + private SplashJNI jni; + + public SplashMac() { + jni = new SplashJNI(); + } + + /** + * 将jar中的资源拷贝到缓存文件夹 + * + * @return 路径 + * @throws IOException 拷贝失败 + */ + private static String loadResFromJar() throws UnsatisfiedLinkError { + File tempLib = null; + try (InputStream inputStream = SplashContext.class.getResourceAsStream(SplashMac.SPLASH_PATH)) { + tempLib = new File(StableUtils.pathJoin(ProductConstants.getEnvHome(), SPLASH_CACHE_NAME)); + byte[] buffer = new byte[1024]; + int read = -1; + try (FileOutputStream fileOutputStream = new FileOutputStream(tempLib)) { + while ((read = inputStream.read(buffer)) != -1) { + fileOutputStream.write(buffer, 0, read); + } + } + return tempLib.getAbsolutePath(); + } catch (IOException e) { + tempLib.deleteOnExit(); + throw new UnsatisfiedLinkError("Unable to open " + SplashMac.SPLASH_PATH + " from jar file."); + } + } + + @Override + public void show() { + if (jni != null) { + File splash = new File(StableUtils.pathJoin(ProductConstants.getEnvHome(), SPLASH_CACHE_NAME)); + String path = splash.exists() ? splash.getAbsolutePath() : loadResFromJar(); + jni.show(path); + } + } + + @Override + public void hide() { + if (jni != null) { + jni.hide(); + jni = null; + } + } + + @Override + public void updateModuleLog(String text) { + if (jni != null) { + jni.updateModuleLog(text); + } + + } + + @Override + public void updateThanksLog(String text) { + if (jni != null) { + jni.updateThanksLog(text); + } + } +} diff --git a/designer-realize/src/com/fr/start/jni/splash.dylib b/designer-realize/src/com/fr/start/jni/splash.dylib new file mode 100755 index 000000000..3e19cd20e Binary files /dev/null and b/designer-realize/src/com/fr/start/jni/splash.dylib differ diff --git a/designer-realize/src/com/fr/start/module/DesignerStartup.java b/designer-realize/src/com/fr/start/module/DesignerStartup.java index dd2b09c53..3f150e043 100644 --- a/designer-realize/src/com/fr/start/module/DesignerStartup.java +++ b/designer-realize/src/com/fr/start/module/DesignerStartup.java @@ -5,8 +5,7 @@ import com.fr.stable.CoreActivator; import com.fr.stable.module.ModuleListener; import com.fr.start.Designer; import com.fr.start.EnvSwitcher; -import com.fr.start.ReportSplashPane; -import com.fr.start.SplashWindow; +import com.fr.start.SplashContext; import com.fr.startup.activators.BasicActivator; /** @@ -16,12 +15,11 @@ public class DesignerStartup extends Activator { @Override public void start() { - startSub(PreStartActivator.class); //启动基础部分 startSub(BasicActivator.class); - //启动画面 - SplashWindow splashWindow = createSplashWindow(); + //启动画面注册监听,必须在初始化国际化之后注册监听 + registerSplashListener(); String[] args = getModule().upFindSingleton(StartupArgs.class).get(); Designer designer = new Designer(args); //启动env @@ -33,17 +31,15 @@ public class DesignerStartup extends Activator { //启动设计器界面 designer.show(args); //启动画面结束 - splashWindow.setVisible(false); - splashWindow.dispose(); + SplashContext.getInstance().hide(); startSub(StartFinishActivator.class); } - private SplashWindow createSplashWindow() { - - ReportSplashPane reportSplashPane = new ReportSplashPane(); - SplashWindow splashWindow = new SplashWindow(reportSplashPane); - getModule().setSingleton(ModuleListener.class, reportSplashPane.getModuleListener()); - return splashWindow; + /** + * 注册启动动画监听器 + */ + private void registerSplashListener() { + getModule().setSingleton(ModuleListener.class, SplashContext.getInstance().getModuleListener()); } @Override