前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一图抵千言《ARouter简明扼要原理分析》

一图抵千言《ARouter简明扼要原理分析》

作者头像
g小志
发布于 2022-03-29 07:15:04
发布于 2022-03-29 07:15:04
78000
代码可运行
举报
文章被收录于专栏:Android常用基础Android常用基础
运行总次数:0
代码可运行

配置

Kotlin项目:

module App:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
apply plugin: 'kotlin-kapt'

defaultConfig{
 javaCompileOptions {
   annotationProcessorOptions {
   //AROUTER_MODULE_NAME必配项 用于拼接生成文件名 AROUTER_GENERATE_DOC 
   // AROUTER_GENERATE_DOC = enable 生成Json文档
   // 生成的文档路径 : build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json
   arguments = [AROUTER_MODULE_NAME:project_name,AROUTER_GENERATE_DOC:"enable"]
     }
   }
}

dependencies{
  api 'com.alibaba:arouter-api:1.5.0'
  kapt 'com.alibaba:arouter-compiler:1.2.2'
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//项目根目录build.gradle
dependencies {
  classpath "com.alibaba:arouter-register:1.0.2"
}

源码流程分析

三个关键阶段

ARouter源码分析.png

自定义处理器工作流程:

整体流程.png

自定义处理器源码分析:结构图

ARoute注解源码解析-1.png

生成类的关系

调用类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Route(path = "/kotlin/test")
class KotlinTestActivity : Activity() {

    @Autowired
    @JvmField var name: String? = null
    @Autowired
    @JvmField var age: Int? = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        ARouter.getInstance().inject(this)  // Start auto inject.

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_kotlin_test)

        content.text = "name = $name, age = $age"
    }
}

ARouter生成类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class KotlinTestActivity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    KotlinTestActivity substitute = (KotlinTestActivity)target;
    substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
  }
}

ARouter.getInstance().inject(this)

这段代码最终会利用当前类名和规则,拼接成KotlinTestActivity$$ARouter$$Autowired的全类名,然后利用反射传进对象。然后执行inject(this); 然后里面会初始化传输字段序列化服务,然后强转target,开始赋值数据

生成类文件的关系

RouteProcessor生成文件.png

由此可总结出下面整体工作流程

ARouter整体工作流程

整体工作流程.png

运行时原理分析

初始化工作流程分析
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  //初始化
  ARouter.init(getApplication());
  
  _ARouter.init(application)   

  LogisticsCenter.init(mContext, executor)  

LogisticsCenter.init(mContext, executor):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //load by plugin first
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                //1 生成文件所有文件的全类名字符串集合
                Set<String> routerMap;

                // It will rebuild router map every times when debuggable.
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                    // These class was generated by arouter-compiler.
                    //2. 赋值集合
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }

                    PackageUtils.updateVersion(context);    // Save new version name when router map update finishes.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }

                logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
                startInit = System.currentTimeMillis();

                 //3. 遍历集合
                for (String className : routerMap) {
                // 这里装在了3中类别的文件: (1) Root文件
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                        //(2) Interceptor 文件
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                         // //(3) Provider 文件  
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }

            logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

