Category Archives: Android

新浪微博Android客户端实战(1-前言)

时下流行风,书籍前头喜欢冠上“人人都”,比如《人人都是产品经理》,《编程ING:人人都能学会程序设计》,我也想跟个风,这年头,不写个微博客户端,都不好意思说自己是做Android开发的。

目前网上已经有一大把教程如何写客户端,写的挺不错的,但有些内容已经过时,而且代码不是很完整。而code.google.com 有很多开放的代码,但又缺乏文档,有些代码写得不敢恭维,对代码有洁癖的我,实在无法看下去了。本博客很久不更新,一些网友怂恿我写一些关于Android开发的文章,想来想去,还是以某个应用的开发为题材吧。新浪官方的客户端为了兼容低版本,使用的api还是老的,现在Android 4.0 使用全新的 UI设计、交互体验,简洁流畅。我认为,一个好的 app 应该与操作系统保持设计的一致性。本系列文章基于 Ice Cream Sandwich,旧的 api不再使用。

以我的经验,写Android应用程序大部分时间花在UI,页面布局上,真正写代码的时间反而少。本客户端的目标不是做个大而全的东西,2-8定律无处不在,实现20%的功能,满足正常使用即可。基本功能:登录,Timeline,详细信息,发文,回复,转载。其他砍掉,事实上,一般人的使用习惯无非就是阅读信息,发文,评论,repost,用心研究这几个功能,做到极致,就很了不起了。

W.Jason Gilmore 在《PHP与MySQ程序设计》里写道:”优秀的程序员会编写可靠的代码,而卓越的程序员则会重用优秀程序员的代码。“。使用新浪微博提供的SDK,再加上一些优秀的Android UI 开源组件,我们可以很快速的开发一款客户端。

目标读者:本文不会step-by-step讲得很详细,前提假设你会搭建开发环境,会一些起码的编程基础,看过ApiDemos,正好挽起袖子干一些事。所有的代码都放在github上,可以下载编译安装运行,有个直观感觉,比什么都好。好了,让我们开始新的征程。

Android 上的消息推送通知(Push Notification)

Android froyo 之后,引入了 C2DM,基于XMPP 协议实现的推送机制。C2DM 的优点无需多言,但缺点也是显而易见:
1. 需要google账号。这一点约束太大了,很多手机厂家把google Apps给阉割了。
2. 国内服务不稳定,原因你懂得的。如果有能力在国外有服务器,可以很快的发送消息到C2DM 服务器,从国内post 数据到 Google server,几乎没响应。
3. C2DM 仅支持 Android 2.2 以上

C2DM 运行在系统级别上,系统内存少时不容易被kill。C2DM与Gmail、Gtalk等共用同一个连接,减少耗电。跟iOS不一样,Android支持App常驻进程,所以大家都不愿意用。现在一些恶意的Android软件,安装后后台开启一个服务,定时向用户PUSH垃圾广告,很邪恶。国内互联网公司都瞎折腾这样那样云,但就没有一家愿意开发和提供一个Google C2DM的墙内替代品。

目前基于长连接push的开源软件有:

AndroidPN

AndroidPN 是一个基于XMPP协议的java开源Android push Notification 实现,包含server与client,server 使用SSH框架,默认后台使用jetty,数据库是hsqldb,该服务器端基本上是在openfire基础上修改实现的,据说微信在Android上的推送实现也是基于openfire,具体未详。官方上的版本已经很久没更新,而且还有一些bugs,比如:
1. 当服务器端重启的时候,客户端就无法在连接到服务器.
2. 不支持离线消息。
3. 推送多条消息重复问题

可以改造成tomcat版本,已经有很多网友在做这样的事。

MQTT

IBM 产品,使用php写的,据说外国网友在论坛上爆料,Facebook Android客户端的推送使用这个。

没有条件自己搞 push 服务器的,可以使用第三方服务

Urban Airship http://urbanairship.com/
http://www.push-notification.org/
http://www.android-push.com/

据了解,钱方支付的 Android 客户端是使用 http://www.android-push.com/ 的服务。

Sqaure 类产品在Android 机型上的适配问题

我们已经公布了 盒子支付客户端支持机型列表 ,而Sqaure 也很早在他们帮助中心提示用户他们对Android 设备的兼容情况。很多用户很关心他们的机型为什么不支持。

众所周知,Android碎片化的问题确实困扰的不少的应用开发商,Animoca 公司最近也大倒苦水,他们购买了400多款手机对应用进行质量测试。

有人问,为何Square与国内的一些公司支持的 Android 机型比你们更多?因为他们跟盒子支付的音频数据方式完全不一样,他们传输数据量小,通讯不加密且只能单向通讯。实现原理比较简单:通过MIC口实现数据接收,使用 android.media.AudioRecord 实现录音,并对采集下来的模拟信号进行处理。主流的音频通讯速率 1.1k/2.2K,传输的数据有限,对于数据量大,也就需要做更多的校验,保证数据传输正确。

那难点在哪呢?

