我正在参加「创意开发 投稿大赛」详情请看:创意开发大赛来了!

阅读本文,你将收获以下内容:

1、通过观察11年前的Android代码,了解安卓开发生态近十年间的演进。

2、通过了解这款创意App的功能,对IT男该如何运用技术做出反思。

3、不幸看到作者大学时期的照片,形象极其猥琐、狼狈、不堪……够了,谁在动键盘?!

前言

因为在的创作者群里比嘻哈,有人觉得我经常信口开河,尤其我写了那篇《我裁完兄弟们辞职了,转行做了一名小职员》后,有掘友评论:“这篇文章艺术成分很高”、“感觉是编故事”等等。

情绪宣泄App:十年前IT男编程撩妹纪实

其中,我文章里提到过一句:我大学期间搞过Android的APP去运作。

情绪宣泄App:十年前IT男编程撩妹纪实

今天,就说说这件事,一来展现下创意作品,二来给自己挽回一些微信……违心……维新……不要了。

女朋友

回到2010年,那时我上大二,谈了一个女朋友,她现在是我孩子的妈妈。

女朋友哪里都好,漂亮、温柔、大方,唯一一点就是脾气太大。

因为我不会制造浪漫,所以女朋友经常对我发脾气,一发脾气就不理我。

我一想,如果她不理我的时间设为N,如果N值无限大,那就相当于分手了,这感情不就黄了吗?

情侣在吵架冷战期间,如何才能不见面也能如同见面一般宣泄情绪,从而刷存在感呢?

可以做一款App来解决这个问题。

冷战红娘App

这款App我取名叫“冷战红娘”,意思是情侣在冷战期间调和关系的红娘媒介,并且我还亲自设计了LOGO和启动页。

情绪宣泄App:十年前IT男编程撩妹纪实

一个安卓小机器人,手里拿着玫瑰花,表示示好,头顶两个触角间发出电波,表示科技和智能。

那一年,我才19岁。不行了,我膨胀得快要爆掉了。

功能简介

本软件主要有三大功能:涂鸦对方照片、写情绪日记、告知情绪结果。

下面是首页的效果:

情绪宣泄App:十年前IT男编程撩妹纪实

下面是实现代码的目录结构:

情绪宣泄App:十年前IT男编程撩妹纪实

女朋友名字中带“冰”,我名字中带“磊”,因此项目名是LoveBing,包名是com.lei.lovebing01

有安卓小伙伴看到目录结构可能会发现少文件,说我在糊弄你,起码你的build.gradle得有吧。

朋友,这是2010年的安卓项目,那时的版本号是SdkVersion="8",也就是Android 2.2,现在最新版本已经到了API 32, Android 12了。从互联网时代来看,就好像是现在和清朝的区别。

那时还没有动态权限请求,存取文件也不用FileProvider,你可以随意读取其他程序的内部数据,应用层就可以静默发送短信和获取定位,开发者可以更好地实现自己的想法,不必受到很多限制。当然,这在现在看来是不安全的。所以,任何事物的成熟都是有周期的。

那时候也没有现在这么多的第三方框架,基本都是调用Android原生的API,操作数据库需要直接使用android.database.sqlite.SQLiteDatabase,SQL语句要自己写,操作异常要自己处理。

下面,就让我们跟随功能,结合代码,一起去剖析一下这款App吧。

涂鸦

女朋友生气不理我了,短信不回,电话不接,女生宿舍我又进不去。但是,她又有怨气没地方宣泄。这时,她就会打开这个功能,可以把我的头像摆出来,然后进行各种攻击,支持花色涂抹,支持往我的照片上放小虫子、扔臭鸡蛋、使用炸弹爆破效果等等。天啊,我这是怎么了……不但有这种想法,而且还开发出了功能。

其实,要实现这个功能,非常简单。

首先,整个页面的数据,是通过配置完成的,各种颜色,工具,以及图标,需要事先声明好。

//信手涂鸦里的数据=====================================================
//工具的名字
public final static String[] colortext={"红色", "黄色","绿色", 
	"粉色", "黑色", "紫色", "蓝色", "浅绿", "棕色"};