可以看出没加载AutoWired文件,也就是说@AutoWired注解字段 在inject()时去创建对象赋值的。 反射找到对象并将Warehouse中的结合作为参数传递进入,把信息装载到内存。注意,这里只是加载了信息,但信息里面的具体内容并未创建。什么意思呢?以Provider为例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ARouter$$Providers$$modulejava implements IProviderGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> providers) {
    providers.put("com.alibaba.android.arouter.demo.service.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
    providers.put("com.alibaba.android.arouter.demo.module1.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
  }
}

反射生成ARouter$$Providers$$modulejava对象,但我们用的是com.alibaba.android.arouter.demo.service.HelloServiceHelloService这个具体类。但这个类这是并未实例化,只有用到的时候才回去实例化创建。其他同理。

LogisticsCenter.init(mContext, executor): 还用到两个重要的类和方法: ClassUtils/Warehouse 1. ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static Set<String> getFileNameByPackageName(Context context, final String packageName) 
throws PackageManager.NameNotFoundException, IOException, InterruptedException {
        final Set<String> classNames = new HashSet<>();

        List<String> paths = getSourcePaths(context);
        //线程同步
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());

        for (final String path : paths) {
            DefaultPoolExecutor.getInstance().execute(new Runnable() {
                @Override
                public void run() {
                //Dex文件
                    DexFile dexfile = null;

                    try {
                        if (path.endsWith(EXTRACTED_SUFFIX)) {
                            //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
                            dexfile = DexFile.loadDex(path, path + ".tmp", 0);
                        } else {
                            dexfile = new DexFile(path);
                        }

                        Enumeration<String> dexEntries = dexfile.entries();
                        while (dexEntries.hasMoreElements()) {
                            String className = dexEntries.nextElement();
                            //核心判断 packageName是我们上面传入的参数ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes"
                            if (className.startsWith(packageName)) {
                                classNames.add(className);
                            }
                        }
                    } catch (Throwable ignore) {
                        Log.e("ARouter", "Scan map file in dex files made error.", ignore);
                    } finally {
                        if (null != dexfile) {
                            try {
                                dexfile.close();
                            } catch (Throwable ignore) {
                            }
                        }
                        //也就是说,如果初始化加载流程没有走完,路由操作将会阻塞,知道加载流程完成
                        parserCtl.countDown();
                    }
                }
            });
        }

        parserCtl.await();

        Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }

如果初始化加载流程没有走完,路由操作将会阻塞,直到加载流程完成

Warehouse 相当于一个加载信息装载的容器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
class Warehouse {
    // Cache route and metas  
    //groupsIndex 装载ARouter$$Root$$moduleName 中的Root文件  <groupName,Group.class>
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    //routes 按需加载完成后 把加载的数据存到routes 集合中,等项目中用到的时候,找到集合中需要的元素 再去实例化对象
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
   //项目中用到的时候,找到集合中需要的元素 再去实例化对象 
    static Map<Class, IProvider> providers = new HashMap<>();
    // //每个服务的原始信息加载完成后存放到这里
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // Cache interceptor   
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
    static List<IInterceptor> interceptors = new ArrayList<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}

整体工作流程.png

路由过程源码分析
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    ARouter.getInstance()
                        .build("/kotlin/test")
                        .withString("name", "老王")
                        .withInt("age", 23)
                        .navigation();

.build("/kotlin/test") 查找分组,构建Postcard对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
   protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
        // 重定向路由路径Service 如果想自定义 则实现PathReplaceService 
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
            //通过forString(path) 返回修改后的Path
                path = pService.forString(path);
            }
            //继续往下走 
            return build(path, extractGroup(path), true);
        }
    }   

extractGroup(path):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 private String extractGroup(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
        }

        try {
        //关键代码 defaultGroup 默认分组 以路由路径 第一个节点为分组名称
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            if (TextUtils.isEmpty(defaultGroup)) {
                throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
            } else {
                return defaultGroup;
            }
        } catch (Exception e) {
            logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
            return null;
        }
    }

build(path, extractGroup(path), true);:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  protected Postcard build(String path, String group, Boolean afterReplace) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            if (!afterReplace) {
                PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
                if (null != pService) {
                    path = pService.forString(path);
                }
            }
            return new Postcard(path, group);
        }
    }

创建:Postcard对象

.withString("name", "老王").withInt("age", 23)

构建参数mBundle对象。

.navigation(); 运行在异步线程中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
        if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
            // Pretreatment failed, navigation canceled.
            return null;
        }

        // Set context to postcard.
        postcard.setContext(null == context ? mContext : context);

        try {
        //给Postcard赋值其他数据
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {//出现异常的情况
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) {
                // Show friendly tips for user.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(mContext, "There's no route matched!\n" +
                                " Path = [" + postcard.getPath() + "]\n" +
                                " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                    }
                });
            }

            if (null != callback) {
            // 路由回调 如果不为空
                callback.onLost(postcard);
            } else {
                // No callback for this invoke, then we use the global degrade service.
                //反射创建全局降级服务  回调 onLost方法
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

            return null;
        }

        if (null != callback) {
        //回调路由成功的方法
            callback.onFound(postcard);
        }
         //如果绿色通道为false 则添加拦截器 
        if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        // 运行在线程池中
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                //继续处理完成后继续向下执行 
                    _navigation(postcard, requestCode, callback);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                    //打断路由 停止路由
                        callback.onInterrupt(postcard);
                    }

                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
         //继续处理完成后继续向下执行 
            return _navigation(postcard, requestCode, callback);
        }

        return null;
    }

