今天一同事突然有个想法找到我来实现,我就先不介绍这个想法了,大意是需要自动生成android应用,中午就随便看了看,确定可行性,因为实在是太忙,只能挪到晚上来研究,终于搞定个大概了,下面做些记录

      首先需要安装ant,这个是必须的,如果已经有了就略过,
wget http://apache.dataguru.cn//ant/binaries/apache-ant-1.9.1-bin.tar.gz
export PATH=$PATH:$HOME/apache-ant/bin

      下面就直接到sdk了,我在http://developer.android.com/sdk/index.html上找了好久,结果藏的也太深了:“DOWNLOAD FOR OTHER PLATFORMS”下载sdk就行了

1
2
3
4
5
6
7
8
9
10
wget http://dl.google.com/android/android-sdk_r22.0.1-linux.tgz
tar -xzf android-sdk_r22.0.1-linux.tgz
export PATH=$PATH:$HOME/android/android-sdk/tools
 
#到此环境配置完了,接下来更新sdk,此动作很慢
android update sdk --no-ui #我更新到2.2就停止了
android list targets #找出自己要编译的版本,比如,我的2.2对应的target是1,那么
android create project -n hello -t 1 -p project/ -k com.mjix -a DefaultActivity #参数的解释就直接 --help吧, -t指定target
cd project
ant release #开始编译

      结果出现了一个超级坑爹的事情就是: libz.so.1: cannot open shared object file: No such file or directory,我再三检查,确信自己安装了,可以通过 1、yum -qa|grep zlib或者2、locate libz.so.1来查看,最后终于找到一位知音博客。下面就直接引用了:
因为现在的系统捏是尼玛64位滴,而该死的Android都是基于32开发的,它依赖的是32位的libz
那位同学的解决办法是export CFLAGS=-m32 后再编译zlib安装,我就偷个懒,直接yum install了一个32位的zlib。

      终于,成功的生成了:BUILD SUCCESSFUL。

接下来可以看到bin下面有一个 hello-release-unsigned.apk,未签名的apk,当然不能发布啦,好了,下面我们就来签名吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//生成一个自己的签名,其它自己填就好了
keytool -genkey -v -keystore hello.key -alias hello -keyalg RSA -keysize 2048 -validity 10000
//第一种签名方式
vi ant.properties //添加
key.store=hello.key
key.store.password=123456
key.alias=hello
key.alias.password=123456
//执行 ant release就可以看到 hello-release-unsigned.apk
 
//第二种签名方法:或者使用如下语句生成:
jarsigner -verbose -keystore hello.key -signedjar bin/hello_signed.apk ./bin/hello-release-unsigned.apk hello -keypass 123456
//最终生成的文件是bin/hello_signed.apk
 
//使用android sdk的zipalign工具优化已签名的apk文件
zipalign -v 4 bin/hello-release-unaligned.apk bin/hello-release-aligned.apk

下面给一些参考链接:

http://my.oschina.net/u/559701/blog/75333

http://developer.android.com/tools/devices/managing-avds-cmdline.html

http://www.android123.com.cn/androidkaifa/173.html

http://developer.android.com/tools/publishing/app-signing.html

无标签信息 2 条

      发现好久没有写过日志了,突然懒起来拽都拽不动,看着要过年了,又开售,天天成堆的群发短信弄的我焦头烂额,一晃春节马上就过年了。年前的时候接到一个33e9的短信网关开发需求,这个网关不同是,统一了三个网关上下行短信号码。其实说来也没啥,但他们要求用java开发,只有java提供的jar包是用socket开发,其它语言只能使用webservice,这么大用户量,用webservice不太现实,还好以前也写过java。

      之前有一套python开发的短信网关,主要分这么几块:1、http的api进程接受请求,短信入库,并插入日志库;2、3网关进程接收到来自http进程的信号通知,取数据发送,等待状态报告、上行消息等;3、日志进程,整理数据、发送状态、状态报告、上行消息等;4、上行服务负责把上行消息归类并通知各个上行处理插件进程。

      为了快速开发,把以前的python脚本升级了一下(主要是进程代码处理上),把网关进程换成了33e9的java代码,在python中有个threading.Event的模块非常好用,所以在java中就自造了一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class ThreadEvent {
    private boolean isset=false;
    private Thread _thread;
 
    /**
     * 事件是否被设置
     * @return
     */
    public Boolean isSet(){
        return isset;
    }
 
    /**
     * 设置事件
     * @return
     */
    public synchronized boolean set(){
        isset = true;
        notify();
        return isset;
    }
 
    /**
     * 事件等待
     * @param timeout wait in milliseconds
     * @throws Exception
     */
    public synchronized void xwait(int timeout) throws Exception{
        wait(timeout);
    }
 
    /**
     * 清除事件设置
     * @return
     */
    public synchronized boolean clear(){
        isset = false;
        return !isset;
    }
}

接下来是信号通知的代码,感知到数据更新后快速响应,而非sleep轮询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
    @SuppressWarnings("sun.misc.Signal")
    private synchronized void init(){
        dataEvent = new ThreadEvent();
        dataEvent.set();
 
        SignalHandler dataSig = new SignalHandler() {
            @Override
            public void handle(Signal signal) {
                dataEvent.set();
            }
        };
        //捕捉收到短信信号
        Signal sig = new Signal("USR2");
        Signal.handle(sig, dataSig);
    }
 
    public void run(){
        smsBean = new SmsBean();
        msgSend = new MsgSend();
        msgSend.start();
 
        int num = 0;
        while(!Service.END){
            try{
                if(!dataEvent.isSet()){
                    dataEvent.xwait(20000);
                }
                //System.out.println("check test");
                num = sendSms();
                if(num<1){
                    dataEvent.clear();
                }
            }catch (Exception ex){
                dataEvent.clear();
                ex.printStackTrace();
                Logging.error("smsMain while error:%s", ex.getMessage());
            }
        }
    }

      io信号通知事件更新的做法除了实现简单,而且可以减少不必要的轮询,而且快速响应,当然缺点是只能在单机部署代码。(或者能多机,但还不知道实现),其余代码都基本可以忽略不计了。

      开发完成后,写了一个小上行处理app,用户上行短信,下发一条对用户的回答,采用的是时下最火的小黄鸡代码啦,很简单,但很好用,代码上

