展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 系统应用 > 12.4 DrySister看妹子应用(第一版)——4.添加数据缓存(加入SQLite)

12.4 DrySister看妹子应用(第一版)——4.添加数据缓存(加入SQLite)

1.一些BB 上节已经给DrySister搭好了一个异步图片加载缓存框架——SisterLoader(妹子加载器),图片从网络拉取成功后,会同时缓存到磁盘和内存里。这样一来,就算后来把网络断掉,之前看过的图片照样还能打开。不过这里还有个挺现实的问题:APP是在有网时进入的,图片相关信息比如URL,也

本文目录

    1.一些BB

    上节已经给DrySister搭好了一个异步图片加载缓存框架——SisterLoader(妹子加载器),图片从网络拉取成功后,会同时缓存到磁盘和内存里。这样一来,就算后来把网络断掉,之前看过的图片照样还能打开。不过这里还有个挺现实的问题:APP是在有网时进入的,图片相关信息比如URL,也是那时候从接口拿到的;一旦退出应用、再断网重进,图片反而加载不出来了。原因并不复杂——图片文件虽然还在缓存里,但图片对应的URL信息没留下来,这套缓存能力就多少有点“使不上劲”。所以,后台接口返回的数据也得一并存起来。更准确地说,就是每次加载时,把返回的数据同步落到本地;等到无网状态下,再直接读取本地数据来完成展示。也正因为如此,这一节要给DrySister补上SQLite,用来存储这些相关数据。目标很明确,直接开干。

    2.开始编写程序

    在develop上开辟一个新的db分支,来完成本节相关代码的编写。

    首先,我们添加一个用来判断网络状态的工具类:NetworkUtils.ja va, 就一个简单的判断网络是否可用的方法~这里我们还需要在AndroidManifest.xml 里加一个权限:android.permission.ACCESSNETWORKSTATE"的权限!

    public class NetworkUtils {
        /** 获取网络信息 */
        private static NetworkInfo getActiveNetworkInfo(Context context) {
            ConnectivityManager cm = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            return cm.getActiveNetworkInfo();
        }
    /** 判断网络是否可用 */
        public static boolean isA vailable(Context context) {
            NetworkInfo info = getActiveNetworkInfo(context);
            return info != null && info.isA vailable();
        }
    }

    接着定义一个数据库字段常量的类:TableDefine.ja va, 在里面把数据库名,字段写上:

    public class TableDefine {
        public static final String TABLE_FULI = "fuli";
        public static final String COLUMN_ID = "id";
        public static final String COLUMN_FULI_ID = "_id";
        public static final String COLUMN_FULI_CREATEAT = "createAt";
        public static final String COLUMN_FULI_DESC = "desc";
        public static final String COLUMN_FULI_PUBLISHEDAT = "publishedAt";
        public static final String COLUMN_FULI_SOURCE = "source";
        public static final String COLUMN_FULI_TYPE = "type";
        public static final String COLUMN_FULI_URL = "url";
        public static final String COLUMN_FULI_USED = "used";
        public static final String COLUMN_FULI_WHO = "who";
    }

    再接着编写数据库的创建类:SisterOpenHelper.ja va,在里面完成数据库的创建

    public class SisterOpenHelper extends SQLiteOpenHelper{
    private static final String DB_NAME = "sister.db";  //数据库名
        private static final int DB_VERSION = 1;    //数据库版本号
    public SisterOpenHelper(Context context) {
            super(context, DB_NAME, null, DB_VERSION);
        }
    @Override
        public void onCreate(SQLiteDatabase db) {
            String createTableSql = "CREATE TABLE IF NOT EXISTS " + TableDefine.TABLE_FULI + " ("
                    + TableDefine.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                    + TableDefine.COLUMN_FULI_ID + " TEXT, "
                    + TableDefine.COLUMN_FULI_CREATEAT + " TEXT, "
                    + TableDefine.COLUMN_FULI_DESC + " TEXT, "
                    + TableDefine.COLUMN_FULI_PUBLISHEDAT + " TEXT, "
                    + TableDefine.COLUMN_FULI_SOURCE + " TEXT, "
                    + TableDefine.COLUMN_FULI_TYPE + " TEXT, "
                    + TableDefine.COLUMN_FULI_URL + " TEXT, "
                    + TableDefine.COLUMN_FULI_USED + " BOOLEAN, "
                    + TableDefine.COLUMN_FULI_WHO + " TEXT"
                    + ")";
            db.execSQL(createTableSql);
        }
    @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
    }

    再接着,我们需要编写一个数据库的操作类,就是做增删改查,分页查询等操作的类, 这里我们把这个类写成单例的:SisterDBHelper.ja va,里面的编写的方法有这些:

    具体代码:

    public class SisterDBHelper {
    private static final String TAG = "SisterDBHelper";
    private static SisterDBHelper dbHelper;
        private SisterOpenHelper sqlHelper;
        private SQLiteDatabase db;
    private SisterDBHelper() {
            sqlHelper = new SisterOpenHelper(DrySisterApp.getContext());
        }
    /** 单例 */
        public static SisterDBHelper getInstance() {
            if(dbHelper == null) {
                synchronized (SisterDBHelper.class) {
                    if(dbHelper == null) {
                        dbHelper = new SisterDBHelper();
                    }
                }
            }
            return dbHelper;
        }
    /** 插入一个妹子 */
        public void insertSister(Sister sister) {
            db = getWritableDB();
            ContentValues contentValues = new ContentValues();
            contentValues.put(TableDefine.COLUMN_FULI_ID,sister.get_id());
            contentValues.put(TableDefine.COLUMN_FULI_CREATEAT,sister.getCreateAt());
            contentValues.put(TableDefine.COLUMN_FULI_DESC,sister.getDesc());
            contentValues.put(TableDefine.COLUMN_FULI_PUBLISHEDAT,sister.getPublishedAt());
            contentValues.put(TableDefine.COLUMN_FULI_SOURCE,sister.getSource());
            contentValues.put(TableDefine.COLUMN_FULI_TYPE,sister.getType());
            contentValues.put(TableDefine.COLUMN_FULI_URL,sister.getUrl());
            contentValues.put(TableDefine.COLUMN_FULI_USED,sister.getUsed());
            contentValues.put(TableDefine.COLUMN_FULI_WHO,sister.getWho());
            db.insert(TableDefine.TABLE_FULI,null,contentValues);
            closeIO(null);
        }
    /** 插入一堆妹子(使用事务) */
        public void insertSisters(ArrayList sisters) {
            db = getWritableDB();
            db.beginTransaction();
            try{
                for (Sister sister: sisters) {
                    ContentValues contentValues = new ContentValues();
                    contentValues.put(TableDefine.COLUMN_FULI_ID,sister.get_id());
                    contentValues.put(TableDefine.COLUMN_FULI_CREATEAT,sister.getCreateAt());
                    contentValues.put(TableDefine.COLUMN_FULI_DESC,sister.getDesc());
                    contentValues.put(TableDefine.COLUMN_FULI_PUBLISHEDAT,sister.getPublishedAt());
                    contentValues.put(TableDefine.COLUMN_FULI_SOURCE,sister.getSource());
                    contentValues.put(TableDefine.COLUMN_FULI_TYPE,sister.getType());
                    contentValues.put(TableDefine.COLUMN_FULI_URL,sister.getUrl());
                    contentValues.put(TableDefine.COLUMN_FULI_USED,sister.getUsed());
                    contentValues.put(TableDefine.COLUMN_FULI_WHO,sister.getWho());
                    db.insert(TableDefine.TABLE_FULI,null,contentValues);
                }
                db.setTransactionSuccessful();
            } finally {
                if(db != null && db.isOpen()) {
                    db.endTransaction();
                    closeIO(null);
                }
            }
        }
    /** 删除妹子(根据_id) */
        public void deleteSister(String _id) {
            db = getWritableDB();
            db.delete(TableDefine.TABLE_FULI,"_id =?",new String[]{_id});
            closeIO(null);
        }
    /** 删除所有妹子 */
        public void deleteAllSisters() {
            db = getWritableDB();
            db.delete(TableDefine.TABLE_FULI,null,null);
            closeIO(null);
        }
    /** 更新妹子信息(根据_id) */
        public void deleteSister(String _id,Sister sister) {
            db = getWritableDB();
            ContentValues contentValues = new ContentValues();
            contentValues.put(TableDefine.COLUMN_FULI_ID,sister.get_id());
            contentValues.put(TableDefine.COLUMN_FULI_CREATEAT,sister.getCreateAt());
            contentValues.put(TableDefine.COLUMN_FULI_DESC,sister.getDesc());
            contentValues.put(TableDefine.COLUMN_FULI_PUBLISHEDAT,sister.getPublishedAt());
            contentValues.put(TableDefine.COLUMN_FULI_SOURCE,sister.getSource());
            contentValues.put(TableDefine.COLUMN_FULI_TYPE,sister.getType());
            contentValues.put(TableDefine.COLUMN_FULI_URL,sister.getUrl());
            contentValues.put(TableDefine.COLUMN_FULI_USED,sister.getUsed());
            contentValues.put(TableDefine.COLUMN_FULI_WHO,sister.getWho());
            db.update(TableDefine.TABLE_FULI,contentValues,"_id =?",new String[]{_id});
            closeIO(null);
        }
    /** 查询当前表中有多少个妹子 */
        public int getSistersCount() {
            db = getReadableDB();
            Cursor cursor = db.rawQuery("SELECT COUNT (*) FROM " + TableDefine.TABLE_FULI,null);
            cursor.moveToFirst();
            int count = cursor.getInt(0);
            Log.v(TAG,"count:" + count);
            closeIO(cursor);
            return count;
        }
    /** 分页查询妹子,参数为当前页和每一个的数量,页数从0开始算 */
        public List getSistersLimit(int curPage,int limit) {
            db =  getReadableDB();
            List sisters = new ArrayList<>();
            String startPos = String.valueOf(curPage * limit);  //数据开始位置
            if(db != null) {
                Cursor cursor = db.query(TableDefine.TABLE_FULI,new String[] {
                        TableDefine.COLUMN_FULI_ID, TableDefine.COLUMN_FULI_CREATEAT,
                        TableDefine.COLUMN_FULI_DESC, TableDefine.COLUMN_FULI_PUBLISHEDAT,
                        TableDefine.COLUMN_FULI_SOURCE, TableDefine.COLUMN_FULI_TYPE,
                        TableDefine.COLUMN_FULI_URL, TableDefine.COLUMN_FULI_USED,
                        TableDefine.COLUMN_FULI_WHO,
                },null,null,null,null,TableDefine.COLUMN_ID,startPos + "," + limit);
                while (cursor.moveToNext()) {
                    Sister sister = new Sister();
                    sister.set_id(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_ID)));
                    sister.setCreateAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_CREATEAT)));
                    sister.setDesc(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_DESC)));
                    sister.setPublishedAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_PUBLISHEDAT)));
                    sister.setSource(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_SOURCE)));
                    sister.setType(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_TYPE)));
                    sister.setUrl(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_URL)));
                    sister.setUsed(cursor.getInt(cursor.getColumnIndex(TableDefine.COLUMN_FULI_USED)));
                    sisters.add(sister);
                }
                closeIO(cursor);
            }
            return sisters;
        }
    /** 查询所有妹子 */
        public List getAllSisters() {
            db = getReadableDB();
            List sisters = new ArrayList<>();
            Cursor cursor = db.rawQuery("SELECT * FROM "+TableDefine.TABLE_FULI,null);
            cursor.moveToFirst();
            while (cursor.moveToNext()) {
                Sister sister = new Sister();
                sister.set_id(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_ID)));
                sister.setCreateAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_CREATEAT)));
                sister.setDesc(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_DESC)));
                sister.setPublishedAt(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_PUBLISHEDAT)));
                sister.setSource(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_SOURCE)));
                sister.setType(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_TYPE)));
                sister.setUrl(cursor.getString(cursor.getColumnIndex(TableDefine.COLUMN_FULI_URL)));
                sister.setUsed(cursor.getInt(cursor.getColumnIndex(TableDefine.COLUMN_FULI_USED)));
                sisters.add(sister);
            }
            closeIO(cursor);
            return sisters;
        }
    /** 获得可写数据库的方法 */
        private SQLiteDatabase getWritableDB() {
            return sqlHelper.getWritableDatabase();
        }
    /** 获得可读数据库的方法 */
        private SQLiteDatabase getReadableDB() {
            return sqlHelper.getReadableDatabase();
        }
    /** 关闭cursor和数据库的方法 */
        private void closeIO(Cursor cursor) {
            if(cursor != null) {
                cursor.close();
            }
            if(db != null) {
                db.close();
            }
        }
    }

    嗯,还是蛮简单的,都是一些SQLite的方法调用而已,接着我们对布局做一下更改, 把activity_main.xml改成下面的代码,就是换成上一个和下一个这样:

    
    
    

    最后就是对MainActivity.ja va的一些逻辑更改了,核心的要点如下:

    • 1.curPos和page的控制以及处理逻辑
    • 2.根据网络是否可用加载网络还是数据库数据
    • 3.加载网络数据时存储到数据库需要避免重复插入 等等。

    代码实现:

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        private Button previousBtn;
        private Button nextBtn;
        private ImageView showImg;
    private ArrayList data;
        private int curPos = 0; //当前显示的是哪一张
        private int page = 1;   //当前页数
        private PictureLoader loader;
        private SisterApi sisterApi;
        private SisterTask sisterTask;
        private SisterLoader mLoader;
        private SisterDBHelper mDbHelper;
    @Override
        protected void onCreate(Bundle sa vedInstanceState) {
            super.onCreate(sa vedInstanceState);
            setContentView(R.layout.activity_main);
            sisterApi = new SisterApi();
            loader = new PictureLoader();
            mLoader = SisterLoader.getInstance(MainActivity.this);
            mDbHelper = SisterDBHelper.getInstance();
            initData();
            initUI();
        }
    private void initData() {
            data = new ArrayList<>();
            sisterTask = new SisterTask();
            sisterTask.execute();
        }
    private void initUI() {
            previousBtn = (Button) findViewById(R.id.btn_previous);
            nextBtn = (Button) findViewById(R.id.btn_next);
            showImg = (ImageView) findViewById(R.id.img_show);
    previousBtn.setOnClickListener(this);
            nextBtn.setOnClickListener(this);
        }
    @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_previous:
                    --curPos;
                    if (curPos == 0) {
                        previousBtn.setVisibility(View.INVISIBLE);
                    }
                    if (curPos == data.size() - 1) {
                        sisterTask = new SisterTask();
                        sisterTask.execute();
                    } else if(curPos < data.size()) {
                        mLoader.bindBitmap(data.get(curPos).getUrl(), showImg, 400, 400);
                    }
                    break;
                case R.id.btn_next:
                    previousBtn.setVisibility(View.VISIBLE);
                    if(curPos < data.size()) {
                        ++curPos;
                    }
                    if (curPos > data.size() - 1) {
                        sisterTask = new SisterTask();
                        sisterTask.execute();
                    } else if(curPos < data.size()){
                        mLoader.bindBitmap(data.get(curPos).getUrl(), showImg, 400, 400);
                    }
                    break;
            }
        }
    private class SisterTask extends AsyncTask> {
    public SisterTask() {
            }
    @Override
            protected ArrayList doInBackground(Void... params) {
                ArrayList result = new ArrayList<>();
                if (page < (curPos + 1) / 10 + 1) {
                    ++page;
                }
                //判断是否有网络
                if (NetworkUtils.isA vailable(getApplicationContext())) {
                    result = sisterApi.fetchSister(10, page);
                    //查询数据库里有多少个妹子,避免重复插入
                    if(mDbHelper.getSistersCount() / 10 < page) {
                        mDbHelper.insertSisters(result);
                    }
                } else {
                    result.clear();
                    result.addAll(mDbHelper.getSistersLimit(page - 1, 10));
                }
                return result;
            }
    @Override
            protected void onPostExecute(ArrayList sisters) {
                super.onPostExecute(sisters);
                data.addAll(sisters);
                if (data.size() > 0 && curPos + 1 < data.size()) {
                    mLoader.bindBitmap(data.get(curPos).getUrl(), showImg, 400, 400);
                }
            }
    @Override
            protected void onCancelled() {
                super.onCancelled();
                sisterTask = null;
            }
        }
    @Override
        protected void onDestroy() {
            super.onDestroy();
            if (sisterTask != null) {
                sisterTask.cancel(true);
            }
        }
    }

    3.运行效果图

    操作步骤:

    把项目跑起来后,一直按下一页下一页缓存一堆图片,接着断开网络,进入DrySister, Duang~,神奇的发现,有妹子出现了,按下一页或者上一页也可以切换图片,妈妈再也 不用担心我在没网的时候不可以看DrySister了!

    运行截图:

    4.小结

    这一步是在上一节的基础上,进一步补上了 SQLite,用来把后台数据落到本地。这样一来,即使在没有网络的情况下,妹子图片照样可以正常查看。整体上,代码层面的主要调整和前面提到的一致,另外还有一些零散的小改动,细节直接看代码会更清楚。最后,再把 db 分支的代码合并到 develop 分支,随后删除 db 分支,并将更新后的 develop 分支推送到 Github。

    代码下载:

    https://github.com/coder-pig/DrySister/tree/develop 欢迎follow,star,觉得有什么想加进来的可以提下issues!

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    系统应用 Edge
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。