LogisticsCenter.completion(postcard);

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }
        //routes是加载信息 但为实例化对象 之前提过  所以这里第一次肯定为空
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {
            // Maybe its does't exist, or didn't load.
            if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                    //对Warehouse.routes初始化 实例化分组对象 获取数据
                    addRouteGroupDynamic(postcard.getGroup(), null);

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }
                //重新调用自己
                completion(postcard);   // Reload
            }
        } else {
        //给postcard赋值其他信息
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need auto inject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }
             //类型判断 PROVIDER类型实例化对象 PROVIDER/FRAGMENT默认开启通道,不经过拦截
            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!");
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

 public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (Warehouse.groupsIndex.containsKey(groupName)){
            // If this group is included, but it has not been loaded
            // load this group first, because dynamic route has high priority.
            //对Warehouse.routes初始化 实例化分组对象 获取数据
            Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
            Warehouse.groupsIndex.remove(groupName);
        }

        // cover old group.
        if (null != group) {
            group.loadInto(Warehouse.routes);
        }
    }

_navigation(postcard, requestCode, callback);

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();

//判断路由类型
        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());

                // Set flags.
                int flags = postcard.getFlags();
                if (0 != flags) {
                    intent.setFlags(flags);
                }

                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                //context如果是Application 那么新建任务栈
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }

                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                    //Activity则调用startActivity
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });

                break;
            case PROVIDER:
            //返回LogisticsCenter.completion(postcard)方法中创建的对象
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class<?> fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
 //反射返回fragment实例
                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

路由结束。

整体工作流程(2).png