View Code PYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
    def get_content(self, cont):
        url = 'http://api.simsimi.com/request.p'
        cont = cont.decode('gbk').encode('utf8')
        params = 'key=your-key&lc=ch&ft=1.00&text='+cont
        f = urllib.urlopen(url, params)
        ret = f.read()
        f.close()
        if ret:
            xret = JsonUtil.read(ret)
            ret = xret.has_key('response') and xret.get('response').decode('utf8').encode('gbk') or '谢谢你发来短信'
        else:
            ret = '谢谢你发来短信'
        return ret

      已经12点过了,但又想起一个功能点,就顺便记录一下吧,自定义listview,listview可以做出各式各样的操作,大致实现方式差不多,今天就实现一个类似微信,新消息数目提示的效果吧。以前也这么搞过,一下子把最近一段时间的积累都写一次,其实这样也有缺陷,少了错误重现和解决方法,其实种种都难免要在错误中不断尝试。下面先看效果图吧。

      实现方式,首先自定义listview的adapter,主要是对不同view的处理方式,少说话,多理会,来,代码,上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class ExtendAdapter extends BaseAdapter{
 
    private ArrayList<HashMap<String, String>> arrlist;
    private LayoutInflater inflater;
    private ExtendViewHolder eHolder;
    private Context context;
    private String[] from;
    private int[] to;
    private int resouseId;
 
    private class ExtendViewHolder {
        TextView msgNumText;
        TextView msgTitle;
        TextView msgDesc;
    }
 
    public ExtendAdapter(Context c, ArrayList<HashMap<String, String>> AlList, int resouse, String[] from, int[] to){
        this.context = c;
        this.resouseId = resouse;
        this.arrlist = AlList;
        this.from = from;
        this.to = to;
 
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
 
    @Override
    public int getCount() {
        return arrlist.size();
    }
 
    @Override
    public Object getItem(int position) {
        return arrlist.get(position);
    }
 
    @Override
    public long getItemId(int position) {
        return position;
    }
 
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView != null) {
            eHolder = (ExtendViewHolder) convertView.getTag();
        } else {
            convertView = inflater.inflate(resouseId, null);
            eHolder = new ExtendViewHolder();
 
            eHolder.msgTitle = (TextView)convertView.findViewById(to[0]);
            eHolder.msgDesc = (TextView)convertView.findViewById(to[1]);
            eHolder.msgNumText = (TextView)convertView.findViewById(to[2]);
            convertView.setTag(eHolder);
        }
 
        HashMap<String, String> appInfo = arrlist.get(position);
 
        if (appInfo != null) {
            String title = appInfo.get(from[0]);
            String desc = appInfo.get(from[1]);
            String msgNum = appInfo.get(from[2]);
 
            eHolder.msgTitle.setText(title);
            eHolder.msgDesc.setText(desc);
            if(!msgNum.equals("") && Integer.valueOf(msgNum)>0){ // 消息大于0
                eHolder.msgNumText.setVisibility(View.VISIBLE);
                eHolder.msgNumText.setText(msgNum);
            }else{
                eHolder.msgNumText.setText("0");
                eHolder.msgNumText.setVisibility(View.GONE);
            }
        }
        return convertView;
    }
 
}

下面来看一下activity中的代码吧,其实跟别的listview实现一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public void initListView(){
        msgClassList = (ListView)findViewById(R.id.msg_class_list);
 
        alist = new ArrayList<HashMap<String, String>>();
        HashMap<String, String> hmap = null;
        String str;
        DbMsgList dbMsg = new DbMsgList(this);
 
        for(int i=0, n= StatusConfig.className.length; i<n; i++){
            hmap = new HashMap<String, String>();
            hmap.put("title", StatusConfig.className[i]);
            hmap.put("desc", StatusConfig.classDesc[i]);
            hmap.put("msg_num", dbMsg.getNewMsgNum(StatusConfig.classType[i]).toString());
            alist.add(hmap);
        }
 
        msgAdap = new ExtendAdapter(this, alist, R.layout.msg_class_list,
                new String[]{"title", "desc", "msg_num"},
                new int[]{R.id.msg_class_title, R.id.msg_class_desc, R.id.msg_class_new_num});
 
        msgClassList.setAdapter(msgAdap);
    }

接下来发一下layout.msg_class_list的代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_height="wrap_content"
                android:layout_width="match_parent">
    <LinearLayout
            android:orientation="vertical"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:paddingLeft="10dip">
 
        <TextView
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:id="@+id/msg_class_title"
                android:layout_marginTop="8dip"
                android:layout_marginBottom="3dip"
                android:text="标题"
                android:textColor="#FF076099"
                android:textSize="20dip">
        </TextView>
 
        <TextView android:id="@+id/msg_class_desc"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:textColor="#079e80"
                  android:layout_marginLeft="10dip"
                  android:text="描述"
                  android:textSize="13dip" />
    </LinearLayout>
 
    <TextView android:layout_height="30dip"
            android:layout_width="30dip"
            android:textColor="#FFFF00"
            android:text="12"
            android:gravity="center"
            android:layout_marginRight="20dip"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:background="@drawable/msg_new_bg"
            android:id="@+id/msg_class_new_num"/>
 
</RelativeLayout>

      在android中一般数据存储估计也就这两种方法最常用了,之前写的所有应用几乎都用了SharedPreferences来存储消息,这种方法存储消息简单,缺陷是只能存储key:value形式的数据,此方法存储的数据放入xml中的,至于路径有点忘了,有兴趣的goo一下。要注意点的是,在做数据存数的时候,选在的key加入uid之类的信息,防止多用户登陆的时候串用了。这次在写这个消息app的时候使用了sqlite来存储消息,sqlite,超小型文件数据库,不介绍了,直接来代码。

      SharedPreferences比较适合存储小型数据,如用户的账号、密码、昵称这类key-value数据

1
2
3
4
5
6
7
8
SharedPreferences spre = getSharedPreferences("user_account", MODE_PRIVATE);
uname.setText(spre.getString("uname", ""));
upwd.setText(spre.getString("upwd", ""));
 
SharedPreferences.Editor edit = spre.edit();
edit.putString("uname", uname.getText().toString());
edit.putString("upwd", upwd.getText().toString());
edit.commit();