一、硬件问题
1. Android 厂商硬件配置不一致,需要购买很多机型一个个去调试。
2. 音频口供电电压高低不一,电压不够带动驱动的,需要加上电池供电。
3. 音频口引脚接线也尽不相同。像 moto xt800,有些厂家针对这款机器在硬件上做了适配。
4. 录音得出的波形不满足要求,录音失真,回路干扰等。
5. 音频口硬件接触不良,插口比较宽或比较深。
6. 输出过大。

二、软件问题
厂商修改 framework 层代码。一些手机原装ROM可以跑,但刷了第三方ROM就出现了问题。传说中的小米手机统常出现线程操作问题,乱跑。

Android 屏蔽线控耳机

研究了Square Android 应用,它在刷卡界面监测耳机按钮事件,全部屏蔽掉线控事件。

MediaButtonDisabler.java:

package org.lytsing.square;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;

public class MediaButtonDisabler extends BroadcastReceiver {

    private static final String TAG = "MediaButtonDisabler";

    private static final BroadcastReceiver INSTANCE = new MediaButtonDisabler();

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "Intercepted media button.");

        abortBroadcast();
    }

    public static void register(Context context) {
        IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
        filter.setPriority(Integer.MAX_VALUE);
        context.registerReceiver(INSTANCE, filter);
    }

    public static void unregister(Context context) {
        context.unregisterReceiver(INSTANCE);
    }
}

简要说明,设置过滤优先级为最高,int 的最大值(2^32 – 1 = 2147483647),这样,就会在第一时间把线控事件的广播中止,使其他应用无法接收到。

在Activity 调用:

    @Override
    protected void onPause() {
        super.onPause();
        
        MediaButtonDisabler.unregister(this);
    }
    
    @Override
    protected void onResume() {
        super.onResume();
        
        MediaButtonDisabler.register(this);
    }

另外,我们发现一些Android手机在音频口插入我们的盒子读卡器后,音乐播放器乱放,反复播放又暂停又播放,开始以为是线控问题,屏蔽掉后,问题依旧。有做这方便的朋友,可以交流交流,分享一下。

Downlodas 之 ByUri.startDownloadByUri

很久以前写过一篇《How to Use Android Downloads Provider》,到了Android 2.2(froyo),Android 提供了更优雅的接口:

public static long startDownloadByUri(Context context,
                                      String url,
                                      String cookieData,
                                      boolean showDownload,
                                      int downloadDestination,
                                      boolean allowRoaming,
                                      boolean skipIntegrityCheck,
                                      String title,
                                      String notification_package,
                                      String notification_class,
                                      String notification_extras)

当然这些接口都是hide的,做ROM系统开发的才可以直接使用,用mmm方式编译。签于这个原因,一些国内第三方电子市场直接拿Downloads组件的代码修改成自己的提供使用。代码实现在:

[deli@violet frameworks]$ find . -type f -name Downloads.java
./base/core/java/android/provider/Downloads.java
./base/core/java/android/net/Downloads.java

还有 packages/providers/DownloadProvider

startDownloadByUri 即这个函数很容易理解,有网友给我发email说不知道如何使用,我贴出系统升级使用的代码片段,希望对大家有帮助。

[deli@violet update]$ cat Download.java

/*
 * Copyright (C) 2010 lytsing.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.android.gsf.update;

import com.google.android.gsf.R;

import android.content.Context;
import android.util.Log;
import android.net.Downloads;

public class Download {

    private final Context mContext;

    private String mNotificationClass;

    private String mNotificationPackage;

    public Download(Context context) {
        mContext = context;
        mNotificationPackage = context.getPackageName();
        mNotificationClass = SystemUpdateService.Receiver.class.getName();
    }

    public boolean downloadUpdate(String url, boolean showDownload, boolean allowRoaming) {
        removeAllDownloads();
        try {
            if (Downloads.ByUri.startDownloadByUri(
                        mContext,
                        url,
                        null,
                        showDownload,
                        2,
                        allowRoaming,
                        true,
                        mContext.getString(R.string.system_update_downloading_notification_title),
                        mNotificationPackage,
                        mNotificationClass,
                        null)== -1) {
                Log.w("update.Download", "Could not insert download entry into provider");
                return false;
            } 

            Log.i("update.Download",new StringBuilder()
                    .append("Started a new update download: ").append(url).toString());
        } catch (Exception e) {
            Log.e("update.Download", "Could not start update download", e);
            return false;
        }

        return true;
    }

    public Downloads.StatusInfo getStatus(String url) {
        return Downloads.ByUri.getStatus(mContext, url, 24 * 3600 * 1000);
    }

    public void removeAllDownloads() {
        Log.v("update.Download", "deleting all update downloads");
        try {
            Downloads.ByUri.removeAllDownloadsByPackage(mContext, mNotificationPackage,
                    mNotificationClass);
        } catch (Exception ex) {
            Log.e("update.Download", "Couldn\'t delete downloads", ex);
        }
    }
}
Pages: Prev 1 2 3 4 5 6 7 8 9 10 11 12 Next