//工具的图片
public final static int[] colorpic = {
	R.drawable.color1, R.drawable.color2,
	R.drawable.color3, R.drawable.color4,
	R.drawable.color5, R.drawable.color6,
	R.drawable.color7, R.drawable.color8,
	R.drawable.color9 };
//信手涂鸦颜色选择
public  static final int red = 0;
public  static final int yellow = 1;
public  static final int green = 2;
public  static final int pink = 3;
public  static final int black = 4;
public  static final int purple = 5;
public  static final int blackblue = 6;
public  static final int lightgreen = 7;
public  static final int orange = 8;
//使用工具里的数据=====================================================
public final static String[] toolstext={"鸡蛋", "炸弹","生物","喷溅"};//工具的名字
public final static int[] toolspic = {//工具的图片
	R.drawable.dao, R.drawable.zhadan01,R.drawable.tool,R.drawable.penjian
};
public final static int[][] toolspic_01 = {//工具的图片使用后
	 {R.drawable.dao_01, R.drawable.dao_02, R.drawable.dao_03,R.drawable.dao_01}
	,{ R.drawable.baozha01, R.drawable.baozha02, R.drawable.baozha03, R.drawable.baozha04}
	,{R.drawable.tools_01, R.drawable.tools_02, R.drawable.tools_03, R.drawable.tools_04}
	,{R.drawable.penjian01, R.drawable.penjian02, R.drawable.penjian03, R.drawable.penjian04}
};

通过配置的方式,有一个极大的好处,那就是以后你增加新的工具,不用修改代码,直接修改配置文件即可。

下面一步就是使用一个Canvas画板,把上面的配置画到画布上,并响应用户的交互,为此我新建了一个CanvasView,它继承了View

public class CanvasView extends View{
    public Bitmap[][] bitmapArray;
    private Canvas  mCanvas;
    public CanvasView(Context context) {
        super(context);
        bitmapArray=new Bitmap[MyData.toolspic.length][4];//实例化工具数组
        //载入工具需要的图像
        InputStream bitmapis;
        for(int i=0; i<MyData.toolspic_01.length; i++){
            for(int j=0; j<MyData.toolspic_01[i].length; j++){
                bitmapis = getResources().openRawResource(MyData.toolspic_01[i][j]);
                bitmapArray[i][j] = BitmapFactory.decodeStream(bitmapis); 
            }
        }
        // 使用mBitmap创建一个画布
        mCanvas = new Canvas(mBitmap);
        mCanvas.drawColor(0xFFFFFFFF);//背景为白色
    }
    //在用户点击的地方画使用的工具
    public void drawTools(int bitmapID){
        Random rand = new Random();
        int myrand = rand.nextInt(bitmapArray[bitmapID].length);
        mCanvas.drawBitmap(bitmapArray[bitmapID][myrand], mX-bitmapArray[bitmapID]
            [myrand].getWidth()/2, mY-juli-bitmapArray[bitmapID][myrand].getHeight()/2, null);	
    }
    ……
}

上面只是部分关键代码,主要展示了如何加载图片,以及如何响应用户的操作,基本无难点。

女朋友毕竟花费了一番功夫,作品肯定要给她保留,因为她可能要展示给我看:你看,昨天你惹我多严重,我把你画成这样!

情绪宣泄App:十年前IT男编程撩妹纪实

涂鸦完成之后,文件保存到SD卡目录即可。权限管理方面,在AndroidManifest.xml中注册一下就行,除此之外,再无其他操作。

<!--  向SD卡写入数据的权限  -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

当然,现在不可以了,Android 6.0以后,你得动态申请权限了。

值得一说的是,上面的图片浏览控件叫ImageSwitcher,这是个老控件了,很简单就可以实现幻灯片浏览的效果。

日记

如果涂鸦无法完全解气,为了能让女朋友把生气的原因表述明白,我特意增加了这个生气日记的功能。效果等同于她面对面骂我,期望她写完了气也就消了。

情绪宣泄App:十年前IT男编程撩妹纪实

你觉得数据应该保存到哪里?我首选的是Android内嵌的Sqlite3数据库。