代码很直白,就不解释了。下面看这次遇到的一个sqlite的应用,我就原模原样的贴代码了,也懒得处理了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class DbMsgList extends SQLiteOpenHelper {
 
    private static final String DATABASE_NAME = "db_esun_msg_list.db";
    private static final int DATABASE_VERSION = 1;
    private SQLiteDatabase _db;
    private Cursor cur;
 
    public DbMsgList(Context context) {
        //CursorFactory设置为null,使用默认值
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
 
    //数据库第一次被创建时onCreate会被调用
    @Override
    public void onCreate(SQLiteDatabase db) {
        String sql = "CREATE TABLE IF NOT EXISTS esun_msg_list" +
                "(f_id INTEGER PRIMARY KEY AUTOINCREMENT, f_uname VARCHAR, f_mtid VARCHAR, " +
                "f_content TEXT, f_type VARCHAR, f_instime DATETIME, f_isread INTEGER)";
        db.execSQL(sql);
    }
 
    //如果DATABASE_VERSION值被改为2,系统发现现有数据库版本不同,即会调用onUpgrade
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("ALTER TABLE esun_msg_list ADD COLUMN other STRING");
    }
 
    public boolean addMsg(String mtid, String content, String type, String instime, int isread){
        _db = getWritableDatabase();
 
        String sql = "INSERT INTO esun_msg_list(f_id, f_uname, f_mtid, f_content, f_type, f_instime, f_isread)" +
                " VALUES (null, ?, ?, ?, ?, ?, ?)";
        Object[] args = {Mine.userName, mtid, content, type, instime, isread};
        _db.execSQL(sql, args);
        _db.close();
        return true;
    }
 
    public boolean setRead(String mtid){
        _db = getWritableDatabase();
        String sql = "UPDATE esun_msg_list SET f_isread = 1 WHERE f_uname = ? and f_mtid = ?";
        Object[] args = {Mine.userName, mtid};
        _db.execSQL(sql, args);
        _db.close();
        return true;
    }
 
    public boolean setReadAll(String type){
        _db = getWritableDatabase();
        String sql = "UPDATE esun_msg_list SET f_isread = 1 WHERE f_uname = ? and f_type = ?";
        Object[] args = {Mine.userName, type};
        _db.execSQL(sql, args);
        _db.close();
        return true;
    }
 
    public Cursor getMsg(String type, Integer offset, Integer num){
        _db = getWritableDatabase();
        String sql = "SELECT * FROM (SELECT * FROM esun_msg_list WHERE f_uname = ? and f_type = ? ORDER BY f_id DESC LIMIT ?, ?) ORDER BY f_id";
        cur = _db.rawQuery(sql, new String[]{Mine.userName, type, offset.toString(), num.toString()});
        return cur;
    }
 
    public boolean clearMsg(String type){
        _db = getWritableDatabase();
        String sql = "DELETE FROM esun_msg_list WHERE f_uname = ? and f_type = ?";
        Object[] args = {Mine.userName, type};
        _db.execSQL(sql, args);
        _db.close();
        return true;
    }
 
    public Integer getNewMsgNum(String type){
        _db = getWritableDatabase();
        String sql = "SELECT count(*) num FROM esun_msg_list WHERE f_uname = ? and f_type = ? and f_isread = 0";
        String[] args = new String[]{Mine.userName, type};
        cur = _db.rawQuery(sql, args);
        cur.moveToFirst();
        Integer num = cur.getInt(cur.getColumnIndex("num"));
        cur.close();
        _db.close();
        return num;
    }
 
    public void dropTable(){
        _db = getWritableDatabase();
        String dsql = "DROP TABLE IF EXISTS esun_msg_list";
        _db.execSQL(dsql);
        _db.close();
    }
 
    public void close(){
        Common.log("close db!");
        try{
            if(_db != null) _db.close();
            if(cur != null) cur.close();
        }catch (Exception ex){
            Common.errorReport(ex);
        }
    }
}

      第四篇了,这段时间一直在折腾域名备案,本来一切以后ok把资料寄过去的时候说资料填写不对,打个电话给客服,真tmd想大骂一顿,因为这个资料填写完成后扫描上传,通过审核了才寄过去的,好吧,我认了你们,再寄一次,正在管局等指示,原则上要1个月,猜想管局其实很闲,但碍于面子,大小也是个政府级别,你搞个申请,我至少也得缓两天给你处理吧,这跟明星耍大牌是一样的,算了,不说了,说多了怀孕咋办。

      一鼓作气吧,那就再写一篇吧,先闲扯一下,晚上看了一下《北京故事》,关于旅行的事情,突然感觉活的好没品,把一年中80%以上的空余时间给了工作,不应该这样生活,还没有结婚,还没有小孩,还感觉不到压力的时候应该出去走走,看看世界的五彩缤纷,正在计划着去趟厦门呢。
      正传,android中消息提示,无非震动、声音、呼吸灯提示,但需要判断好提示场景下的提示方式,比如,应用进入后台,应该声音提示+呼吸灯提示;应用打开,但不在当前活动,则震动提示,如果应用在当前活动界面,则不用提示。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
    protected void handleMsgData(MsgObject msg){
        DbMsgList dbMsg = new DbMsgList(service);
 
        String mtid = msg.getStringItem("mtid");
        String content = msg.getStringItem("content");
        String type = msg.getStringItem("type");
        String instime = msg.getStringItem("instime");
 
        //添加消息到sqlite数据库
        dbMsg.addMsg(mtid, content, type, instime, 0);
        dbMsg.close();
 
        String title = "未知主题";
        if(StatusConfig.classTypeConf.containsKey(type)){
            title = StatusConfig.className[StatusConfig.classTypeConf.get(type)];
        }
 
        判断应用是否在后台
        if(!Utils.isAppOnForeground(service)){
            Utils.showMsgNotification(service, title, content);
        }
 
    }
 
    public static boolean isAppOnForeground(Context ctx){
        String packName = ctx.getPackageName();
        List<RunningAppProcessInfo> appProcess = getManager(ctx).getRunningAppProcesses();
        if(appProcess == null) return false;
        //遍历所有的应用,找出自己
        for(RunningAppProcessInfo info:appProcess){
            if(info.processName.equals(packName) && info.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND){
                return true;
            }
        }
        return false;
    }
 
    private static NotificationManager _notificationManager;
    public static void showMsgNotification(Context ctx, String title, String content){
        try{
            if(_notificationManager == null){
                _notificationManager = (NotificationManager)ctx.getSystemService(Context.NOTIFICATION_SERVICE);
            }
            String appName = ctx.getResources().getString(R.string.app_name);
            Notification notification = new Notification(R.drawable.icon_chat, appName, System.currentTimeMillis());
            Intent nint = new Intent(ctx, MainActivity.class);
            PendingIntent pint = PendingIntent.getActivity(ctx, 0, nint, 0);
            notification.setLatestEventInfo(ctx, title, content, pint);
            notification.defaults |= Notification.DEFAULT_SOUND; //消息提示
            notification.flags |= Notification.FLAG_AUTO_CANCEL; //点击消息后,自动取消消息
            notification.flags |= Notification.FLAG_SHOW_LIGHTS; //呼吸灯提示
 
            _notificationManager.notify(0x001, notification); //第一个消息id保证同时只有一个提示消息
        }catch (Exception ex){
            Common.errorReport(ex);
        }
    }

      写了快大半年android了,发现写的博客数量太少了。很多东西其实都可以总结记录一下的,哎,只怪太懒,难得今天这么休闲,就一口气了。其实这篇文章介绍的主题也是今天才搞定的,初衷是,服务端发送富文本标签到客户端可以显示,TextView本来是可以显示图片的,比如微信中插入图片类似,问题是什么呢,图片异步加载的实现方式,客户端想做到用户体验非常爽需要花费大量功夫处理。废话多了就啰嗦了,写了一个自己比较好用的类,先演示使用方法吧,想用的人是不关心你的实现。

