当前位置: 首页 > news >正文

文山做网站安卓优化大师最新版下载

文山做网站,安卓优化大师最新版下载,温州城建论坛网,商城站时刻表文章目录1. 简介2. 特性3. 演示3.1 集成3.2 配置3.3 注解模块3.4 网络模块3.4.1 GET请求3.4.2 POST请求3.4.3 使用缓存3.4.4 上传文件3.4.5 下载文件3.5 图片加载模块3.6 数据库模块3.6.1 创建数据库3.6.2 增加数据3.6.3 删除数据3.6.4 修改数据3.6.5 查询数据4. 源码地址1. 简…

文章目录

  • 1. 简介
  • 2. 特性
  • 3. 演示
    • 3.1 集成
    • 3.2 配置
    • 3.3 注解模块
    • 3.4 网络模块
      • 3.4.1 GET请求
      • 3.4.2 POST请求
      • 3.4.3 使用缓存
      • 3.4.4 上传文件
      • 3.4.5 下载文件
    • 3.5 图片加载模块
    • 3.6 数据库模块
      • 3.6.1 创建数据库
      • 3.6.2 增加数据
      • 3.6.3 删除数据
      • 3.6.4 修改数据
      • 3.6.5 查询数据
  • 4. 源码地址

1. 简介

xUtils,是基于Afinal开发的目前功能比较完善的一个Android开源框架,GitHub官网为:xUtils3。在介绍xUtils之前,我们可以简单看看Afinal的简介:

  • Afinal 是一个android的sqlite orm和 ioc 框架。同时封装了android中的http框架,使其更加简单易用;
  • 使用finalBitmap,无需考虑bitmap在android中加载的时候oom的问题和快速滑动的时候图片加载位置错位等问题。
  • Afinal的宗旨是简洁,快速。约定大于配置的方式。尽量一行代码完成所有事情。

具有强大特性的Afinal,实现了四个主要的功能:View注入网络传输数据库操作图片加载。然而,作者在搜索Afianl的相关资料时,发现了其中的一些问题。事实上,该框架的GitHub官网:Afinal显示此框架最后更新的时间是五年前,也就是说几乎不再维护了,一些遗留问题可能还没有处理完全。在这种背景下,为了解决Afinal框架中出现的种种问题,xUtils应运而生。

最初学习xUtils时,作者使用的是较老版本的,那时候集成xUtils还需要导入jar包才行。但现在最新的xUtils3出现了,作为一个集成了大量工具方法的框架,无论是为了代码的简洁还是偷懒,我们都应该去认真地学习它。

2. 特性

xUtils 包含了orm, http(s), image, view注解, 但依然很轻量级(251K), 并且特性强大, 方便扩展:

  1. orm: 高效稳定的orm工具, 使得http接口实现时更方便的支持cookie和缓存
    • 灵活的, 类似linq表达式的接口.
    • 和greenDao一致的性能.
  2. http(s): 基于UrlConnection,Android4.4以后底层为okHttp实现
    • 请求协议支持11种谓词:GET,POST,PUT,PATCH,HEAD,MOVE,COPY,DELETE,OPTIONS,TRACE,CONNECT
    • 支持超大文件(超过2G)上传
    • 支持断点下载(如果服务端支持Range参数,客户端自动处理断点下载)
    • 支持cookie(实现了domain, path, expiry等特性)
    • 支持缓存(实现了Cache-Control, Last-Modified, ETag等特性, 缓存内容过多时使用过期时间+LRU双重机制清理)
    • 支持异步和同步(可结合RxJava使用)调用
  3. image: 有了http(s)及其下载缓存的支持, image模块的实现相当的简洁
    • 支持内存缓存, 磁盘缓存(缩略图和原图), 并且支持回收被view持有, 但被MemCache移除的图片, 减少页面回退时的闪烁.
    • 支持在ListView滑动时, 自动停止被回收复用的item对应的下载任务(再次下载时断点续传)
    • 支持webp, gif(部分比较老的系统只展示静态图)
    • 支持圆角, 圆形, 方形等裁剪, 支持自动旋转…
  4. view注解: view注解模块仅仅400多行代码却灵活的支持了各种View注入和事件绑定
    • 事件注解支持且不受混淆影响…(参考sample的混淆配置)
    • 支持绑定拥有多个方法的listener

