资讯动态

Eclipse时代Android个人中心开发:登录态、头像与缓存实战

发布时间:2026/10/17 4:49:35 来源:尧图企业网站定制
这已经是这个系列第五篇了。前面几篇把整个博学谷项目的框架、主页、课程列表、详情页一路搭了下来到这一篇终于轮到用户模块——“我的”。这个模块在电商、教育、工具类App里都长得差不多上面一个头像、中间一堆功能入口、底部再来个退出登录。看起来简单实际上它涉及登录态、用户信息持久化、头像选择与裁剪、缓存清理、版本更新提示这些杂活几乎把客户端本地存储和界面交互的边边角角都摸了一遍。如果你现在打开Android Studio写个个人中心可能半天就完事。但在Eclipse ADT那个年代没有Gradle没有AndroidX连依赖包都要自己往libs里扔稍不注意就是R文件丢失、图片OOM、中文乱码三座大山。这篇就按我当时做博学谷项目“我”模块的完整过程来写从页面设计、存储方案、核心逻辑到一路踩过的坑尽量把能“抄作业”的细节都放出来。1. 模块拆解这个“我”字背后藏着哪些活儿1.1 页面上看到的部分先别急着写布局把“我的”页面打开对着线框图一项项列。当时的博学谷项目需求文档里“我的”模块主要承担三块内容用户信息区头像、昵称、个人签名一句话介绍点击可以进入个人资料编辑页。功能入口区我的课程、我的收藏、我的笔记、意见反馈、检查更新、清除缓存、关于我们。账户操作区退出当前账号。这几块内容层级差异很大。用户信息区是核心每个页面几乎都要访问功能入口区是跳板点击后进入各自业务页面账户操作区则涉及登录态的清理。我最初的方案是把整个页面做成一个ScrollView套各种自定义组合布局后来发现入口太多、代码太冗余改成了布局拼装。顶部信息区用线性布局或相对布局直接写在主XML里下面的入口区用一个主容器代码里循环生成条目或者直接在XML里写死十几个TextView——考虑到后续要维护我推荐用封装好的单条Item布局反复include代码清晰得多。1.2 页面背后那些不可见的逻辑界面只是表象“我的”模块真正的复杂度在下面几层登录态管理。App冷启动时要知道用户是否已登录才能决定显示“默认头像点击登录”还是“真实头像昵称”。用户数据存储。昵称、头像、签名这些信息总不能每次打开App都去服务器拉需要本地缓存一套。头像选择的跨界面通信。从相册选图、裁剪、上传、回显每一步都涉及临时文件、权限和Bitmap处理。缓存统计与清理。要统计App缓存大小还要在清理后把界面数字刷新回来。很多初学者容易把“我”的模块当成纯静态UI来做觉得放几个TextView就够了。真要到上线级别这层皮下面每一行都是逻辑。拿缓存来说你需要在SD卡和应用私有目录里遍历文件算大小再递归删除还要小心别把用户数据库和SharedPreferences文件删了否则用户要崩溃。1.3 数据存储方案的取舍开发前我在用户信息到底存哪这个问题上犹豫过。选项有三个纯内存、SharedPreferences、SQLite。纯内存肯定不行App一重启就丢。SQLite能干但为了存十个字段专门建库建表写DAO多少有点小题大做。最终的方案是用户基本信息昵称、头像路径、签名、登录Token放进SharedPreferences方便随时取和学习强相关的数据课程记录、收藏列表放进SQLite。这个分工在项目里实测下来非常舒服读用户信息不需要查表写的时候也是apply一把梭简单可靠。SharedPreferences本质是一个XML文件存在应用的私有目录里不需要权限就能读写。里面存String、int、boolean这些基础类型完全够用。头像这种图片文件不能直接塞进XML存的是文件路径图片本身放到getExternalFilesDir或者缓存目录。这套套路在当时算标准答案放到现在也还是常规做法。2. 环境准备与关键技术选型2.1 Eclipse与ADT的版本搭配用Eclipse做Android开发第一道坎是环境。我当时用的Eclipse LunaADT插件的版本大概在23左右配套的SDK Build Tools是23以上。如果你是新装环境要特别注意JDK版本和Eclipse的位数必须一致否则“Unrecognized VM option”这类报错能把人整崩溃。项目结构上Eclipse的Android工程没有Gradle的module概念就是一个Android Application Project。src是Java代码res是资源gen目录会自动生成R.java千万别手贱去改。libs里放第三方jar包然后右键Link Source或者直接放进去ADT会自动把它打进APK。博学谷项目里没有引入太多第三方库因为用了很多自定义控件第三方依赖少反而清爽。还有一个老生常谈的问题Eclipse的编码格式默认是GBK而这个项目里所有文件我都用UTF-8保存。也正因如此模拟器上中文乱码几乎成了必现问题后面专门讲怎么处理。2.2 用户信息存储SharedPreferences为主、SQLite为辅前面说过用户信息我存在SharedPreferences里。这里给出我实际用的几个键名结构一目了然public class UserLocalStore { private static final String SP_NAME user_info; private static final String KEY_LOGIN_STATUS is_login; private static final String KEY_USER_ID user_id; private static final String KEY_NICKNAME nickname; private static final String KEY_SIGNATURE signature; private static final String KEY_AVATAR_PATH avatar_path; private static final String KEY_TOKEN token; }封装一个UserLocalStore类把读写方法集中管理。比如保存登录信息的方法public void saveUserInfo(User user) { SharedPreferences sp context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor sp.edit(); editor.putBoolean(KEY_LOGIN_STATUS, true); editor.putString(KEY_USER_ID, user.getUserId()); editor.putString(KEY_NICKNAME, user.getNickName()); editor.putString(KEY_SIGNATURE, user.getSignature()); editor.putString(KEY_AVATAR_PATH, user.getAvatarPath()); editor.putString(KEY_TOKEN, user.getToken()); editor.apply(); }SharedPreferences的很多坑都出在commit和apply的混用上。apply是异步写盘效率高commit是同步写盘有返回值。我当时在退出登录时用了commit因为需要立刻拿到true/false来决定要不要跳转登录页。在保存用户信息时用apply就够别听人说什么apply丢数据——在Activity生命周期结束前调用apply系统会等写盘完成这个坑在后来无数项目里基本没再犯过。2.3 头像与图片加载方案当时没有Glide项目里加载图片的方案很原始ImageView直接setImageBitmap或者用BitmapFactory.decodeFile加载本地文件。第一步是头像来源我做了两个入口拍照和相册选择。拍照需要设一个File的Uri把拍完的照片存进getExternalCacheDir外部缓存目录不需要任何存储读写权限然后通过Intent传过去。相册选择用Intent.ACTION_PICK从系统相册拿图。拿到原始图片之后必须做两件事压缩和裁剪。系统裁剪Intent是最省事的做法Intent cropIntent new Intent(com.android.camera.action.CROP); cropIntent.setDataAndType(imageUri, image/*); cropIntent.putExtra(crop, true); cropIntent.putExtra(aspectX, 1); cropIntent.putExtra(aspectY, 1); cropIntent.putExtra(outputX, 200); cropIntent.putExtra(outputY, 200); cropIntent.putExtra(return-data, true);注意这个系统裁剪Activity在不同厂商ROM上表现不一样有的机型会闪退或直接跳过。为了稳定我后来自己写了个简单的裁剪页或者退一步用“方形头像不做圆角直接将相册图片缩放居中显示”的方案。在一套项目里稳定性优先级远高于功能华丽度。3. 从零搭出“我”的界面3.1 整体布局搭建我把“我的”页面的根布局写成了垂直方向的LinearLayout好处是层级简单坏处是顶部信息区、功能入口区、底部退出按钮三块挤在一起时需要靠权重和滚动配合。实际项目的布局是外层一个LinearLayout内部放一个ScrollView包住内容主体底部再把退出登录按钮固定在根布局下部。这样内容多了可以滚退出按钮始终可见交互上最顺手。用户信息区用了一个背景为主题色的RelativeLayout高度大概120dp。里面左侧放头像ImageView右侧放昵称TextView和签名TextView整个区域再盖一个透明的点击监听点击跳个人资料页。头像用BitmapUtils工具类加载如果读到本地路径不存在就展示默认头像。功能入口区我封装了一个Item条目包含左侧图标、中间标题、右侧“”箭头这个条目反复复制使用LinearLayout android:layout_widthmatch_parent android:layout_height50dp android:orientationhorizontal android:gravitycenter_vertical android:backgrounddrawable/item_selector ImageView android:idid/iv_icon android:layout_width24dp android:layout_height24dp android:layout_marginLeft15dp/ TextView android:idid/tv_title android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:layout_marginLeft12dp android:textSize14sp android:textColor#333333/ ImageView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginRight15dp android:srcdrawable/arrow_right/ /LinearLayout这里要重点说item_selector这是当时很常见的点击反馈技巧。selector文件放在drawable目录下默认一个背景按下状态一个颜色比如浅灰色。selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:state_pressedtrue color android:color#F0F0F0/ /item item color android:color#FFFFFF/ /item /selector把这个文件设置为条目的background点击时自动出现按压反馈不需要在Java代码里绑监听性价比极高。3.2 圆形头像与圆角处理的土办法圆形头像在现在的App里是标配但在Eclipse年代没有现成的CircleImageView可以用只能自己写一个圆形裁剪或者用BitmapShader。我当时用的方案是自定义一个CircleImageView类核心代码就一段Override protected void onDraw(Canvas canvas) { if (bitmap null) { super.onDraw(canvas); return; } canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, getWidth() / 2f, paint); }关键是初始化时设置paint的ShaderBitmapShader shader new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); paint.setShader(shader);这段代码有个前提bitmap必须是正方形否则圆会被拉伸变形。所以在设置头像前必须先把图片裁剪成正方形再丢给CircleImageView。我写了一个BitmapUtil.cropToSquare()方法居中截取最长边简单实用public static Bitmap cropToSquare(Bitmap bitmap) { int width bitmap.getWidth(); int height bitmap.getHeight(); int side Math.min(width, height); int x (width - side) / 2; int y (height - side) / 2; return Bitmap.createBitmap(bitmap, x, y, side, side); }3.3 列表项点击态与自定义弹窗功能入口的点击事件我当时在XML里的条目上都加了一个android:onClickonItemClick然后在Activity里写一个统一的onClick方法通过view.getId()区分条目。相比给每个Item单独setOnClickListener这样代码集中度更高也方便后期在同一个方法里埋点统计。个人资料的修改需要弹窗。Android自带的AlertDialog样式太丑我自定义了一个Dialog布局放两个EditText一个取消一个确定。自定义Dialog要记住设置window的背景为透明否则四周会有默认白边。关键代码Dialog dialog new Dialog(this); dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setContentView(R.layout.dialog_edit_info); Window window dialog.getWindow(); window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));窗口的宽度建议设置成屏幕宽度的85%太宽显得突兀太窄输入费劲。当时没有PercentRelativeLayout所以用WindowManager拿到屏幕宽高再按比例计算WindowManager.LayoutParams params window.getAttributes(); params.width (int) (screenWidth * 0.85); dialog.getWindow().setAttributes(params);这套方案在Eclipse项目里非常通用后来我迁移到Android Studio也照样沿用。4. 核心业务逻辑实现4.1 登录状态拦截“我”的模块有一个关键交互逻辑未登录时点击头像、我的课程、我的收藏等入口不能直接进对应页面而是先跳转登录页已登录时才能继续。这个逻辑不只在“我的”页面用写课、评论、下单前的判断都要。我在基类BaseActivity里写了一个工具方法protected boolean checkLogin() { if (UserLocalStore.isLogin(this)) { return true; } else { ToastUtils.showShort(this, 请先登录); startActivity(new Intent(this, LoginActivity.class)); return false; } }然后在每个需要登录的按钮点击事件里第一行调用if (!checkLogin()) return; // 继续执行已登录的逻辑这套写法简单粗暴但项目里用得很顺手。唯一要注意的是登录成功后回到原页面原页面的数据不会自动刷新。必须在Activity的onResume里重新加载一遍用户数据或者使用startActivityForResult并在onActivityResult里处理。我在项目里两种都用了建议组内统一用onResume刷新不容易漏。4.2 数据回显与二次校验用户信息区的数据加载在onResume里统一做private void initUserInfo() { boolean isLogin UserLocalStore.isLogin(this); if (isLogin) { nickname UserLocalStore.getNickname(this); signature UserLocalStore.getSignature(this); avatarPath UserLocalStore.getAvatarPath(this); tvNickname.setText(nickname); tvSignature.setText(TextUtils.isEmpty(signature) ? 这个人很懒什么都没留下 : signature); if (!TextUtils.isEmpty(avatarPath)) { Bitmap bitmap BitmapUtil.loadLocalBitmap(avatarPath); if (bitmap ! null) { ivAvatar.setImageBitmap(bitmap); } } } else { tvNickname.setText(点击登录); tvSignature.setText(登录后享受更多服务); ivAvatar.setImageResource(R.drawable.ic_default_avatar); } }这段代码里我吃过的亏有两个。第一签名和昵称从SharedPreferences取出来可能是空字符串直接setText会导致UI上留下一块空白所以要判断TextUtils.isEmpty并设置默认文案。第二头像路径可能指向一个已经被删除的临时文件此时BitmapFactory.decodeFile返回null整个ImageView就会变空白。必须先判空再setImageBitmap。4.3 修改资料与头像上传修改资料包括昵称、签名、头像三者。昵称和签名我用同一个自定义Dialog处理。Dialog弹出时把当前值放在EditText里确定后先做非空校验通过后调用保存方法。这里有个细节接口那边要求昵称不能为空但签名可以。校验逻辑要区分字段。我当时封装了一个TextUtil.isEmpty然后分别针对两个EditText判断。头像上传是个大工程。流程是点击头像 → 判断登录 → 弹出选择框拍照/相册→ 得到图片Uri → 裁剪 → 压缩 → 保存到本地文件 → 更新SharedPreferences里的avatar_path → 刷新ImageView。压缩这步最容易漏。手机拍出来的照片动辄3MB以上不压缩就加载到内存里200dp的ImageView用不了那么多像素白白导致OOM。我的压缩函数大致是这样public static Bitmap getCompressedBitmap(String filePath, int reqWidth, int reqHeight) { BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeFile(filePath, options); int width options.outWidth; int height options.outHeight; int inSampleSize 1; while (width / inSampleSize reqWidth || height / inSampleSize reqHeight) { inSampleSize * 2; } options.inSampleSize inSampleSize; options.inJustDecodeBounds false; return BitmapFactory.decodeFile(filePath, options); }这个inSampleSize采样压缩是当时解决OOM的通用套路先解析宽高再按比例抽样最后一次性加载到内存。reqWidth和reqHeight我传的是200配合系统裁剪输出尺寸一张头像内存占用能控制在200KB以内在低端模拟器上也能流畅显示。4.4 意见反馈与检查更新功能入口里“意见反馈”和“检查更新”是我认为最能体现工程完整度的两个页面。意见反馈页面我给用户提供了一个EditText输入框和提交按钮。提交时先做空判断再弹一个“提交成功”的Toast关闭页面。实际项目中应该接网络接口但因为博学谷的后端模拟接口没有完全就绪我先用了本地保存的方式把用户反馈写到SharedPreferences里等之后的版本再接网络。检查更新逻辑当时也做了本地模拟。点击检查更新如果本地SP中保存过“最新版本号”和当前版本号对比版本号不同则弹出对话框提示有新版本否则Toast提示已经最新版本。虽然没接真实的更新下载接口但完整的流程和状态判断已经有了后面接服务器时只需要替换那一个网络请求方法。4.5 清除缓存计算与退出登录的起于止清除缓存这个功能很多App里都做得很虚点击后干脆弹个Toast“缓存已清理”就走了。这次项目里我把它做实了。计算缓存大小的核心是递归遍历目录public static long getCacheSize(File dir) { long size 0; File[] files dir.listFiles(); if (files null) return 0; for (File file : files) { if (file.isDirectory()) { size getCacheSize(file); } else { size file.length(); } } return size; }这里要遍历的目录一般有两个getCacheDir()和getExternalCacheDir()。遍历的时候千万小心别把getFilesDir()里的数据库文件也删了。做完遍历之后在UI里显示String sizeDesc Formatter.formatFileSize(this, totalSize);Formatter是Android自带的自动把字节数转成KB、MB比手写好用。清理的操作就是递归删除文件。但deleteRecursively()这个方法当时 Java 7 还没普及我用自己写的deleteAllFiles递归删。退出登录在代码层面要做的就三件事清空用户信息相关SP、跳转登录页、关闭当前页面。我在项目里加了一个小细节清空SharedPreferences之前先确认用户真的想退出弹一个AlertDialog确认框。这个确认框别用默认的稍微自定义样式整个App的调性更统一。5. 异常排查与避坑笔记5.1 R文件彻底消失和switch-case失效Eclipse开发里最经典的坑莫过于R.java突然不见。通常发生在两种情况下有资源文件命名非法比如文件名带了大写字母、数字开头或者res目录下有文件被误删导致资源索引不合法。解决办法很简单先看res目录里有没有报红叉的资源文件逐个修正然后Project → Clean勾选需要清理的项目等ADT自动重建R.java。如果Clean完还没有大概率是某个XML里引用了不存在的资源这时候看Problems视图一条条改。第二个坑是ADT 22之后R类里的字段不再是常量了switch-case里不能直接引用R.id.xxx会报case表达式必须是常量的错误。我一开始很纳闷后来才明白这是Google官方坑。解决方法也简单把switch改成if-elseint id view.getId(); if (id R.id.btn_edit_info) { // ... } else if (id R.id.btn_my_course) { // ... }别想着用IDE自动修复它不会帮你改这个老老实实手写。5.2 头像加载图片OOM个人中心最容易OOM的地方就是头像。相册里选一张1080P的大图直接decode进内存手机RAM稍微小点就崩。除了前面讲的inSampleSize压缩还要注意Bitmap用完之后的回收。在切换头像或页面销毁时回收旧Bitmapif (oldBitmap ! null !oldBitmap.isRecycled()) { oldBitmap.recycle(); }不过这里有坑如果一个Bitmap被多个ImageView引用回收后其他ImageView再draw就会崩溃。我的经验是在个人资料页头像处加载头像时每次都用cropToSquare返回一个新Bitmap旧Bitmap先recycle不要直接复用同一个全局Bitmap对象给多个页面否则回收时机很难控制。5.3 中文乱码与Eclipse编码设置做“我”模块时很多人会直接在Java代码里写中文字符串比如“退出登录”“我的收藏”结果模拟器上全变成乱码。根因是Eclipse默认工作空间编码是GBK而项目文件是UTF-8。我当时的解决办法分三步右键项目 → Properties → Resource → Text file encoding选择UTF-8。如果已经乱码的文件先全选内容复制出来再把文件编码改成UTF-8粘贴回去。打开Window → Preferences → General → Workspace把Text file encoding改成UTF-8让以后新文件默认就是UTF-8。字符串资源我干脆全部抽到res/values/strings.xml里不在Java代码里直接写字面量。这样即使编码出问题改字符串一个文件就够不用翻遍所有类。5.4 模拟器性能问题与真机调试Eclipse自带的Android模拟器启动慢、响应慢是出了名的尤其是跑“我的”模块这种图片操作密集的页面。我建议直接拿真机调试开发机开启USB调试用adb install直接装效率翻倍。真机调试唯一要注意的是同一个App在Debug签名和Release签名状态下不能直接覆盖安装。我当时切换签名调试时ADB经常报“INSTALL_FAILED_UPDATE_INCOMPATIBLE”处理办法是卸载旧包重新装。如果不想反复卸载可以在Eclipse的Run Configuration里指定Release签名覆盖Debug签名但一般没必要开发阶段Debug足矣。5.5 列表条目复用导致的数据错乱我的入口区虽然XML写死但“我的课程”列表页里使用了ListViewitem复用是绕不开的坑。在Adapter的getView里如果不给ImageView设默认图、不给TextView设默认文本滑动时就会看到错乱的数据。我在博学谷项目里遇到过典型场景第一行是“已购买”状态第二行是“未购买”状态滑动到后面未购买的行却显示成了已购买。原因就是item复用了convertView还在但position变了holder里绑定的数据没清零。解决方案是在getView开头把所有控件重置TextView tvState holder.tvState; tvState.setText(); ivCover.setImageResource(R.drawable.default_cover);先把状态清空再根据数据赋值这样无论item怎么复用都不会出现残留问题。我最后想补充的一点实操心得“我的”模块虽然处在App的边缘位置却是最能体现一个开发者对状态管理、生命周期和异常处理理解深度的模块。如果你真想做透不要只停留在布局和点击事件上试着把头像加载、缓存清理、登录态切换这些细节全部真实跑通一次收获远比写一百个“Hello World”快。这个模块未来要扩展的方向也很明确加入消息提醒红点、用户等级、签到积分、多语言切换入口都是在现有功能入口区上加条目的事。只要布局和逻辑分层做得足够清晰后续加需求会非常从容。做完博学谷的“我”模块我对Eclipse这个老古董的怨气消了不少。很多问题都是自己不注意编码规范、不熟悉资源管理机制带来的跟IDEA本身没多大关系。工具是旧但Android开发的根基逻辑一点都没变。到现在写新项目我依然会想起当年在Eclipse里一遍遍Clean、重启、找R文件的时光——那时候没有gradle帮忙每一行路径配置都靠手工也正是那段经历让我对Android工程结构的理解比后来直接上手Studio的开发者扎实得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