1
2
3
4
5
TextView htmlTextView = (TextView)findViewById(R.id.text_view);
Spanned sp = Html.fromHtml(content,
                    new HtmlImageGetter(htmlTextView, "/esun_msg", _defaultLoading),
                    null);
htmlTextView.setText(sp);

      介绍一下HtmlImageGetter析构中的三个参数,第一个是Textview对象,主要是为了加载完成后刷新才能显示图片用,第二个参数是,加载出来的图片保存到sd卡的目录,第三个参数是图片未加载时候先显示的加载中Drawable对象。怎么样,用起来还算ok吧,因为图快速实现,所以几乎都忽略了注释,下面就直接来类了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
public class HtmlImageGetter implements Html.ImageGetter{
 
    private TextView _htmlText;
    private String _imgPath;
    private Drawable _defaultDrawable;
 
    public HtmlImageGetter(TextView htmlText, String imgPath, Drawable defaultDrawable){
        _htmlText = htmlText;
        _imgPath = imgPath;
        _defaultDrawable = defaultDrawable;
    }
 
    @Override
    public Drawable getDrawable(String imgUrl) {
 
        String imgKey = Common.md5(imgUrl);
        String path = Environment.getExternalStorageDirectory() + _imgPath;
        FileUtil.createPath(path);
 
        String[] ss = imgUrl.split("\\.");
        String imgX = ss[ss.length-1];
        imgKey = path+"/" + imgKey+"."+imgX;
 
 
        if(FileUtil.exists(imgKey)){
            Drawable drawable = FileUtil.getImageDrawable(imgKey);
            if(drawable != null){
                drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
                return drawable;
            }else{
                Common.log("load img:"+imgKey+":null");
            }
        }
 
        URLDrawable urlDrawable = new URLDrawable(_defaultDrawable);
        new AsyncThread(urlDrawable).execute(imgKey, imgUrl);
        return urlDrawable;
    }
 
    private class AsyncThread extends AsyncTask<String, Integer, Drawable> {
        private String imgKey;
        private URLDrawable _drawable;
 
        public AsyncThread(URLDrawable drawable){
            _drawable = drawable;
        }
 
        @Override
        protected Drawable doInBackground(String... strings) {
            imgKey = strings[0];
            InputStream inps = NetWork.getInputStream(strings[1]);
            if(inps == null) return _drawable;
 
            FileUtil.saveFile(imgKey, inps);
            Drawable drawable = Drawable.createFromPath(imgKey);
            return drawable;
        }
 
        public void onProgressUpdate(Integer... value) {
 
        }
 
        @Override
        protected void onPostExecute(Drawable result) {
            _drawable.setDrawable(result);
            _htmlText.setText(_htmlText.getText());
        }
    }
 
    public class URLDrawable extends BitmapDrawable {
 
        private Drawable drawable;
 
        public URLDrawable(Drawable defaultDraw){
            setDrawable(defaultDraw);
        }
 
        private void setDrawable(Drawable ndrawable){
            drawable = ndrawable;
            drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable
                    .getIntrinsicHeight());
            setBounds(0, 0, drawable.getIntrinsicWidth(), drawable
                    .getIntrinsicHeight());
        }
 
        @Override
        public void draw(Canvas canvas) {
            drawable.draw(canvas);
        }
    }
}

在代码中有两个封装的方法,一个是网络请求图片和保存图片到sd卡中,就简单贴贴代码了,没甚么技术含量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
public class NetWork {
 
    public static String getHttpData(String baseUrl){
        return getHttpData(baseUrl, "GET", "", null);
    }
 
    public static String postHttpData(String baseUrl, String reqData){
        return getHttpData(baseUrl, "POST", reqData, null);
    }
 
    public static String postHttpData(String baseUrl, String reqData, HashMap<String, String> propertys){
        return getHttpData(baseUrl, "POST", reqData, propertys);
    }
 