总的来说,xUtils主要有4个模块:注解模块,网络模块,图片加载模块,数据库模块。包括我们之前学习过的ButterKnife和Dagger,xUtils算是我们接触的第三个具有依赖注入的框架了。

按照这个陈列出来的模块顺序,下面简单演示一下相应的功能代码。当然,如果想要了解关于xUtils更多的使用方法,在其GitHub官网上就有名为sample的样例工程,读者可以参考使用。

3. 演示

3.1 集成

依旧去xUtils的GitHub官网上获取最新版本的依赖,然后添加到module下的build.gradle中,代码如下:

implementation 'org.xutils:xutils:3.8.8'

3.2 配置

集成了xUtils后,为了方便演示,我们添加一些可能会用到的权限,修改Manifest.xml,代码如下:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 可选 -->

同时,新建一个名为MyApplication的类,继承自Application,在其onCreate()方法中进行xUtils的初始化,代码如下:

import android.app.Application;import org.xutils.x;public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();x.Ext.init(this);x.Ext.setDebug(BuildConfig.DEBUG); // 是否输出debug日志, 开启debug会影响性能.}
}

最后,别放了在Manifest.xml中声明我们刚刚创建的MyApplication,即添加android:name属性,代码如下:

<applicaiotion
android:name=".MyApplication"
...
</applicaiton>

集成和配置完毕后,接下来,我们就开始逐一讲解xUtils中的四个模块:注解模块,网络模块,图片加载模块,数据库模块

3.3 注解模块

该模块的功能类似于ButterKnife和Dagger,依旧是给相应字段加上特定注解,达到简化代码的目的。在该模块中,我们将使用到以下注解:

  1. @ContentView:加载当前的Activity布局时,将@ContentView加入到Activity的上方,效果等同于setContentView(R.layout.activity_xxx);

  2. @ViewInject:View注解的作用是代替我们写了findViewById()这行代码,一般用于敏捷开发。

  3. @Event:事件注解,大部分用来替代点击事件的匿名内部类。拥有valuetype两个参数,使用该注解有以下的注意事项:

    1. 方法必须私有限定,即使用private
    2. 方法参数形式必须和type对应的Listener接口一致,type的默认值是View.OnClickListener.class
    3. 注解参数value支持数组,类似于value={id1, id2, id3}
    4. 其它参数说明见{@link org.xutils.event.annotation.Event}类的说明。

我们编写一个简单的Activity,来体验一下这三个注解的使用。修改布局文件activity_main.xml,在里面添加两个按钮和一个文本控件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"android:orientation="vertical"><Buttonandroid:id="@+id/btn_test"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="测试按钮"/><Buttonandroid:id="@+id/btn_test2"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="测试按钮2"/><TextViewandroid:id="@+id/tv_test"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="测试文本" /></LinearLayout>

之后,我们使用xUtils所提供的三个注解来在MainActivity中使用,实现一个简单的逻辑:点击两个按钮时的文本发生变化,代码如下:

package com.androidframelearn.util_xutils;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;import org.xutils.view.annotation.ContentView;
import org.xutils.view.annotation.Event;
import org.xutils.view.annotation.ViewInject;
import org.xutils.x;@ContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {@ViewInject(R.id.btn_test)private Button btn_test;@ViewInject(R.id.btn_test2)private Button btn_test2;@ViewInject(R.id.tv_test)private TextView tv_test;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);x.view().inject(this);}@Event(value = {R.id.btn_test,R.id.btn_test2},type = View.OnClickListener.class)private void onClick(View view){switch (view.getId()){case R.id.btn_test:tv_test.setText("修改成了测试文本");break;case R.id.btn_test2:tv_test.setText("修改成了测试文本2");break;}}
}

