☰
仿QQ安卓App源码解析:从Gradle配置到聊天界面实现
2026/10/10 6:25:00 网站建设 项目流程

简介:这款基于Java开发的仿QQ原生安卓APP源码,定位为Android初学者与实训学生的完整项目参考,重点演示即时通讯类应用的项目组织、社交界面搭建与基础交互逻辑实现,可作为课程设计、毕业设计或仿社交App练手的基础工程。压缩包共85个文件、大小仅355KB,包含35个PNG图片资源、27个XML界面与配置资源、9个Java核心类文件,以及Gradle构建脚本、ProGuard规则、说明文档等工程配套文件,结构清晰,适合按标准安卓工程导入并二次开发。目前已有321人浏览学习。读者可对照源码梳理原生安卓工程从资源目录划分到页面渲染、事件响应、列表展示的常见写法,也能借助现成界面素材与XML布局快速修改出具有QQ风格的原型,甚至可以在此基础上继续扩展登录、聊天列表、好友管理等模块,形成完整的仿社交应用实践项目。

1. 仿 QQ 安卓 App 源码包:大作品做不出来,小项目刚好够用

一个 Java 仿 QQ 的安卓 App 源码包,听起来像要复刻整个 QQ,实际上一共只有 9 个 Java 类,是典型的实训项目:社交功能点到为止,用户体验靠布局和 drawable 撑起来。正因为小,它反而是入门者最容易看完的一整套安卓工程——从 Gradle 构建到资源文件再到消息列表,你能在一天之内把整个 App 的每一条线索串起来。

如果你正在交课程设计、准备毕业答辩,或者想在手边放一个“完整但看得懂”的 App 源码,这份资源比那些动辄几十上百个类的全家桶更友好。它不需要你懂网络协议、推送和多端同步,打开就能在本地跑出一个界面交互很像即时通讯软件的壳子,拿来改改就能当作业交。

当然它也有明显边界:没有真实后端、没有推送,很多数据靠本地文件和无痕内存演出来。但这不影响它演示社交 App 的界面结构和交互逻辑,反而省掉了大量与“仿 QQ”目标无关的复杂度。对这个体量来说,代码少不是缺点,是不给你添乱。

2. 先把工程读明白:Gradle 配置、资源目录与 9 个 Java 类的对应关系

打开下载包先别急着连手机,我会按“构建配置 → 资源 → 代码”的顺序把工程当文档读一遍。这样后面不管是改包名、换图标还是调界面,都能快速找到位置。很多同学第一次拿到源码,习惯性先点 Run,结果卡在 Gradle 同步上半天,就是因为漏了这一步。

2.1 从文件清单反推项目规模:为什么说它小而完整

一个工程如果包含 35 个 PNG、27 个 XML、9 个 Java 类,通常意味着:图片占了资源大头,布局和配置次之,业务逻辑非常精简。这种分配很符合仿 QQ 演示课的定位——把注意力放在可见的界面效果上,而不是网络、协议和数据层。

我一般先按文件类型画一张职责表,再去翻目录就不会迷路:

文件类型常见用途
PNG 文件好友头像、Tab 图标、默认背景、按钮状态图
XML 文件AndroidManifest、Activity 布局、item 布局、drawable shape / selector、values 字符串与颜色
Java 文件Activity、Adapter、数据 Bean、工具类
Gradle / Properties工程构建、SDK 版本、依赖声明、JVM 参数、混淆规则
JAR 包本地依赖库,通常会放在 libs 目录

项目里 9 个 Java 类大致可以猜到职责分配:1 到 2 个 Activity 负责登录和主界面,1 个聊天 Activity,2 到 3 个 Adapter 处理好友列表和消息列表,剩下的数据 Bean 和工具类各占一两个。如果能画出“类与画面”的对应关系,后续改起来就有了地图。

需要提醒一句:这类资源是演示级,不是产品级。它没有完整账号体系、消息数据库、网络层和推送通道,不要期望它能扛住真实聊天场景。它的价值在于让你在最小复杂度内看懂“一个社交 App 的界面和交互是怎么组织起来的”。

2.2 Gradle 配置层:构建脚本里藏着项目能不能打开的关键

打开工程后,我习惯先看三个文件:settings.gradle、根目录 build.gradle、app 目录下 build.gradle,外加 gradle.properties。它们决定了工程能不能 sync 成功。最容易被忽略的是 gradle/wrapper/gradle-wrapper.properties,它告诉 Gradle 去下载哪个版本。

distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-x.x-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists

这里gradle-x.x-all.zip里的版本号一定要以你下载包里实际值为准。Gradle 版本和 Android Studio 版本不是随便配的:版本差太多会出现 DSL 解析失败,版本太旧又无法识别新版 Android 插件。第一次 sync 会下载指定版本到本地 Gradle 缓存目录,如果下载卡住或失败,直接表现就是左下角一直转圈。

plugins { id 'com.android.application' } android { namespace 'com.demo.im' compileSdk 31 defaultConfig { applicationId "com.demo.im" minSdk 21 targetSdk 30 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.recyclerview:recyclerview:1.2.0' }

这是一份比较典型的 app 模块 Gradle 脚本,具体版本号应以包内文件为准,这里只展示结构。namespace和applicationId分别对应编译期命名空间和运行时包名,改成你自己的包名时必须两边一起改,否则资源引用会错乱。compileSdk决定你能用哪些 API,minSdk决定最低支持机型,演示项目保持 21 左右既能用上 Material 组件,又不会把低版本设备挡在门外。minifyEnabled false表示关闭代码混淆,实训项目一般不开,开了反而可能因为 keep 规则不全导致反射类丢失,运行直接崩。

另一个容易翻车的地方是 gradle.properties。里面如果有android.useAndroidX=true,说明工程开启了 AndroidX,所有依赖包必须走 androidx 系列;如果用旧 support 库混着来,会报一堆依赖冲突。项目里那份proguard-rules.pro目前没被混淆启用,但文件存在意味着将来开 release 时可以用它配置 keep 规则。

2.3 资源文件层:27 个 XML 和 35 张 PNG 分别放在哪

看资源目录比看代码更容易理解 App 长什么样。资源文件的分工决定了界面的最终效果,仿 QQ 这种项目尤其吃资源,35 张 PNG 基本都是头像、Tab 图标、默认背景和三态按钮图。

app/src/main/res/ ├── drawable/ # shape、selector、气泡背景、按钮状态 ├── layout/ # Activity 布局和 item 布局 ├── mipmap-hdpi/ # 应用图标 ├── mipmap-xhdpi/ ├── mipmap-xxhdpi/ ├── values/ # strings.xml、colors.xml、styles.xml └── values-night/ # 深色模式资源

看到这个结构就知道,27 个 XML 不只是布局定义,还有大量 drawable 文件存放在drawable/里。比如聊天气泡的圆角背景,会拆成一个独立的 shape 文件;底部 Tab 的选中状态,会写成 selector 文件,选中时加载亮色图标,未选中时加载灰色图标。这也是为什么 PNG 文件数量多的原因。

颜色值通常在values/colors.xml里集中管理。替换成自己的主题色时,只改 colors.xml 比直接改每个布局里的硬编码颜色靠谱得多。我一般还会顺手检查values-night/是否存在,有的话要注意深色模式下气泡、背景和文字颜色的对比度,否则夜间模式下聊天界面会一团的看不清。

提示:改资源前先把 .idea 目录和根目录的 upload.zip 过滤掉。真正要动的只有app/src/main里面的内容,其余是 Android Studio 的工程配置,改了反而会让项目打不开。

3. 核心功能拆解:登录、主界面和消息气泡是怎么串起来的

演示级仿 QQ 不会像完整版即时通讯软件一样做协议层,它用最直白的本地数据串起了“登录 — 好友列表 — 聊天窗口”这条完整链路。我把拆包过程按功能模块拆成三块,分别对应三类最经典的空壳套路:本地校验登录、RadioGroup 切换主界面、一个 Adapter 支撑消息列表。看明白这三块,你把这个项目改造成自己想法的成本就很低了。

3.1 登录模块:本地校验还是假请求?演示项目最常省略的部分

仿 QQ 实训项目的登录实现通常有两种:一种是写死一组账号密码本地比对,另一种是只要输入框非空就直接进入主界面。两者都跳过了网络请求,因为实训重点在 UI 和页面跳转,而不是用户体系。

private void handleLogin(String account, String password) { if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) { Toast.makeText(this, "账号或密码不能为空", Toast.LENGTH_SHORT).show(); return; } // 实训项目一般在此处写死一组账号 if ("demo".equals(account) && "123456".equals(password)) { startActivity(new Intent(this, MainActivity.class)); finish(); } else { Toast.makeText(this, "账号或密码错误", Toast.LENGTH_SHORT).show(); } }

这里TextUtils.isEmpty会把空字符串和 null 一并拦掉,比单独调equals("")安全。先trim()去掉首尾空格再校验,能避免用户在账号栏敲空格后误报错。很多同学拿到源码后第一件事就是改这里,把写死的账号改成自己的学号,演示起来更有“自己的项目”的感觉。

如果要把校验逻辑换成真实后端,常见做法是把 if 判断换成回调接口,让 Activity 只负责 loading 和跳转,网络请求放到单独的任务类里,而不是直接在登录按钮回调里写死 URL。但这一步会引入线程、回调、超时处理,实训周期短的话不建议做。9 个 Java 类的体量,强行塞网络层反而会破坏原本清晰的页面逻辑。

3.2 主界面与 Tab 切换:用 RadioGroup 而不是 Fragment,为什么够用

底部三个 Tab(消息、联系人、动态)是仿 QQ 的标配。完整版会用 ViewPager 加 Fragment 做滑动切换,但在这个项目里,我推荐先看 RadioGroup 加内容区切换的方案,因为它足够简单,也让源码的类数量保持在可控范围。

<RadioGroup android:id="@+id/rg_main" android:orientation="horizontal" android:layout_gravity="bottom"> <RadioButton android:id="@+id/tab_message" android:text="消息" /> <RadioButton android:id="@+id/tab_contact" android:text="联系人" /> <RadioButton android:id="@+id/tab_discover" android:text="动态" /> </RadioGroup>

点击切换的逻辑也直白,监听选中项后切换内容区:

rgMain.setOnCheckedChangeListener((group, checkedId) -> { switch (checkedId) { case R.id.tab_message: showPage(0); break; case R.id.tab_contact: showPage(1); break; case R.id.tab_discover: showPage(2); break; } }); private void showPage(int index) { // ViewFlipper 的 setDisplayedChild 比反复 inflate 布局更省内存 flipper.setDisplayedChild(index); }

用 ViewFlipper 而不是每次setContentView切换页面,代码量少,而且切换时能保留页面状态。缺点是无法做左右滑动手势,不过实训演示也不需要这个效果。

这里有个极易踩坑的细节:RadioButton 默认样式在部分主题下会带一个圆圈指示器,直接跑起来会在文字左边出现一个奇怪的原点。解决办法是给 RadioButton 设置button="@null",或者改用自定义 selector 作为背景,这样底部导航才像真正的 IM 工具。

3.3 聊天界面与消息气泡:一个 Adapter 撑起两种气泡方向

聊天气泡是“社交功能”里最核心的视觉元素。实现思路是所有消息放在一个 List 里,每条消息用boolean self标识是当前用户发的还是对方发的。根据这个标志切换气泡背景和文字颜色,左右位置交给 item 布局的 gravity 控制,代码里不对坐标做任何操作。

先定义一个轻量数据 Bean:

public class MessageBean { private String content; private boolean self; // true 表示当前用户发出的消息 private long time; public MessageBean(String content, boolean self, long time) { this.content = content; this.self = self; this.time = time; } public boolean isSelf() { return self; } public String getContent() { return content; } public long getTime() { return time; } }

然后是适配器的关键代码:

public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = inflater.inflate(R.layout.item_chat_msg, parent, false); holder = new ViewHolder(); holder.avatar = convertView.findViewById(R.id.iv_avatar); holder.content = convertView.findViewById(R.id.tv_content); holder.time = convertView.findViewById(R.id.tv_time); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } MessageBean msg = list.get(position); holder.content.setText(msg.getContent()); holder.time.setText(formatTime(msg.getTime())); if (msg.isSelf()) { holder.content.setBackgroundResource(R.drawable.bg_bubble_blue); holder.content.setTextColor(Color.WHITE); } else { holder.content.setBackgroundResource(R.drawable.bg_bubble_gray); holder.content.setTextColor(Color.DARK_GRAY); } return convertView; }

convertView判空再复用,是 ListView 适配器的标准写法,能显著减少滚动时的内存开销。这里我依然建议用 ListView 而不是 RecyclerView,因为 BaseAdapter 代码量小、理解门槛低,逻辑一目了然。想升级到 RecyclerView 也不难,把 getView 换成 onCreateViewHolder / onBindViewHolder,数据源不变。

这一块的短板也很明显:消息列表全部在内存里,内存里数据没了聊天记录就没了。实训中期我经常加一条“清空聊天记录”的按钮,用来演示内存数据的释放逻辑,顺带让老师看到列表状态管理的能力。

4. 首次导入编译问题排查:五类高频翻车现场

从下载到跑通,你会发现真正的坑很少在业务代码,而是集中在环境和构建配置上。这里列出的五类翻车事故,基本覆盖了第一次导入这种工程时 90% 的卡点。每条按现象、原因、解决的思路写,照着对照定位。

4.1 卡在 Gradle 下载,sync 转十分钟不出结果

现象:导入工程后左下角一直显示 Gradle sync,转了一圈又一圈,网络差的时候能等半小时,最后提示 sync failed。

原因:gradle-wrapper.properties里distributionUrl指定的 Gradle 版本需要从网络下载,本地没有对应缓存,下载过程又慢又容易断。

解决:打开gradle/wrapper/gradle-wrapper.properties,把distributionUrl里的版本号改成本机已验证可用的版本号,然后重新 Sync。如果连哪个版本可用都不确定,常见做法是在 Android Studio 里新建一个默认工程,看它用的是哪个 Gradle 版本,再照抄到distributionUrl上。也可以手动下载对应 zip 包放到用户目录的 Gradle wrapper 缓存目录下,离线同步,但路径层级要匹配,比较麻烦。

4.2 compileSdk 版本和本地 SDK 对不上

现象:sync 报错提示找不到 hash string 对应的 android 平台,或者 aapt2 相关编译异常,打开布局预览也是一片红。

原因:build.gradle里的compileSdk/targetSdk写的数字大于或小于本地已安装的 SDK 版本,Android Studio 找不到匹配的 platform 资源。

解决:打开 SDK Manager,安装对应版本的 Android 平台;或者反过来,把compileSdk改成已安装的版本,并让targetSdk跟随它同步调整。我个人更喜欢改工程而不是装一堆 SDK 版本,因为本地 SDK 缓存多了之后,不同平台版本混用反而容易出构建缓存异常。这里有玄学成分,但“改 compileSdk 指向已装版本”是复现成本最低的路子。

4.3 AndroidX 和旧 support 包冲突

现象:sync 报错提示依赖需要 AndroidX,或者运行时报NoClassDefFoundError,去网上搜又看到一堆“开 Jetifier”的说法。

原因:工程开了 AndroidX,但某个依赖库还是旧 support 包;或者反过来,gradle.properties 里没开 AndroidX,代码里却用了androidx开头的类。

解决:到gradle.properties里确认这两行配置:

android.useAndroidX=true android.enableJetifier=true

useAndroidX让工程把旧 support 库自动映射到 AndroidX 等价类,enableJetifier负责在构建时转换那些声明了旧包的三方库。改完后执行一次 Clean Project 再 Sync,通常就能把冲突压下去。如果个别依赖不支持 Jetifier,那就是库选型问题,只能换兼容版本。

4.4 本地 JAR 包没有被编译进去

现象:代码里引用了某个包,编译时报 “package xxx does not exist”,或者 APK 运行时抛ClassNotFoundException。

原因:下载包里带了一个 JAR 包文件。如果你的工程结构里 jar 放错了目录,或者build.gradle没有对它做依赖声明,Gradle 不会自动把它编译进 APK。

解决:把 JAR 包放到app/libs/目录,然后在 app 模块的dependencies里补一行:

implementation files('libs/xxx.jar')

如果这个目录里可能会陆续加入多个 jar,也可以用implementation fileTree(dir: 'libs', include: ['*.jar'])一把梭,省得每次加 jar 都得改脚本。改完记得 Sync 再构建,Competent 到这一步基本就是 Gradle 的依赖可见性问题。

4.5 真机连不上,点 Run 找不到设备

现象:工具栏设备列表是空的,插了手机也没反应,或者模拟器启动后一直被提示 offline。

原因:手机没开启 USB 调试,数据线只能充电不能传数据,或者设备弹窗上的“允许 USB 调试”授权没有点确认。

解决:先不急着在 Android Studio 里找设备,在命令行执行adb devices看输出。如果是 unauthorized,把手机上的调试授权弹窗重新确认一遍;如果列表为空,换原装数据线并重新插拔,在开发者选项里关掉再打开 USB 调试。实在不行执行adb kill-server和adb start-server重启服务。

提示:Windows 上同时挂着多个模拟器实例时,可能出现 adb 端口占用,表现为设备状态异常。把多余的模拟器全部退出,再重跑一次adb devices就能恢复。

5. 从“能跑”到“像 QQ”:把用户体验打磨到可以演示

代码能跑只是及格线,能演示才是作品的完成度。花半小时把这几个细节调完,整个 App 的观感会从“实训作业”变成“一个像样的 Demo”。这些改动不需要新增类,不需要改架构,全部落在资源文件和少量工具方法里。

5.1 细节一:圆角头像、气泡阴影和 Tab 选中状态单独放 drawable

仿 QQ 的视觉核心是圆角、阴影和状态反馈。这些不应该写在 Java 代码里,而是用 drawable 文件描述。比如聊天气泡:

<!-- bg_bubble_left.xml --> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#EEEEEE" /> <corners android:radius="12dp" /> </shape>

.solid定义填充色,.corners定义圆角半径。改成自己的主题色时直接改 color 值。右边气泡同理,只是填充色换成代表自己的颜色。把气泡和背景都收敛到 drawable 里,后面调主题就只需要动 colors.xml 和 shape,不用在 Java 里到处找setBackgroundColor。

Tab 选中状态是用户体验里的关键反馈。用 selector 让图标在选中和未选中之间切换:

<selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:drawable="@drawable/tab_msg_on" /> <item android:drawable="@drawable/tab_msg_off" /> </selector>

把这份 selector 设为 RadioButton 的android:button背景,同时配合button="@null"去掉默认圆圈。看到这里你就明白为什么资源包里 PNG 数量这么多——所有状态图标都得准备两套颜色。

5.2 细节二:时间格式化与未读红点

聊天列表里的时间戳如果不格式化,输出就是一串数字,演示时非常出戏。加一个工具方法就能解决:

private String formatTime(long timestamp) { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); return sdf.format(new Date(timestamp)); }

HH:mm是 24 小时制,hh:mm是 12 小时制,注意别混。如果想要“昨天上午 10:00”这样的相对时间,需要用日历类判断日期差,实训项目做到“今天显示时间、昨天显示昨天”就已经够惊艳了。

未读红点也不用切一张图片,用 layer-list 叠两个圆角 shape 就能画出带白边的红点,效果比贴图干净,而且在不同分辨率下不会糊。红点控件我习惯单独抽出一个BadgeView,没数据时设置 Visibility 为 gone,有数据时显示,逻辑清晰。

5.3 细节三:SharedPreferences 记住登录状态

很多演示项目杀进程重进就要重新登录,看起来不太真实。加一个本地会话存储,体验立刻上一个台阶:

private void saveSession(String account) { SharedPreferences sp = getSharedPreferences("session", MODE_PRIVATE); sp.edit().putString("account", account).apply(); } private boolean hasSession() { SharedPreferences sp = getSharedPreferences("session", MODE_PRIVATE); return sp.contains("account"); }

这里用apply()而不是commit(),前者异步写盘不阻塞主线程,后者同步等待返回,在 UI 线程里会有卡顿风险。演示项目只存用户名,不存密码,也不要在里面放任何敏感信息。如果你想让聊天记录也保留,那就得引入 SQLiteOpenHelper 或 Room,这会新增不少类,不是这份源码当前体量该做的事。

注意:SharedPreferences 适合保存少量配置,不适合存大量消息。硬塞大列表进去,读取时会把主线程卡到无响应。

6. 答辩前强制走一遍验证清单

把项目改完、准备演示或答辩之前,我会强制自己走一遍回归清单,而不是写到能编译就收工。这一轮花不了多少时间,但能避免绝大多数现场翻车。

第一个必查项:把这套源码拷贝到另一个目录,用另一台环境的 Android Studio 重新导入一次,确认十分钟内能冷启动编译。很多人只在原来的工程里点 Run 特别顺,换台电脑就卡在 Gradle 版本和 SDK 路径上,答辩现场换机器就直接崩。第二个必查项:冷启动后先进登录页,用写死账号走一遍完整流程,再杀进程重启,确认会话还在。第三,过一遍中文字符串没有乱码,小屏和大屏布局没有溢出。第四,Tab 图标切换后选中态正确,聊天气泡左右分明,红点显示位置没有偏。第五,检查 applicationId 和 namespace 是否还是原来的 demo 包名,答辩前改成自己的包名,避免留过多模板痕迹。

第六个必查项最容易被忽略:录屏演示时,把屏幕亮度调到自动,字号调大到看得清,切到开发者模式,关闭所有动画缩放,让页面切换不要拖泥带水。这套小配置在投影和录屏时非常管用。

这个项目我第一次看到时,也嫌它“就这么点东西”。后来我在上面加了会话分组、消息时间、记住登录状态,才意识到 9 个类的空间恰好能装下这些最常见的设计点。从那以后,我每次做完课程设计级别的 App,都会留出半天做回归验证,而不只是写完就交。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询