    /**
     * 获取赛事信息
     * @return
     */
    public static String getHttpData(String baseUrl, String method, String reqData, HashMap<String, String> propertys){
        String data = "", str;
        PrintWriter outWrite = null;
        InputStream inpStream = null;
        BufferedReader reader = null;
 
        HttpURLConnection urlConn = null;
        try{
            URL url = new URL(baseUrl);
            urlConn = (HttpURLConnection)url.openConnection();
            //启用gzip压缩
            urlConn.addRequestProperty("Accept-Encoding", "gzip, deflate");
            urlConn.setRequestMethod(method);
            urlConn.setDoOutput(true);
            urlConn.setConnectTimeout(Config.TIME_OUT);
 
            if(propertys != null && !propertys.isEmpty()){
                Iterator<Map.Entry<String, String>> props = propertys.entrySet().iterator();
                Map.Entry<String, String> entry;
                while (props.hasNext()){
                    entry = props.next();
                    urlConn.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }
 
            outWrite = new PrintWriter(urlConn.getOutputStream());
            outWrite.print(reqData);
            outWrite.flush();
 
            urlConn.connect();
 
            //获取数据流
            inpStream = urlConn.getInputStream();
            String encode = urlConn.getHeaderField("Content-Encoding");
 
            //如果通过gzip
            if(encode !=null && encode.indexOf("gzip") != -1){
                Log.d(Config.LOG_TAG, "get data :" + encode);
                inpStream = new GZIPInputStream(inpStream);
            }else if(encode != null && encode.indexOf("deflate") != -1){
                inpStream = new InflaterInputStream(inpStream);
            }
 
            reader = new BufferedReader(new InputStreamReader(inpStream));
 
            while((str = reader.readLine()) != null){
                data += str;
            }
        }catch (MalformedURLException ex){
            Common.errorReport(ex);
        }catch (IOException ex){
            Common.errorReport(ex);
        }finally{
            if(reader !=null && urlConn!=null){
                try {
                    outWrite.close();
                    inpStream.close();
                    reader.close();
                    urlConn.disconnect();
                } catch (IOException ex) {
                    Common.errorReport(ex);
                }
            }
        }
 
        Log.d(Config.LOG_TAG, "[Http data]["+baseUrl+"]:" + data);
        return data;
    }
 
    /**
     * 获取Image信息
     * @return
     */
    public static Bitmap getBitmapData(String imgUrl){
        Bitmap bmp = null;
        Log.d(Config.LOG_TAG, "get imgage:"+imgUrl);
 
        InputStream inpStream = null;
        try{
            HttpGet http = new HttpGet(imgUrl);
            HttpClient client = new DefaultHttpClient();
            HttpResponse response = (HttpResponse)client.execute(http);
            HttpEntity httpEntity = response.getEntity();
            BufferedHttpEntity bufferedHttpEntity = new BufferedHttpEntity(httpEntity);
 
            //获取数据流
            inpStream = bufferedHttpEntity.getContent();
            bmp = BitmapFactory.decodeStream(inpStream);
 
        }catch (Exception ex){
            Common.errorReport(ex);
        }finally{
            if(inpStream !=null){
                try {
                    inpStream.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
 
        return bmp;
    }
 
    /**
     * 获取url的InputStream
     * @param urlStr
     * @return
     */
    public static InputStream getInputStream(String urlStr){
        Log.d(Config.LOG_TAG, "get http input:"+urlStr);
        InputStream inpStream = null;
        try{
            HttpGet http = new HttpGet(urlStr);
            HttpClient client = new DefaultHttpClient();
            HttpResponse response = (HttpResponse)client.execute(http);
            HttpEntity httpEntity = response.getEntity();
            BufferedHttpEntity bufferedHttpEntity = new BufferedHttpEntity(httpEntity);
 
            //获取数据流
            inpStream = bufferedHttpEntity.getContent();
        }catch (Exception ex){
            Common.errorReport(ex);
        }finally{
            if(inpStream !=null){
                try {
                    inpStream.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
        return inpStream;
    }
}
 
package com.esun.trade.lib;
 
import java.io.*;
 
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.Log;
import com.esun.trade.inc.Config;
 
public class FileUtil {
 
    private static int FILE_SIZE = 4*1024;
 
    public static boolean hasSdcard(){
        String status = Environment.getExternalStorageState();
        if(status.equals(Environment.MEDIA_MOUNTED)){
            return true;
        }
        return false;
    }
 
    public static boolean createPath(String path){
        File f = new File(path);
        if(!f.exists()){
            Boolean o = f.mkdirs();
            Log.i(Config.LOG_TAG, "create dir:"+path+":"+o.toString());
            return o;
        }
        return true;
    }
 
    public static boolean exists(String file){
        return new File(file).exists();
    }
 
    public static File saveFile(String file, InputStream inputStream){
        File f = null;
        OutputStream outSm = null;
 
        try{
            f = new File(file);
            String path = f.getParent();
            if(!createPath(path)){
                Log.e(Config.LOG_TAG, "can't create dir:"+path);
                return null;
            }
 
            if(!f.exists()){
                f.createNewFile();
            }
 
            outSm = new FileOutputStream(f);
            byte[] buffer = new byte[FILE_SIZE];
            while((inputStream.read(buffer)) != -1){
                outSm.write(buffer);
            }
            outSm.flush();
        }catch (IOException ex) {
            Common.errorReport(ex);
            return null;
 
        }finally{
            try{
                if(outSm != null) outSm.close();
            }catch (IOException ex) {
                Common.errorReport(ex);
            }
        }
        Common.log("[FileUtil]save file:"+file+":"+Boolean.toString(f.exists()));
 
        return f;
    }
 
    public static Drawable getImageDrawable(String file){
        if(!exists(file)) return null;
        try{
            InputStream inp = new FileInputStream(new File(file));
            return BitmapDrawable.createFromStream(inp, "img");
        }catch (Exception ex){
            Common.errorReport(ex);
        }
        return null;
    }
}

      这两天一直在搞一个公司app的东东,收获很丰富,真到是经验都是在磨砺中积累而来。想起之前写的德州扑克的app,处理太烂了,接下来把这几天积累的经验总结一下。
      1、关于socket消息处理的问题。场景,一个应用开一个socket服务和服务器通信,但关于事件的处理,如,服务端发送来有聊天信息、游戏信息、更新信息等等,每个活动(Activity)只关心自己的事件。看处理事件走过的三个阶段

      1) 在德州扑克中第一次做app与socket通信,所以当时采用了类似插件思想的消息订阅模式,每个activity(如果需要)都对应一个model(class类)通过注册事件关心列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//model的基础接口
public interface Model {
 
    /**
     * 处理消息
     * @param msg
     */
    public void handleMessage(MsgObject msg);
 
}
 
//下面这样注册信息给对应的模块
EventCenter.register(EventConfig.CONNECT_RESP, Mconnect.getInstance());
EventCenter.register(EventConfig.PING_RESP, Mconnect.getInstance());
EventCenter.register(EventConfig.TABLE_JOIN_RESP, Mtable.getInstance());

      这样看起来很好很合理,但代码写多了就问题来了,如在游戏大厅activity中收到一条游戏逻辑的消息,本来不应该处理,但处理了,这就导致可能用户退出了当前activity的时候还在响应消息。

      2) 这次写的这个消息应用刚开始也这样,前两天睡觉前仔细琢磨一下,发现个不错的办法:关心消息的每个活动都继承自一个事件父类,事件父类有对消息的基本处理方法,每个活动关心该消息就重新该消息接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class EventActivity extends Activity {
 
    public static EventActivity self;
 
    public EventActivity(){
        self = this;
    }
 
    public void handleMessage(MsgObject msg){
        switch (msg.cmd){
            case Key.X_CONNECT:
                this.handleLogin(msg);
                break;
 
            case Key.MSG_DATA:
                this.handleMsgData(msg);
                break;
        }
    }
 
    public void handleLogin(MsgObject msg){
        Common.log("[handle_login]:"+msg.code);
    }
 
    public void handleMsgData(MsgObject msg){}
}
 
//这是初始化事件模块
public class InitEvent extends Model{
 
    public static EventActivity eAct = new EventActivity();
 
    private InitEvent(){
        EventCenter.register(Key.X_CONNECT, eAct);
        EventCenter.register(Key.MSG_DATA, eAct);
        EventCenter.register(Key.X_ACT_CHAT, eAct);
        EventCenter.register(Key.X_ACT_CHAT_GET, eAct) ;
    }
 
    private static InitEvent _instance;
 
    public void init(EventActivity eActivity){
        eAct.self = eActivity;
    }
 
    public static InitEvent getInstance(){
        if(_instance == null){
            _instance = new InitEvent();
        }
        return _instance;
    }
}
 
//在每个活动中都初始化这个模块
InitEvent.getInstance().init(this);

      这样每个消息都会发送到当前活动的相应方法来处理,绝不会出现两个activity会处理同一个消息的情况。

      3) 本来写好了应用发现不错,问题来了,如果用户把应用放入后台,应用待机后,socket会自动断开,这样就不能后台消息推送。要把socket放入后台,最简单直接的方法莫过于放置于service中,但问题当然也来了,service和activity通信的问题,如果放入后台,应用其实是不会处理service中的消息。至于service和activity的消息处理机制分两种情况,1、service通知activity,需要通过广播消息事件;2、activit通知service,需要通过Binder来实现,这个google一下就有很多了,我这里就直接代码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class SocketService extends Service {
 
    private final SocketBinder sbinder = new SocketBinder();
    private Communicate comm = Communicate.getInstance();
 
    @Override
    public IBinder onBind(Intent intent) {
        Common.log("service onbind");
        return sbinder;
    }
 
    @Override
    public void onCreate(){
        Common.log("service onCreate");
        super.onCreate();
 
        comm.setOnConnectListen(new Communicate.OnConnectListen() {
            @Override
            public void onConnect(Socket socket) {
                if(Mine.userName != null && !Mine.userName.equals("")){
                    MsgObject msg = Utils.getLoginPackage();
                    comm.send(msg);
                }
 
                sendMsg(Key.B_CONN_OPEN, "socket on connect!");
            }
 
            @Override
            public void onClosed(Socket socket) {
                sendMsg(Key.B_CONN_CLOSE, "socket on closed!");
            }
 
            @Override
            public void onReceiveMsg(byte[] content) {
                MsgObject msg = MsgFormat.getMsgObject(content);
                Common.log("【receive】:"+msg.data);
                new SocketMsg(SocketService.this).handleMessage(msg);
 
                Intent intent = new Intent(getResources().getString(R.string.broad_msg));
                intent.putExtra("type", Key.B_CONN_MSG);
                intent.putExtra("cmd", msg.cmd);
                intent.putExtra("data", msg.data);
                sendBroadcast(intent);
            }
        });
 
        comm.runAsync();
    }
 
    @Override
    public void onStart(Intent intent, int startId){
        super.onStart(intent, startId);
        sendMsg(Key.B_TEST_MSG, "service on start!");
        Common.log("service onstart");
 
        if(comm.isStop || Communicate.sconn == null || Communicate.sconn.isClosed()){
            comm.runAsync();
        }
    }
 
    private void sendMsg(int type, String data){
        Intent intent = new Intent(getResources().getString(R.string.broad_msg));
        intent.putExtra("type", type);
        intent.putExtra("data", data);
        sendBroadcast(intent);
    }
 
    /**
     * 此方法是activity调用service的方法
     **/
    public void sendToSocetMsg(MsgObject msg){
        comm.send(msg);
    }
 
    public class SocketBinder extends Binder{
        public SocketService getService(){
            return SocketService.this;
        }
    }
}

      在主activity中通过以下方法来初始化和开启service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    public void initService(){
        //过滤广播给Receriver
        IntentFilter filter = new IntentFilter();
        filter.addAction(getResources().getString(R.string.broad_msg));
        registerReceiver(SocketReceiver.getInstance(), filter);
 
        //启动服务
        Intent it = new Intent(this, SocketService.class);
        startService(it);
        //绑定activity和service
        bindService(it, serviceConn, BIND_AUTO_CREATE);
    }
 
    private ServiceConnection serviceConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //保存service实例供activity调用service
            Utils.socketService = ((SocketService.SocketBinder)iBinder).getService();
        }
 
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Utils.socketService = null;
        }
    };