可以看到,这里我们使用了xUtils3所提供的三个注解进行代码的编写,代码看起来变得简洁,可读性也强了很多。
注意:在使用xUtils之前,记得在Activity或者Fragment的onCreate()/onCreateView()中调用x.view().inject(this)来初始化一下xUtils,不然无法使用里面的功能!

3.4 网络模块

xUtils在Android 4.4之后,底层使用的是OkHttp来实现网络模块。所以这里简单演示一下跟网络有关的几个主要功能,分别是GET请求POST请求使用缓存上传文件下载文件。下面直接贴出业务代码(这里的GET/POST请求的义务以常见的登录校验/登录为例,而使用缓存则以GET请求为例),具体的api数量过多,就不一一讲解了,读者一看便知是怎么使用的了。

3.4.1 GET请求

@Event(value = {R.id.btn_get},type = View.OnClickListener.class)
private void onTestGet(View view){String url ="http://10.0.0.2:8080/login"RequestParams params = new RequestParams(url);//params.setSslSocketFactory(...); // 设置sslparams.addQueryStringParameter("username","abc");params.addQueryStringParameter("password","123");x.http().get(params, new Callback.CommonCallback<String>() {public void onSuccess(String result) {Toast.makeText(x.app(), result, Toast.LENGTH_LONG).show();Log.i(TAG, "onSuccess result:" + result);}//请求异常后的回调方法@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}//主动调用取消请求的回调方法@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}});}

3.4.2 POST请求

@Event(R.id.btn_post)
private void onTestPost(View v) {String url = "http://10.0.0.2:8080/login";RequestParams params = new RequestParams(url);params.addParameter("username", "abc");params.addParameter("password", "123");x.http().request(HttpMethod.PUT, params, new Callback.CommonCallback<String>() {@Overridepublic void onSuccess(String result) {Toast.makeText(x.app(),result,Toast.LENGTH_LONG).show();}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}});
}

3.4.3 使用缓存

@Event(R.id.btn_cache)
private void cache(View v) {
RequestParams params = new RequestParams(url);
params.setCacheMaxAge(1000*60); //为请求添加缓存时间
Callback.Cancelable cancelable = x.http().get(params, new Callback.CacheCallback<string>() {@Overridepublic void onSuccess(String result) {Log.i("JAVA","onSuccess:"+result);}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}//result:缓存内容@Overridepublic boolean onCache(String result) {//在setCacheMaxAge设置范围(上面设置的是60秒)内,如果再次调用GET请求,//返回true:缓存内容被返回,相信本地缓存,返回false:缓存内容被返回,不相信本地缓存,仍然会请求网络Log.i(TAG,result);return true;}
});

3.4.4 上传文件

@Event(R.id.btn_upload)
private void upload(View v){
String path="/mnt/sdcard/Download/icon.jpg";
RequestParams params = new RequestParams(url);
params.setMultipart(true);
params.addBodyParameter("file",new File(path));
x.http().post(params, new Callback.CommonCallback<string>() {@Overridepublic void onSuccess(String result) {}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}
});

3.4.5 下载文件