Android中最原始的sqlite数据库操作是这样的,先利用官方的SQLiteOpenHelper创建数据库和数据表。

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DBOpenHelper extends SQLiteOpenHelper {
	private static final String DATABASENAME = "angrydiary.db"; //数据库名称
	private static final int DATABASEVERSION = 1;//数据库版本
	public DBOpenHelper(Context context) {
		super(context, DATABASENAME, null, DATABASEVERSION);
	}
	public void onCreate(SQLiteDatabase db) {
		//建数据表 message  日记
		db.execSQL("CREATE TABLE message (message_id integer primary key autoincrement, 
                    message_title varchar(50), message_text varchar(500), message_state integer)");
	}
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL("DROP TABLE IF EXISTS message");
		onCreate(db);
	}
}

然后再写上操作数据表数据的方法。就拿生气日记信息的数据处理举例子。

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class MessageServiceDB {
	private DBOpenHelper dbOpenHelper;
	//构造函数
	public MessageServiceDb(Context context) {
		this.dbOpenHelper = new DBOpenHelper(context);
	}
	//保存数据
	public void save(Message message){
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		ContentValues values = new ContentValues();
		values.put("message_title", message.getTitle());
		values.put("message_text", message.getText());
		db.insert("message", null, values);
		db.close();
	}
	//获得数据
	public List<Message> getScrollData(Integer offset, Integer maxResult){
		List<Message> messageList = new ArrayList<Message>();
		SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
		Cursor cursor = db.query("message", null, null, null, null, null, "message_id desc", offset+","+ maxResult);
		while(cursor.moveToNext()){
			Integer message_id = cursor.getInt(cursor.getColumnIndex("message_id"));
			String message_title = cursor.getString(cursor.getColumnIndex("message_title"));
			String message_text = cursor.getString(cursor.getColumnIndex("message_text"));
			Message message = new TvMessage(message_id,message_title, message_text);
			messageList.add(message);
		}
		cursor.close();
		db.close();
		return messageList;
	}
	//删除一项数据
	public void delete(Integer id){
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		db.delete("message", "message_id=?", new String[]{id.toString()});
		db.close();
	}   
}

利用之前构造好的数据库帮助类dbOpenHelper,然后调用增删改查进行数据处理。这里面的增删改查,有两种方式实现,一种直接写Sql语句,另一种支持对象操作。我基本上都是用的对象操作,比如删除直接就是db.delete(xx)

日志列表我也是煞费苦心,为了便于了解女朋友还对哪些事情生气,我特意开发了生气事件黑白名单功能。一个事件可以反复标记是否原谅我了。这样可以了解女朋友心中还有哪些心结,可以让我逐个攻破。

情绪宣泄App:十年前IT男编程撩妹纪实

此处调用一个修改方法就可以了,其实就是取出message对象后重新setState一个新值就可以了。

//修改信息状态
public void update(Message message){
    SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
    ContentValues values = new ContentValues();
    values.put("message_state", message.getState());
    db.update("message", values, "message_id=?", new String[]{message.getId().toString()});
    db.close();
}

需要注意的是,对于每次打开数据库或者Cursor,都要记得关闭,不关闭不会影响功能,但是会带来风险。现在你使用各种框架的话,完全不用考虑这些操作,因为他们都帮你做了。

安卓开发,以前和现在,用SDK和用第三方框架,就像是汽车的手动挡和自动挡,其中的优劣,自己体会。虽然我是从老手动挡过来的,但是我站自动挡这边,因为我不会傻到逆着发展趋势去行走。

反馈

女朋友也涂鸦了,也写了生气日记,最后应该也累了。为了缓解她的疲劳,我特意开发了一个看图发愣的功能。只需要点开看图发愣,就会随机出现一个唯美的动态图,并且伴随着唰唰的雨声,可以让她看上几个小时,仔细思考人生,思考我这个男朋友值不值得交往。

情绪宣泄App:十年前IT男编程撩妹纪实