      如果activity需要发送socket消息到服务端可以使用:Utils.socketService.sendToSocetMsg(msg)方法了,当然自己可以再定义一些别的被调用方法。

      现在对处理逻辑很清楚了吧,而且彻底把消息和处理分离。

无标签信息 0 条

      做手机应用很容易遇到这个需求,网上也有很多人实现了过程,这次刚好也遇到了,只在setThumb中添加了几句缩小按钮的代码以适应应用的需要,下面看一下实现效果吧:

      //在这里写点记事吧,就不新开了。这段时间一直忙于写android版的德州游戏,最初花了一周多点时间就出了一个粗略版应用,接下来大刀阔斧的把所有布局重新设计,大约改版了3-4次,主要是之前没有这方面经验,为了适应动画需求而做,终于在上周历时7-8天出了个动画版本。之前他们开会也确认德州android最终会交由北京开发,但我们还是继续保持,主要是为了做技术积累。

      星哥有天早上来了兴奋的给我说他终于想到要做什么了,把500wan小秘书改成app应用:1、节约短信成本;2、消息实时触达;3、内容多样化。恩,确实很好的一个设计,而且我也觉得我们有那个能力去实现。希望这个想法可以早日提上开发历程。

      那天看见有人在公司传阅这个博客,之前也出现过这样的事情,以前写过一些比较上火的文章,提到了很多人和事,最后不得不删除,前端时间还是想写一些,主要是为了记录走过的这些路,经历的这些点滴,所以在首页加了公司访问限制,对不住了。还是很想让看我博客的人知道不要传阅,看看娱乐还是可以的。