@Event(R.id.btn_download)
private void download(View v){
url = "http://10.0.0.2:8080/server/nfctest.apk";
RequestParams params = new RequestParams(url);
//自定义保存路径,Environment.getExternalStorageDirectory():SD卡的根目录
//注意,如果你的Android版本为Android X,由于其作用域存储的特性,应使用context.getExternalFilesDir()来获取关联目录
params.setSaveFilePath(Environment.getExternalStorageDirectory()+"/myapp/");
//自动为文件命名
params.setAutoRename(true);
x.http().post(params, new Callback.ProgressCallback<file>() {@Overridepublic void onSuccess(File result) {//apk下载完成后,调用系统的安装方法Intent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(Uri.fromFile(result), "application/vnd.android.package-archive");startActivity(intent);}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}//网络请求之前回调@Overridepublic void onWaiting() {}//网络请求开始的时候回调@Overridepublic void onStarted() {}//下载的时候不断回调的方法@Overridepublic void onLoading(long total, long current, boolean isDownloading) {//当前进度和文件总大小Log.i(TAG,"current:"+ current +",total:"+total); }
});

3.5 图片加载模块

xUtils3的图片模块,重点在于加载图片的4个bind方法,loadDrawableloadFIle用法和ImageOptions用法,这些方法的详情如下所示:

void bind(ImageView view, String url);void bind(ImageView view, String url, ImageOptions options);void bind(ImageView view, String url, Callback.CommonCallback<Drawable> callback);void bind(ImageView view, String url, ImageOptions options, Callback.CommonCallback<Drawable> callback);Callback.Cancelable loadDrawable(String url, ImageOptions options, Callback.CommonCallback<Drawable> callback);Callback.Cancelable loadFile(String url, ImageOptions options, Callback.CacheCallback<File> callback);

使用图片加载模块共有三个步骤:

  1. 使用@ViewInject来绑定ImageView:

    @ViewInject(R.id.iv1)
    ImageView image01;
    @ViewInject(R.id.iv2)
    ImageView image02;
    @ViewInject(R.id.iv3)
    ImageView image03;
    
  2. 定义图片网络地址或本地地址:

    String[] urls = {"http://img4.imgtn.bdimg.com/it/u=3182769660,1810895318&fm=23&gp=0.jpg","http://img2.imgtn.bdimg.com/it/u=1278435851,1308167727&fm=23&gp=0.jpg","http://img2.3lian.com/2014/f4/199/d/6.jpg","http://pic1.win4000.com/wallpaper/4/584b9ea3a511c.jpg"...
    };	
    
  3. 通过ImageOptions.Builder().set方法设置图片的属性 然后通过bind方法加载图片,显示图片的方法setImg()如下:

private void setImg() {/*** 通过ImageOptions.Builder().set方法设置图片的属性*/ImageOptions options = new ImageOptions.Builder().setFadeIn(true).build(); //淡入效果//ImageOptions.Builder()的一些其他属性://.setCircular(true) //设置图片显示为圆形//.setSquare(true) //设置图片显示为正方形//setCrop(true).setSize(200,200) //设置大小//.setAnimation(animation) //设置动画//.setFailureDrawable(Drawable failureDrawable) //设置加载失败的动画//.setFailureDrawableId(int failureDrawable) //以资源id设置加载失败的动画//.setLoadingDrawable(Drawable loadingDrawable) //设置加载中的动画//.setLoadingDrawableId(int loadingDrawable) //以资源id设置加载中的动画//.setIgnoreGif(false) //忽略Gif图片//.setParamsBuilder(ParamsBuilder paramsBuilder) //在网络请求中添加一些参数//.setRaduis(int raduis) //设置拐角弧度//.setUseMemCache(true) //设置使用MemCache,默认true/*** 加载图片的4个bind方法*/x.image().bind(image01, urls[0]);x.image().bind(image02, urls[1], options);x.image().bind(image03, urls[2], options, new Callback.CommonCallback<Drawable>() {@Overridepublic void onSuccess(Drawable result) {}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}});x.image().bind(image04, urls[3], options, new Callback.CommonCallback<Drawable>() {@Overridepublic void onSuccess(Drawable result) {}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}});/*** loadDrawable()方法加载图片*/Callback.Cancelable cancelable = x.image().loadDrawable(urls[4], options, new Callback.CommonCallback<Drawable>() {@Overridepublic void onSuccess(Drawable result) {image05.setImageDrawable(result);}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}});//主动取消loadDrawable()方法//cancelable.cancel();/*** loadFile()方法* 应用场景:当我们通过bind()或者loadDrawable()方法加载了一张图片后,* 它会保存到本地文件中,那当我需要这张图片时,就可以通过loadFile()方法进行查找。* urls[0]:网络地址*/x.image().loadFile(urls[0],options,new Callback.CacheCallback<File>(){@Overridepublic boolean onCache(File result) {//在这里可以做图片另存为等操作Log.i(TAG,"file:" + result.getPath() + result.getName());return true; //相信本地缓存返回true}@Overridepublic void onSuccess(File result) {Log.i(TAG,"file");}@Overridepublic void onError(Throwable ex, boolean isOnCallback) {}@Overridepublic void onCancelled(CancelledException cex) {}@Overridepublic void onFinished() {}});
}

3.6 数据库模块

3.6.1 创建数据库

要使用xUtils的数据库模块,我们先要配置DaoConfig,作为数据库的配置信息,以此来获取xUtils提供的DbManager对象,代码如下:

DbManager.DaoConfig daoConfig = new DbManager.DaoConfig()//设置数据库名,默认xutils.db.setDbName("myapp.db")// 不设置dbDir时, 默认存储在app的私有目录..setDbDir(new File("/sdcard")) // "sdcard"的写法并非最佳实践, 这里为了简单, 先这样写了..setDbVersion(1)//数据库版本//设置是否允许事务,默认true//.setAllowTransaction(true)//设置表创建的监听.setTableCreateListener(new DbManager.TableCreateListener(){@Overridepublic void onTableCreated(DbManager db, TableEntity<?> table) {Log.i("JAVA", "onTableCreated:" + table.getName());}})//设置数据库更新的监听.setDbUpgradeListener(new DbManager.DbUpgradeListener() {@Overridepublic void onUpgrade(DbManager db, int oldVersion, int newVersion) {}})//设置数据库打开的监听.setDbOpenListener(new DbManager.DbOpenListener() {@Overridepublic void onDbOpened(DbManager db) {//开启数据库支持多线程操作,提升性能db.getDatabase().enableWriteAheadLogging();}});
DbManager db = x.getDb(daoConfig);

我们创建一个名为Person的实体类,以此来创建一张数据表,代码如下:

/*** onCreated = "sql" sql:当第一次创建表需要插入数据时候在此写sql语句例:CREATE UNIQUE INDEX index_name ON person(id,name)*/
@Table(name ="person",onCreated = "")
public class Person {/*** name = "id":数据库表中的一个字段* isId = true:是否是主键* autoGen = true:是否自动增长* property = "NOT NULL":添加约束*/	@Column(name = "id",isId = true,autoGen = true,property = "NOT NULL")private int id;@Column(name = "c_name")private String name;public Person(String name) {this.name = name;}//默认的构造方法必须写出,如果没有,这张表是创建不成功的public Person() {}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Person{" +"id=" + id +", name='" + name + '\'' +'}';}
}

当上面这两步都完成之后,接下来就是数据库的创建,这里需要使用到第一步所创建出来的Dbmanger对象来创建数据库了,代码如下:

//数据库的创建
@Event(R.id.btn_create_db)
private void createDB(View v) throws DbException {//用集合向Person表中插入多条数据ArrayList<Person> person = new ArrayList<>();person.add(new Person("张三"));person.add(new Person("李四"));person.add(new Person("赵六"));//db.save()方法不仅可以插入单个对象,还能插入集合db.save(person);
}

数据库创建完毕之后,接下来就是对数据库中的表进行常规操作——增删改查的说明了。

3.6.2 增加数据

增加数据的操作跟创建数据库中的业务类似,代码如下:

//增加数据
@Event(R.id.btn_add_data)
private void addData(View v) throws DbException {//用集合向Person表中插入多条数据ArrayList<Person> person = new ArrayList<>();person.add(new Person("张三"));person.add(new Person("李四"));person.add(new Person("赵六"));//db.save()方法不仅可以插入单个对象,还能插入集合db.save(person);
}

3.6.3 删除数据

删除数据的操作比较简单,下面提供两种删除数据的方式,供读者参考,代码如下:

@Event(R.id.btn_delete_data)
private void deleteData(View v) throws DbException {//第一种写法:db.delete(Person.class); //child_info表中数据将被全部删除//第二种写法,添加删除条件:WhereBuilder b = WhereBuilder.b();b.and("id",">",2); //构造修改的条件b.and("id","<",4);db.delete(Person.class, b);
}

当然,除了删除数据之外,我们也可以删除数据表,代码如下:

@Event(R.id.btn_delete_table)
private void deleteTable(View v) throws DbException {db.dropTable(Person.class);
}

甚至,我们还可以直接删除数据库,代码如下:

//删除数据库
@Event(R.id.btn_delete_db)
private void deleteDB(View v) throws DbException {db.dropDb();
}

3.6.4 修改数据

修改数据的操作要稍微复杂一些,下面提供三种修改数据的方式,供读者参考,代码如下:

//修改表中数据
@Event(R.id.btn_update_data)
private void updateData(View v) throws DbException {//第一种写法:Person first = db.findFirst(Person.class);first.setName("张三01");db.update(first, "c_name"); //c_name:表中的字段名//第二种写法:WhereBuilder b = WhereBuilder.b();b.and("id", "=", first.getId()); //构造修改的条件KeyValue name = new KeyValue("c_name", "张三02");db.update(Person.class, b, name);//第三种写法:first.setName("张三修改");db.saveOrUpdate(first);Toast.makeText(this,"修改成功",Toast.LENGTH_LONG).show();
}

3.6.5 查询数据

查询数据的操作基本上是增删改查中逻辑最复杂的,这里仅列出部分查询场景,代码如下:

@Event(R.id.btn_query_data)
private void queryData(View v) throws DbException {//查询数据库表中第一条数据Person first = db.findFirst(Person.class);Log.i(TAG,first.toString());//findAll():查询所有结果List<Person> personAll =db.findAll(Person.class);List<String > list = new ArrayList<String>();for(int i=0;i<personAll.size();i++){list.add(personAll.get(i).toString());}ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1,list);lv1.setAdapter(adapter); //添加查询条件进行查询WhereBuilder b = WhereBuilder.b();b.and("id",">",2); //构造修改的条件b.and("id","<",4);List<Person> all = db.selector(Person.class).where(b).findAll();//第二种写法:List<Person> all2 = db.selector(Person.class).where("id",">",2).and("id","<",4).findAll();for(Person person :all){Log.i(TAG,person.toString());}
}

4. 源码地址

AFL——Android框架学习

http://www.lbrq.cn/news/2416879.html

相关文章:

  • 西宁公安网站建设四川成都最新消息
  • 建站的注意事项官网站内推广内容
  • 万网网站备案流程十大计算机培训学校
  • 自己做链接的网站吗提高工作效率的措施
  • 宁波做网站的网页seo搜索引擎优化
  • 网站如何做等保备案临沂今日头条新闻最新
  • wordpress b站插件市场调研方案范文
  • 广州网站seo上海关键词优化的技巧
  • 个人网站建设分几个步走百度站长平台官网登录入口
  • 大埔建设工程交易中心网站湖南长沙最新情况
  • mediwiki 做网站东莞网站建设做网站
  • 五台网站建设攀枝花网站seo
  • 小程序解析wordpressseo电商运营是什么意思
  • 现在企业做网站用什么软件培训心得体会2000字
  • 邢台做网站的价格免费建网站平台
  • 商城网站模块北京seo百度推广
  • 动态网站设计用什么软件杭州seo整站优化
  • 源码上传网站魔贝课凡seo课程好吗
  • 网站如何做留言板南宁seo收费
  • 怎么做网站推广的论文离我最近的电脑培训中心
  • 建设银行网站是什么应用商店app下载
  • 安徽省建设厅执业资格注册中心网站百度浏览器网址
  • 商城小程序多少钱seo关键词优化推广报价表
  • 孝感网站建设公司推广平台网站有哪些
  • 校友网站 建设做网络推广有前途吗
  • 做分类信息网站模板能搜任何网站的浏览器
  • 茂易网站建设seo专员是指什么意思
  • 网站的做网站的公司台州网站优化公司
  • 品牌网站建设报价青岛网络工程优化
  • 做文章网站外贸网站建站
  • Netty中CompositeByteBuf 的addComponents方法解析
  • Java基础面试题
  • 前端JavaScript进阶
  • Dify 1.6 安装与踩坑记录(Docker 方式)
  • 企业级安全威胁检测与响应(EDR/XDR)架构设计
  • C++现代编程之旅:从基础语法到高性能应用开发