对于如何展示gif动态图,前端可能要骂人了,因为gif还需要动代码吗?浏览器不全给解释了。但是,当时我需要自己去解析,安卓原生的图片控件是无法展示动图的。所以,你看老一辈的程序员面临多少困难,新一代的程序员完全不用考虑这些。所以,你们应该把更多精力放在更高级的研究上,因为我相信你们也有你们的困难点。

这个图很美,我单独拿出来了,朋友们可以保存下来,自己看。或者,你试试自己去解析一下。

情绪宣泄App:十年前IT男编程撩妹纪实

好了,现在总该不生气了吧。针对于此时的场景,我又开发了一个快捷短信功能,女朋友可以选择短信模板,快速给我发送短信息,给我一个台阶,让我及时去哄她。她可以说不是她发的,是我开发的软件发的,这样可以避免“她先联系的我”这类不利立场的产生。

情绪宣泄App:十年前IT男编程撩妹纪实

我一贯执行配置策略,短信模板也是写到value文件夹下的xml文件中的。

<string-array name="message_ex">
    <item>不要生气了,我错了!</item>   
    <item>我不生气了,你快点陪我逛街!</item>   
    <item>讨厌,还不给我打电话!</item>   
    <item>我错了,我不该对你发火的!</item>   
    <item>三个小时内给我打电话!</item>   
    <item>快给我给我买爆米花!</item>     
</string-array>

关于发送短信,这里面有两点细节。

SmsManager smsManager = SmsManager.getDefault();
PendingIntent sentIntent = PendingIntent.getBroadcast(DuanxinActivity.this, 0, new Intent(), 0);
//如果字数超过70,需拆分成多条短信发送
if (text.length() > 70) {
    List<String> msgs = smsManager.divideMessage(text);
    for (String msg : msgs) {
    smsManager.sendTextMessage(phone, null, msg, sentIntent, null);                        
    }
} else {
    smsManager.sendTextMessage(phone, null, text, sentIntent, null);
}

第一,为什么不调用Intent(Intent.ACTION_SEND)让系统去发送短信?一开始确实是这样的。但是,后来改良为调用代码应用内发送了。因为我不想让女朋友离开我的软件,跳到第三方系统应用,避免用户跳出场景,因为有时候女朋友会夺命连环发短信(告知对方问题很严重,提高优先级),需要来回切程序,这样用着不爽,就更生气了。而且自己发送我还能捕获到发送结果,给出“发送成功”的温馨提示,但是交给第三方应用发送你是获取不到的。

第二,关于字数超过70,需要拆成多条短信,这也是经过实践得来的。满满的都是痛。

有同学不明白为什么要发短信,因为那时候还没有微信,微信是2011年才出来的。

后记

后来,经过不断反馈和改良,这款App越来越完善。

最后,女朋友看我这么用心地对待这份感情,尤其对于她反馈的软件问题,我询问地非常仔细(复现场景、发生时间、前后操作流程),修改地也非常及时(改完了就让她再试试看),她感觉我是一个靠谱和细心的人,于是她也慢慢地不再那么容易生气了。

再后来,有一个全国高校的大学生IT技能比赛,我的老师就让我拿这个作品参赛了,最后去了北京大学进行了决赛。

虽然这款App技术含量不高,但它是一款身经百战的App,它经过了多次迭代,因为用户体验和创意比较好,我最终获得全国第七名的成绩,荣获二等奖。

下面是证书,教育部的证书我是不敢造假的,我用Photoshop(是的,我也会做UI设计)做了简单的遮挡和放大,主要想让大家看一下日期确实是2011年,和我文章里描述的一样(我没有编故事)。

情绪宣泄App:十年前IT男编程撩妹纪实

这款App真的没有任何技术含量,无外乎控件的罗列、画板的绘制、数据的存储。我想现在的每一个大学生都能做的到。但是不夸张地讲,它看起来却是很强大的样子。而究其根源,我想应该就是它运用技术手段尝试去解决生活中的问题,让效果得到了放大,使它具备了生命力。

直至今天,我依然在做类似(用技术解决生活中的问题)的事情。

最后,我想借助App的标语结束本文,这也是我每天都会看到的一句话:

相信技术,传递价值。

我正在参加「创意开发 投稿大赛」详情请看:创意开发大赛来了!