      好了,算是插了一段广告吧,时间有点晚了,贴完代码睡觉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
package com.esun.texas.view;
 
import android.content.Context;
import android.graphics.*;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AbsSeekBar;
import android.widget.SeekBar;
 
 
public class VerticalSeekbar extends SeekBar {
    private Drawable mThumb;
    private int height;
    private int width;
    public interface OnSeekBarChangeListener {
        void onProgressChanged(VerticalSeekbar VerticalSeekBar, int progress, boolean fromUser);
        void onStartTrackingTouch(VerticalSeekbar VerticalSeekBar);
        void onStopTrackingTouch(VerticalSeekbar VerticalSeekBar);
    }
 
    private OnSeekBarChangeListener mOnSeekBarChangeListener;
 
    public VerticalSeekbar(Context context) {
        this(context, null);
    }
 
    public VerticalSeekbar(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.seekBarStyle);
    }
 
    public VerticalSeekbar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
 
    public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
        mOnSeekBarChangeListener = l;
    }
 
    void onStartTrackingTouch() {
        if (mOnSeekBarChangeListener != null) {
            mOnSeekBarChangeListener.onStartTrackingTouch(this);
        }
    }
 
    void onStopTrackingTouch() {
        if (mOnSeekBarChangeListener != null) {
            mOnSeekBarChangeListener.onStopTrackingTouch(this);
        }
    }
 
 
    void onProgressRefresh(float scale, boolean fromUser) {
        Drawable thumb = mThumb;
        if (thumb != null) {
            setThumbPos(getHeight(), thumb, scale, Integer.MIN_VALUE);
            invalidate();
        }
        if (mOnSeekBarChangeListener != null) {
            mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser);
        }
    }
 
    private void setThumbPos(int w, Drawable thumb, float scale, int gap) {
        int available = w+getPaddingLeft()-getPaddingRight();
        int thumbWidth = thumb.getIntrinsicWidth();
        int thumbHeight = thumb.getIntrinsicHeight();
        available -= thumbWidth;
        // The extra space for the thumb to move on the track
        available += getThumbOffset() * 2;
        int thumbPos = (int) (scale * available);
        int topBound, bottomBound;
        if (gap == Integer.MIN_VALUE) {
            Rect oldBounds = thumb.getBounds();
            topBound = oldBounds.top;
            bottomBound = oldBounds.bottom;
        } else {
            topBound = gap;
            bottomBound = gap + thumbHeight;
        }
        thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
    }
 
    protected void onDraw(Canvas c)
    {
        c.rotate(-90);
        c.translate(-height,0);
        super.onDraw(c);
    }
 
    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
        height = View.MeasureSpec.getSize(heightMeasureSpec);
        width = View.MeasureSpec.getSize(widthMeasureSpec);
        this.setMeasuredDimension(width, height);
    }
 
    private Bitmap getThumbImage(Bitmap bmp, int width, int height){
        int w = bmp.getWidth(); int h = bmp.getHeight();
        Matrix m = new Matrix();
        m.postScale((float)width/w, (float)height/h);
        Log.d("test", "__"+w+"___"+h+"___"+width+"___"+height);
        return Bitmap.createBitmap(bmp, 0, 0, w, h, m, true);
    }
 
    @Override
    public void setThumb(Drawable thumb){
        Bitmap bmp = ((BitmapDrawable) thumb).getBitmap();
        int w = 25;
        int h = (int)(w*2.2766);
        bmp = getThumbImage(bmp, w, h);
 
        mThumb = new BitmapDrawable(bmp);
        super.setThumb(mThumb);
    }
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(h, w, oldw, oldh);
    }
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            return false;
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                setPressed(true);
                onStartTrackingTouch();
                trackTouchEvent(event);
                break;
 
            case MotionEvent.ACTION_MOVE:
                trackTouchEvent(event);
                attemptClaimDrag();
                break;
 
            case MotionEvent.ACTION_UP:
                trackTouchEvent(event);
                onStopTrackingTouch();
                setPressed(false);
                break;
 
            case MotionEvent.ACTION_CANCEL:
                onStopTrackingTouch();
                setPressed(false);
                break;
        }
        return true;
    }
    private void trackTouchEvent(MotionEvent event) {
        final int Height = getHeight();
        final int available = Height - getPaddingBottom() - getPaddingTop();
        int Y = (int)event.getY();
        float scale;
        float progress = 0;
        if (Y > Height - getPaddingBottom()) {
            scale = 0.0f;
        } else if (Y  < getPaddingTop()) {
            scale = 1.0f;
        } else {
            scale = (float)(Height - getPaddingBottom()-Y) / (float)available;
        }
 
        final int max = getMax();
        progress = scale * max;
 
        setProgress((int) progress);
    }
 
    private void attemptClaimDrag() {
        if (getParent() != null) {
            getParent().requestDisallowInterceptTouchEvent(true);
        }
    }
    public boolean dispatchKeyEvent(KeyEvent event) {
        if(event.getAction()==KeyEvent.ACTION_DOWN)
        {
            KeyEvent newEvent = null;
            switch(event.getKeyCode())
            {
                case KeyEvent.KEYCODE_DPAD_UP:
                    newEvent = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_DPAD_RIGHT);
                    break;
                case KeyEvent.KEYCODE_DPAD_DOWN:
                    newEvent = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_DPAD_LEFT);
                    break;
                case KeyEvent.KEYCODE_DPAD_LEFT:
                    newEvent = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_DPAD_DOWN);
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    newEvent = new KeyEvent(KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_DPAD_UP);
                    break;
                default:
                    newEvent = new KeyEvent(KeyEvent.ACTION_DOWN,event.getKeyCode());
                    break;
            }
            return newEvent.dispatch(this);
        }
        return false;
    }
}
无标签信息 0 条

      项目需要加入声音,之前也写过一个小app,用了MediaPlayer,但是这次这个声音极其短,播放的时候没有反应只有一条日志,忘了内容了,大意就是内容太短,最后发现播放这类提示应应该是用SoundPool来播放,应用出来后,玩了一段时间发现没声音了,奇怪,什么意思,赶紧搜搜,下面这文章写的很不错:http://blog.sina.com.cn/s/blog_71d26ff00100uzci.html,把内容copy过来存档:
    SoundPool的用法就不再提了,网上资料多,就说说在实际应用中可能会遇到的一些奇葩的问题,这些问题应该是和底层实现上有关系。
1、AudioFlinger could not create track, status: -12
    SoundPool即音效池,在创建的时候 maxStream这个参数代表能够同时播放的最大音效数,这里切忌合理使用,写的太大后会报AudioFlinger could not create track, status: -12 。。。。一旦报了这个错,你就听不到声音了,呵呵。