图片.png

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022.03.11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
8个免费LOGO生成器测评,Logo设计很简单
Logo设计,传统的设计软件之外,新一代的设计工具分为两类,一类是以创某贴、稿某设计、某画为主,根据现有的模板,修改成自己的logo名称,就可以完事了;的确很简单,只不过某些程度上,个性化创意就会相对不足,也会存在于他人logo一样的情况。另一类就是进阶一点的在线logo设计生成器,能够输入logo名称智能分析设计logo方案。国外这类软件比较多,中文logo设计生成器相对较少,我挑选了8个用过的作为今天的测评对象。
LOGO设计小能手
2021/10/27
15.3K0
用AI为企业设计品牌,Tailor Brands获1550万美元B轮融资
用AI为企业设计品牌,Tailor Brands获1550万美元B轮融资
数据猿
2018/05/31
6520
​如何用AI一键建立一个企业官网?都在说没人教你做我教你-优雅草卓伊凡
这里卓伊凡以我们星云智控科技的星云智控系统官网为例,所以首先我得有个很好的简单的介绍,很明显上一篇发过,我们已经有了
卓伊凡
2025/04/24
2250
Logojoy筹集450万美元,开发AI用于自动生成品牌logo
定制的品牌资产并不便宜。自由职业网站Upwork上的普通徽标设计师每小时收费约45美元,这只是基本费率。根据工作的性质和规模,它最终可能会接近每小时150美元。
AiTechYun
2018/12/18
5950
Logojoy筹集450万美元,开发AI用于自动生成品牌logo
有哪些好玩的生成器?LOGO生成器YYDS
http://alteredqualia.com/xg/examples/nebula_artefact.html
LOGO设计小能手
2021/12/30
1.5K0
分享一些实用的在线网站(免费)
该图片由Coffee Bean在Pixabay上发布 受到公众号「曾少贤」的启发,自己也整理了一些我在生活中经常使用到的在线网站,涉及的领域有图片、设计、文档、编程、学习等方面,这里分享出来,希望可以
出其东门
2019/07/19
5K0
分享一些实用的在线网站(免费)
2022年最酷的6个Logo设计灵感干货网站
当你打算给自己的店铺/公司/品牌设计一个logo时,第一选择就是去百度、小红书、微博上搜索好看的logo设计案例,给自己的logo设计准备一些灵感,但是缺乏一个系统专业的logo素材信息库,因此会耗费不少时间。今天呢,这篇文章将整理出,我精挑细选的6个最有用的logo设计灵感网站,分享给大家。
LOGO设计小能手
2022/11/04
1.2K0
2022年最酷的6个Logo设计灵感干货网站
解放生产力:5款互联网人必备的AI工具
作为国内最早自研的logo生成器之一,它的中文界面语言无障碍,并支持中英文logo设计。
LOGO设计小能手
2023/04/12
6340
不怕不会设计logo拉-本篇教你如何使用AI设计logo-如何快速用AI设计logo-附上AI绘图logo设计的咒语-优雅草央千澈-实战教程
最近有2个产品需要设计logo,一个叫做香蕉视频(banana),一个叫做FF社交(free firend-自由社交),还有个椰枣影院(迪拜影视软件dates),不过客户在logo上有一定的要求,但是又没有太高的预算,问了下设计师大概要800-1500RMB吧,然后去淘宝也问了下最少的也要250RMB,刚好优雅草一直在人工智能方面学习,因此本文就直接记录出来啦。
卓伊凡
2025/01/15
6160
自媒体创业必备的6个在线神器|免费效率工具
身边的朋友们现在都在搞点兼职副业,在抖音、小红书以及微博等建立自媒体账号,运营个人品牌线上创业,给自己拓宽收入来源增加一点抗风险的能力。工欲善其事必先利其器,如果你也有这个打算,今天这篇文章整理了6款宝藏创业工具神器,都是正在自媒体创业的朋友工作中经常会用到的免费工具,操作简单,省时省力,能够帮很大的忙少走弯路,一起看看吧。
LOGO设计小能手
2022/07/27
1.4K0
自媒体创业必备的6个在线神器|免费效率工具
AI ppt生成器 Tome
一款 AI 驱动的 PPT/幻灯片内容辅助生成工具。只需要输入一个标题或者一段特定的描述,AI 便会自动生成一套包括标题、大纲、内容、配图的完整 PPT。
@小森
2024/03/15
5760
AI ppt生成器 Tome
你也想用上这别具一格的网站 Logo 吗,赶紧进来手把手教会你!
有时我们会在网络上或日常生活中看到一些模仿各大知名网站风格 Logo 的图标 ,但对于没有平面设计能力的普通网友来说,想制作出类似效果的 Logo 门槛还是有点高。
iMike
2019/11/24
9000
WaterStamp —— 一个实用的网页水印生成器开发记
最近,我和 CodeBuddy 一起完成了一个名为 WaterStamp 的网页水印生成器项目。这个小工具主要用于给网页或图片添加水印,方便版权保护。整个项目采用了 Vue3 + Canvas 技术栈,配合 Element Plus 组件库实现,支持自定义水印内容、颜色、角度、不透明度等,能满足多种水印模式,包括整页重复、对角线、角落 Logo,同时还能实时预览并导出带水印的图片或者网页截图,还支持保存配置模板。整个开发过程,CodeBuddy 都是主动驱动的,我主要是观察和记录这个过程,下面分享下具体细节。
繁依Fanyi
2025/05/18
980
《揭秘AI领域的绝密武器——我整理了一份超级详细的AI工具合集》
随着人工智能(AI)技术的迅猛发展,我们仿佛置身于一个神奇的科幻世界。AI工具和应用不断涌现,为各行各业带来了巨大的变革和机遇。然而,对于普通用户而言,了解和掌握这些众多的AI工具可能会显得有些困难。但现在,你有机会揭开AI领域的绝密武器!
老码小张
2023/05/27
2.3K0
《揭秘AI领域的绝密武器——我整理了一份超级详细的AI工具合集》
关于AI绘画优雅草央千澈整理的一份咒语(与AI对话提示词-应用于AI绘图和AI生成视频)-本文长期更新-本次更新2025年1月15日更新-长期更新建议点赞收藏
1. 扁平化风格Logo (Flat Style Logo)这种设计以其简单的形状和鲜艳的颜色而引人注目,这使得它在任何尺寸都能很好地工作,无论是在手机屏幕还是在广告牌上。此外,它的简单设计也使得其加载速度更快,对于网站和移动应用的用户体验非常重要。Prompt: “Flat vector logo of a #insert item, #insert color, trending on Dribble.”
卓伊凡
2025/01/15
3250
【腾讯云AI绘画】AI绘画专栏之全网第一套系统的AI绘画课 零基础学会AI绘画
随着深度学习和计算机视觉的进步,AI绘画逐渐成为了可能。通过大量的数据训练和复杂的算法模型,AI可以学习艺术家的绘画风格,并生成以假乱真的艺术作品。这种技术的突破,使得AI能够模仿各种绘画风格,从古典到现代,从印象派到抽象艺术,尽显无限创造力。
疯狂的KK
2023/11/30
3.4K17
【腾讯云AI绘画】AI绘画专栏之全网第一套系统的AI绘画课 零基础学会AI绘画
分享15个全球顶尖的AIGC图片生成平台
人工智能正在改变许多行业的格局,而其中改变最直观和影响最大的就是AIGC领域的图像创作。
非喵鱼
2022/12/31
33.2K0
分享15个全球顶尖的AIGC图片生成平台
强烈推荐!一个可以把文本生成漂亮图表的AI做图神器
人类大脑处理图像比文字快60,000倍!一张精良图表3秒内能传达需5分钟才读完的内容。
一臻AI
2025/04/21
6940
强烈推荐!一个可以把文本生成漂亮图表的AI做图神器
拒绝加班的九大工具神器:logo在线生成爱了
8个工作必备的神器网站,解决你可能遇到的各种问题,职场新人表示很需要这些工具!有被好用到。
LOGO设计小能手
2022/02/25
1K0
Slidesgo:让演示文稿制作轻而易举
在数字化时代,信息的传递方式正在经历一场革命。传统的文字和图片已经无法满足我们对信息呈现的需求。现在,我们需要的是更加动态、更具吸引力的演示文稿。这正是 Slidesgo 出现的原因——一个致力于提供高质量、多样化演示模板的平台,让演示文稿成为人人触手可得的产品。
程序那些事儿
2024/10/22
5490
Slidesgo:让演示文稿制作轻而易举
推荐阅读
8个免费LOGO生成器测评,Logo设计很简单
15.3K0
用AI为企业设计品牌,Tailor Brands获1550万美元B轮融资
6520
​如何用AI一键建立一个企业官网?都在说没人教你做我教你-优雅草卓伊凡
2250
Logojoy筹集450万美元,开发AI用于自动生成品牌logo
5950
有哪些好玩的生成器?LOGO生成器YYDS
1.5K0
分享一些实用的在线网站(免费)
5K0
2022年最酷的6个Logo设计灵感干货网站
1.2K0
解放生产力:5款互联网人必备的AI工具
6340
不怕不会设计logo拉-本篇教你如何使用AI设计logo-如何快速用AI设计logo-附上AI绘图logo设计的咒语-优雅草央千澈-实战教程
6160
自媒体创业必备的6个在线神器|免费效率工具
1.4K0
AI ppt生成器 Tome
5760
你也想用上这别具一格的网站 Logo 吗,赶紧进来手把手教会你!
9000
WaterStamp —— 一个实用的网页水印生成器开发记
980
《揭秘AI领域的绝密武器——我整理了一份超级详细的AI工具合集》
2.3K0
关于AI绘画优雅草央千澈整理的一份咒语(与AI对话提示词-应用于AI绘图和AI生成视频)-本文长期更新-本次更新2025年1月15日更新-长期更新建议点赞收藏
3250
【腾讯云AI绘画】AI绘画专栏之全网第一套系统的AI绘画课 零基础学会AI绘画
3.4K17
分享15个全球顶尖的AIGC图片生成平台
33.2K0
强烈推荐!一个可以把文本生成漂亮图表的AI做图神器
6940
拒绝加班的九大工具神器:logo在线生成爱了
1K0
Slidesgo:让演示文稿制作轻而易举
5490
相关推荐
8个免费LOGO生成器测评,Logo设计很简单
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档