2、256个音效
    当调用load方法的时候实际就是把音效加载到了 SoundPool中,此时返回的streamId其实就是该音效在SoundPool中的Id,这个ID从0还是1来着(有点记不清了)递增,不过要注意的是,不要超过 256 这个临界点。也就是说第257个声音加载进去后,调用play方法其实是播不出来的,说不定还会挤掉一些前面加载好的声音。这个256的限制通过查看SDK源码基本就能了解清楚,它底层就那么实现的,用一个类似堆栈来存。
3、unload方法和release方法
    如果你音效多,也不要指望unload方法来清除掉一些音效后再load新的进去,虽然unload后音效卸载了,但是前面分给它在SoundPool里面的Id可没有释放掉,也就是说这个时候你load新的进去只会在后面继续累加,然后累加多了就超过256了,然后就就听不到声音,然后就没有然后了。要想彻底清掉前面的音效请使用release方法,它会连内存中占用的资源一起释放掉。
其他还有点什么呢,load需要一点点时间,load后不要马上unload,load ---play--unload的做法并不可取,不要load太大的音效,它只会申请1M的内存空间。SoundPool出错后通常会看到retuen的值是0。

      最后我亲测了一次,发现果然是load值超过了256后就没有声音了,所以就用了一个HashMap把这个声音值存储了,结果发现还是解决不了问题,只能在值快到达200时主动release掉,release掉后要重新测试化播放器。下面把我的代码贴一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package com.esun.texas.controller;
 
import android.app.Activity;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.util.Log;
import com.esun.texas.R;
import com.esun.texas.config.Config;
import com.esun.texas.lib.Common;
import com.esun.texas.model.Mactivity;
 
import java.util.HashMap;
 
/**
 * Created by IntelliJ IDEA.
 * User: tuyl
 * Date: 12-4-1
 * Time: 下午4:15
 * To change this template use File | Settings | File Templates.
 */
public class Csound {
 
    public static MediaPlayer player = new MediaPlayer();
    public static SoundPool spool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);
    public static AudioManager mgr = (AudioManager)Mactivity.NEW_ACTIVITY.getSystemService(Context.AUDIO_SERVICE);
 
    public static int CARD_SOUND = R.raw.card;
    public static int CHECK_SOUND = R.raw.check;
    public static int CHIPS_SOUND = R.raw.chips;
    public static int GATHER_CHIPS_SOUND = R.raw.chips_gathering;
    public static int FOLD_SOUND = R.raw.fold;
    public static int MY_TURN_SOUND = R.raw.myturn;
    public static int ONTURN_SOUND = R.raw.onturn;
 
    public static boolean isMute = false;
 
    public static HashMap<Integer, Integer> sounds = new HashMap<Integer, Integer>();
 
    private static int getSound(int resId){
        if(!sounds.containsKey(resId)){
            int spload = spool.load(Mactivity.NEW_ACTIVITY, resId, 1);
            sounds.put(resId, spload);
            Log.d(Config.LOG_TAG, "init sound:"+resId);
        }
        return sounds.get(resId);
    }
 
    /**
     * 播放短音效
     * @param resId
     */
    public static void play(int resId){
        try{
            if(isMute) return; //静音
 
            final int spload = spool.load(Mactivity.NEW_ACTIVITY, resId, 1);
            Log.d(Config.LOG_TAG, "play sound:"+resId+"___"+spload);
            spool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener(){
                public void onLoadComplete(android.media.SoundPool soundPool, int i, int i1){
                    try{
                        float streamVolumeCurrent=mgr.getStreamVolume(AudioManager.STREAM_MUSIC);
                        float streamVolumeMax=mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
                        float volume=streamVolumeCurrent/streamVolumeMax;
                        spool.play(spload, volume, volume, 0, 0, 1);
                    }catch (Exception ex){
                        Common.errorReport("Csound.play.onLoadComplete", ex);
                    }
                }
            });
            if(spload > 200){
                spool.release();
                spool = new SoundPool(10, AudioManager.STREAM_SYSTEM, 5);
            }
 
        }catch (Exception ex){
            Common.errorReport("Csound.play", ex);
        }
    }
 
 
    public static void playMusic(int resSound){
        try{
            if(isMute) return; //静音
 
            Activity act = Mactivity.NEW_ACTIVITY;
            AssetFileDescriptor afd = act.getResources().openRawResourceFd(resSound);
 
            player.reset();
            player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
            player.prepare();
            player.start();
 
            afd.close();
 
            player.setOnCompletionListener(new MediaPlayer.OnCompletionListener(){
                public void onCompletion(MediaPlayer arg0){
                    player.release(); //释放资源
                }
            });
        }catch (Exception ex){
            Common.errorReport("Csound.playMusic", ex);
        }
    }
}
无标签信息 0 条
20 / 04 / 2012 admin

      刚开始写的时候就是直接从网上copy,因为像快速出原型,结果花费了好多时间做调试,代码离想象中总是差很远,最后我实在受不了了,只能自己一个字一个字的码出了一个类库,贴出来,分享给有用的人。使用方法:

1
2
RC4 rc4 = new RC4(Info.RC4_KEY);
String data = rc4..make("body");

这段代码我想解释都解释不了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.esun.texas.lib;
public class RC4{
    public int[] box = new int[256];
 
    public RC4(String key){
        byte[] k = key.getBytes();
        int i = 0, x = 0, t = 0, l = k.length;
 
        for(i=0; i<256; i++){
            box[i] = i;
        }
 
        for(i=0; i<256; i++){
            x = (x+box[i]+k[i%l]) % 256;
 
            t = box[x];
            box[x] = box[i];
            box[i] = t;
        }
    }
 
    public byte[] make(byte[] data){
        int t, o, i=0, j = 0, l = data.length;
        byte[] out = new byte[l];
        int[] ibox = new int[256];
        System.arraycopy(box, 0, ibox, 0, 256);
 
        for(int c=0; c<l; c++){
            i = (i+1) % 256;
            j = (j+ibox[i]) % 256;
 
            t = ibox[j];
            ibox[j] = ibox[i];
            ibox[i] = t;
 
            o = ibox[(ibox[i] + ibox[j]) % 256];
            out[c] = (byte)(data[c] ^ o);
        }
        return out;
    }
}
无标签信息 0 条