之前系统出了一次问题,主要是一个服务开启了太多句柄,导致最后socket超出最大值,这次增加进程句柄数量监控:

def check_pid_handle_num(maxnum = 500):
    try:
        #获取当前所有进程打开句柄数
        cmd = "/usr/sbin/lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr"
        p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
        #轮询检查当前所有进程
        ret = {}
        for i in p.stdout.readlines():
            d = i.strip(' ').strip('\n').split(' ')
            if d[0] > maxnum:
                cmdd = "ps -C -p "+d[1]+"|awk '{print $6}'"
                pp = subprocess.Popen(cmdd, shell=True, stdout=subprocess.PIPE)
                #放入当前进程和句柄数量
                ret[d[1]] = {'serv':pp.stdout.readlines()[1], 'num':d[0]}
                pp.terminate()
        return ret
    except:
        logging.error('error in check_pid_handle_num:%s', traceback.format_exc())
        return []

      如果超出了最大数,方法返回的是个字典{‘进程id’:{‘serv’:’服务名’, ‘num’:’打开句柄数’}}。查看系统当前最大句柄数:ulimit -n

      昨晚没折腾玩,今早来弄了一下,发现sdcard创建路径失败,还没检查原因。先说下模拟器创建sdcard镜像文件的方法。进入android-sdk的安装目录,我是放在 e:\android\projectfile\android-sdk中,进入该目录:mksdcard 1024M sdcard.img 创建完成。
      在打开eclipse创建avd时选择sd card为 file,指向刚刚创建的sdcard.img文件。向sdcard中上传文件可以使用:adb push e:\test.jpg sdcard/test.jpg。done,上班

      因为上一篇博客代码有点太多,就单开了,这两天主要在看android语音录制和压缩转码相关知识,前端时间看见腾讯官方微博宣布,已经开放出即使聊天软件正在等待审批,但这个直接影响电话运营商,能通过的可能性应该不大,但我对这方面的技术很有兴趣,所以就试试看。其中涉及到很多声音方面的处理问题,在android中有两个类可以录制语音:AudioRecord和MediaRecorder,MediaRecorder主要是录制音频并写入文件,而AudioRecord主要是录制音频流,录制的音频流为pcm格式,关于pcm格式可以自行搜索一下,在传输过程中可以转换为amr格式,但没有相关可以类库有点麻烦,另外iphone不支持播放amr格式音频,如果需要跨两个平台可以使用AAC,压缩比也不错,音质也很好,我还没有测试过,网上评议而已。编码方面大家都推荐speex,我看了一下,需要是用System.loadLibrary加载进speex提供的类库。下面记录一下写的一个边录制边播放的一段代码吧:

package voice.hcent.com;
import java.io.IOException;
import android.app.Activity;
import android.os.Bundle;
import android.os.Looper;
import android.os.Process;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
public class VoiceHcentActivity extends Activity {
    static {
        System.loadLibrary("media_jni");
    }
    public int frequency = 8000;
    private int rBufferSize, pBufferSize;
    private Button startSpeech;
    private AudioRecord recorder;
    private VoiceSpeech vspeech;
    private AudioTrack player;
    private boolean stopSpeech = false;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        init();
        test();
    }
    public void init(){
        try{
            startSpeech = (Button)findViewById(R.id.StartSpeech);
            //设置播放器缓冲区大小
            pBufferSize = AudioTrack.getMinBufferSize(frequency, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);
            //获取播放器对象
            player = new AudioTrack(AudioManager.STREAM_MUSIC, frequency,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
                    pBufferSize, AudioTrack.MODE_STREAM);
            //设置录音缓冲区大小
            rBufferSize = AudioRecord.getMinBufferSize(frequency,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
            //获取录音机对象
            recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    frequency, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, rBufferSize);
        }catch (Exception e) {
            String msg = "ERROR init: "+e.getStackTrace();
            VoiceHcentActivity.this.toastMsg(msg);
        }
    }
    /**
     * 开始录音
     */
    public void startRecord(){
        stopSpeech = false;
        vspeech = new VoiceSpeech();
        vspeech.start();
    }
    /**
     * 结束录音
     */
    public void stopRecord() {
        stopSpeech = true;
    }
    /**
     * 开始播放录音
     */
    public void startPlay(){
        //设置播放器音量
        player.setStereoVolume(0.7f, 0.7f);
        player.play();
    }
    /**
     * 结束播放录音
     */
    public void stopPlay(){
        player.stop();
    }
    public void test(){
        startSpeech.setOnTouchListener(new View.OnTouchListener() {
            public boolean onTouch(View arg0, MotionEvent arg1) {
                switch (arg1.getAction()) {
                    case MotionEvent.ACTION_DOWN: //开始说话
                        startPlay();
                        startRecord();
                        toastMsg("starting record!");
                        break;
                    case MotionEvent.ACTION_UP: //停止说话
                        Log.i("hcent", "111");
                        stopPlay();
                        Log.i("hcent", "222");
                        stopRecord();
                        toastMsg("stoped record!");
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
    }
    public class VoiceSpeech extends Thread{
        @Override
        public void run() {
            super.run();
            try {
                byte[] tempBuffer, readBuffer = new byte[rBufferSize];
                int bufResult = 0;
                recorder.startRecording();
                while(!stopSpeech){
                    bufResult = recorder.read(readBuffer, 0, rBufferSize);
                    if(bufResult>0 && bufResult%2==0){
                        tempBuffer = new byte[bufResult];
                        System.arraycopy(readBuffer, 0, tempBuffer, 0, rBufferSize);
                        player.write(tempBuffer, 0, tempBuffer.length);
                    }
                    Log.d("hcent", "get read:"+bufResult+"___"+readBuffer.length);
                }
                recorder.stop();
                Looper.prepare();
                VoiceHcentActivity.this.toastMsg("AudioSpeech have ended!");
                Looper.loop();
            } catch (Exception e) {
                String msg = "ERROR AudioRecord: "+e.getStackTrace();
                Looper.prepare();
                VoiceHcentActivity.this.toastMsg(msg);
                Looper.loop();
            }
        }
    }
    @Override
    protected void onDestroy(){
        player.release();
        recorder.release();
        super.onDestroy();
        Process.killProcess(Process.myPid());
    }
    public void toastMsg(String msg){
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
        Log.e("hcent", msg);
    }
}
无标签信息 0 条

      看了一下时间,快20多天没有写日记了。上周末两天,这周末两天,都潜心学习android,今天没怎么花心思。上周末主要在结束进程和锁屏上花了时间,还出了一个应用:www.mjix.com。主要涉及了几个知识点:1、锁屏;2、当前进程;3、当前内存;4、widget应用。
      这个周末他们去香港玩,看天阴沉沉的就临时退出了,结果证明也是明智之举,昨天花了一天时间研究android语音,主要分audioRecord和MediaRecorder两个模块。下面我对上面涉及到的知识分别用代码说明,就不语言解释了:
1、锁屏:锁屏需要获得用户的激活权限,所以有一步需要坚持当前用户是否已同意锁屏激活。

package com.mjix;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
public class ScreenLock{
    private Context _ctx;
    private ComponentName _cpn;
    private DevicePolicyManager _dpm;
    public ScreenLock(Context ctx){
        this._ctx = ctx;
        _cpn = new ComponentName(this._ctx, MyAdminRec.class);
        _dpm = (DevicePolicyManager)this._ctx.getSystemService(Context.DEVICE_POLICY_SERVICE);
    }
    /**
     * 锁屏
     * @return
     */
    public boolean lock(){
        boolean flag = _dpm.isAdminActive(_cpn);
        if(flag){ //已激活
            _dpm.lockNow();
        }else{ //开始激活
            return this._active();
        }
        return false;
    }
    public boolean checkActive(){
        boolean flag = _dpm.isAdminActive(_cpn);
        if(!flag){ //开始激活
            this._active();
            return false;
        }
        return true;
    }
    /**
     * 激活管理权限
     * @return
     */
    private boolean _active() {
        Intent inte = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
        inte.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, _cpn);
        inte.putExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION, "MJIX 优化应用需要您授权才能使用!");
        this._ctx.startActivity(inte);
        return true;
    }
}

2、获取当前正在运行的进程,然后结束应用进程,释放内存。

package com.mjix;
import java.util.ArrayList;
import java.util.List;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.Context;
import android.util.Log;
public class KillProcess {
    private Context _ctx;
    private ActivityManager _acm;
    private ArrayList _appList;
    public KillProcess(Context ctx){
        this._ctx = ctx;
    }
    /**
     * 获取所有非系统进程
     * @return
     */
    public ArrayList getAllApps(){
        this._acm = (ActivityManager)this._ctx.getSystemService(Context.ACTIVITY_SERVICE);
        List runs = this._acm.getRunningAppProcesses();
        ArrayList rs = new ArrayList();
        for(RunningAppProcessInfo rapp : runs){
            if(rapp.processName.equals("system") || rapp.processName.equals("com.android.phone")){
                continue;
            }
            rs.add(rapp);
        }
        Log.d("mjix", "len:"+runs.size()+"__"+rs.size());
        return rs;
    }
    /**
     * 结束所有非系统进程
     * @return
     */
    public int kill(){
        this._appList = this.getAllApps();
        for(RunningAppProcessInfo app : this._appList){
            this._acm.killBackgroundProcesses(app.processName);
        }
        return this._appList.size();
    }
}

3、获取当前手机可用内存信息。

    /**
     * 获取系统内存
     * @param ctx
     * @return
     */
    public static String getMemory(Context ctx){
        MemoryInfo mi = new MemoryInfo();
        ActivityManager am = (ActivityManager)ctx.getSystemService(Context.ACTIVITY_SERVICE);
        am.getMemoryInfo(mi);
        return Integer.toString((int)mi.availMem/1024/1024) ;
    }

4、widget应用,创建小工具,处理相关事务。

package com.mjix;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.RemoteViews;
public class MjixWidget extends AppWidgetProvider {
    private static RemoteViews views;
    private static MemoryInfo mi;
    private static ActivityManager am;
    private static AppWidgetManager awm;
    private static Intent site;
    private static int[] ids;
    private static KillProcess kp;
    private static ScreenLock sl;
    /**
     * 初始化widget相关设置
     */
    public void onEnabled(Context ctx){
        super.onEnabled(ctx);
    }
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        try{
            ctx.stopService(site);
        }catch (Exception e) {
            Log.d("mjix", "ERROR STOP SERVICE:"+e.getMessage());
        }
        Log.d("mjix", "stop server");
    }
    /**
     * 更新应用
     */
    public void onUpdate(Context ctx, AppWidgetManager awmanger, int[] wgIds){
        try{
            this.initWidget(ctx ,awmanger, wgIds);
            super.onUpdate(ctx, awm, wgIds);
        }catch (Exception e) {
            updateViews(ctx, "ERROR:"+e.getMessage());
            Log.d("mjix", "ERROR ONUPDATE:"+e.getMessage());
        }
    }
    /**
     * 初始化
     * @param ctx
     */
    private void initWidget(Context ctx, AppWidgetManager awmanger, int[] wgIds){
        if(mi != null) return ;
        ids = wgIds;
        awm = awmanger;
        kp = new KillProcess(ctx);
        sl = new ScreenLock(ctx);
        mi = new MemoryInfo();
        am = (ActivityManager)ctx.getSystemService(Context.ACTIVITY_SERVICE);
        site = new Intent(ctx, TimeService.class);
        ctx.startService(site);
        Log.d("mjix", "start service!");
        views = new RemoteViews(ctx.getPackageName(), R.layout.mjix_widget);
        Intent ite = new Intent("com.mjix.action.TOUCH");
        Log.d("mjix", "start set broadcast!");
        PendingIntent pite = PendingIntent.getBroadcast(ctx, 0, ite, 0);
        views.setOnClickPendingIntent(R.id.MjixWidget, pite);
        Log.d("mjix", "init overe!");
    }
    /**
     * 响应点击消息
     */
    public void onReceive(Context ctx, Intent ite){
        super.onReceive(ctx, ite);
        Log.d("mjix", ite.getAction());
        String act = ite.getAction();
        if(act.equals("com.mjix.action.TOUCH")){
            try{
                kp.kill();
                updateViews(ctx); //更新应用
            }catch(Exception ex){
                updateViews(ctx, ex.getMessage());
            }
            try{
                sl.lock();
            }catch (Exception ex) {
                updateViews(ctx, "未激活");
            }
        }
    }
    /**
     * 更新
     * @param ctx
     */
    public static void updateViews(Context ctx){
        try{
            String smem = getMemory(ctx);
            views.setTextViewText(R.id.Memory, smem);
            awm.updateAppWidget(ids, views);
        }catch(Exception e){
            updateViews(ctx, "异常");
            Log.d("mjix", "ERROR UPDATE WIDGET:" + e.getMessage());
        }
    }
    /**
     * 更新
     * @param ctx
     */
    public static void updateViews(Context ctx, String str){
        views.setTextViewText(R.id.Memory, str);
        awm.updateAppWidget(ids, views);
    }
}

一.负载均衡
      简单来说,就是按照目标server的参数进行合理分配,这个参数可以是失败率,也可以是响应时间,也可以是请求量,甚至是随机数。
      我们来按照从简单到复杂逐个看一下几种实现。
1.轮询式
逻辑比较简单,直接看代码:

vector vecServer;
while(1)
{
    Server* server = vecServer[curIndex % vecServer.size()];
    curIndex ++;
}

      如上代码就是一个简单的轮询式分配方法,这种方法优点实现简单,cpu计算少,缺点就是无法动态判断server的状态,当后端有一台server挂掉的时候,会至少1/vecServer.size()的请求。(最为严重的情况是由于单台后端server的超时导致前段全部挂死)。而且这种分配方法有一个bug,就是当每次请求结束后就释放内存,那么curIndex永远都只会为0,即每次都请求第一个server。
2.定死权重式
      这种方式适用于那种需要实现就规定后端server的权重,比如A比Bserver的响应速度快,我们希望A接受的请求比B多。

//假设A,B, C server的权重分别为 10 5 2
typedef struct _serverinfo
{
    //server 指针
    Server* server;
    //权重
    int weight;
}ServerInfo;
 
vector vecServer;
vecServer.push_back(A);
vecServer.push_back(B);
vecServer.push_back(C);
 
vector vecWeight;
for (unsigned i = 0; i < vecServer.size(); i++)
{
    vecWeight.insert(vecWeight.end(),vecServer[i]->weight,i);
}
while(1)
{
    index = vecWeight[random() % vecWeight.size()];
    Server* server = vecServer[index].server;
}

      上面的代码也比较好理解,一共有两个数组,一个是server信息数组vecServer,一个是权重数组vecWeight。在分配server时,先通过权重数组vecWeight获取到server信息数组的下标,然后分配server。
这样的做法在我1年半写的一个项目是有使用的,经过统计效果是很不错的,基本是访问量是严格按照权重比分配的。这样做的cpu消耗也不高,但是缺点也是显而易见的,就是还是没有办法动态调整权重,需要人为去修改。所以我们接下来看第三种。
3.动态调整权重
      要讨论这种方法前,我们先要明确几个希望使用他的原因:
1.我们希望server能够自动按照运行状态进行按照权重的选择
2.我们不希望手工去配置权重变化
      然后是我们实现方法,很明显,我们需要一个基准来告诉我们这台server是否是正常的。这个基准是什么,是历史累计的平均值。比如如果是按照响应时间分配权重,那么就是所有后端server历史累计的平均响应时间,如果是错误率也是如此。
      那么一旦调整了权重,我们什么时候来调整权重呢,调整比例是怎样呢?按照我的经验,一般是隔一段固定时间才进行调整,如果正常但是权重过低,那么就按照20%的比例恢复;如果server不正常,那么直接按照当前server响应时间/历史平均响应时间进行降权。这里的逻辑之所以不一样是有原因的,因为服务出现问题的时候,我们是能够知道这坏的程度有多少的,就是当前server响应时间/历史平均响应时间进行降权;但是要恢复的时候,你并不能保证server能够支撑到多大的访问量,所以只能按照20%放量来试。也避免滚雪球效应的发生。
我们来看一下代码。

typedef struct _serverinfo
{
    unsigned        _svr_ip;            //目标主机
    float           _cfg_wt;            //配置的权重
    float           _cur_wt;            //当前实际权重
    int             _req_count;         //请求数
    float           _rsp_time;    //请求总响应时间
    float           _rsp_avg_time;      //请求平均响应时间
    int             _rsp_error;         //请求错误数
}ServerInfo;
 
vector vecServer;
int total_rsp_time = 0;
int total_req_count = 0;
 
unsigned int comWeight = 100;
unsigned int MaxWeight = 1000;
while(1)
{
    //按照文中第二种方式进行server分配
    serverInfo._req_count++;
    serverInfo._rsp_time+=rsp_time;//响应时间
 
    total_req_count++;
    total_rsp_time += rsp_time;
 
    if(! 需要重建权重)
    {
        continue;
    }
 
    float total_rsp_avg_time = (float)total_rsp_time / (float)total_req_count;
    for(vector::iterator it = vecServer.begin();it!=vecServer.end();++it)
    {
        it->_rsp_avg_time = (float)it->_rsp_time / (float)it->_req_count;
        if(it->_rsp_avg_time > total_rsp_avg_time)
        {
            it->_cur_wt = int(comWeight*total_rsp_avg_time/it->_rsp_avg_time);
        }
        else
        {
            it->_cur_wt *= 1.2;
        }
        it->_cur_wt = it->_cur_wt < MaxWeight ? it->_cur_wt : MaxWeight;
    }
 
    //按照文中第二种方式重建权重数组
}

      以上基本展示了动态调整的过程,代码可能只是起演示作用,很多比如越界的检测都没有做,大家参照就好~
      OK,到这里我们基本就结束了负载均衡的讨论了,但是还有一个话题:过载保护。
二.过载保护
      关于过载保护其实经常适合负载均衡结合在一起使用的,但有两个问题:
1.过载参照的基准是谁。
      是上面代码中的total_rsp_avg_time吗?
      不是,因为除非所有机器的正常性能完全一样,
      否则不可以拿total_rsp_avg_time来作为某台机器的负载基准。
      而能拿来做参照的,只有这台server自身的历史累计值。
2.怎么实现过载保护。其实很简单,我们定义两个值_cur_max_queue_cnt和_queue_req_cnt,
      意义分别是这个server上在一段时间内允许分配的最多次数和当前已经排队的个数。
      _cur_max_queue_cnt值是通过当前时间段响应时间和历史累计时间算出来的。
      每次使用分配的server前,都要判断一下_queue_req_cnt是否达到了_cur_max_queue_cnt,
      如果达到了,分配失败。否则分配成功并且_queue_req_cnt++。
代码如下:

typedef struct _serverinfo
{
    unsigned        _svr_ip;            //目标主机
    float           _cfg_wt;            //配置的权重
    float           _cur_wt;            //当前实际权重
    int             _req_count;         //请求数
    float           _rsp_time;    //请求总响应时间
    float           _rsp_avg_time;      //请求平均响应时间
    int             _rsp_error;         //请求错误数
 
    int             _total_req_count;           //总的请求数
    float           _total_rsp_avg_time;        //总的请求平均响应时间
    int             _total_rsp_error;           //总的请求错误数
    float           _total_rsp_time;        //总的请求时间
 
    float           _cur_max_queue_cnt; //当前实际允许的最大排队请求数
    int             _queue_req_cnt;         //当前排队请求数
 
}ServerInfo;
 
float comxQueueSize = 1000;
float maxQueueSize = 10000;
while(1)
{
    //按照文中第二种方式进行server分配
 
    if(serverInfo._queue_req_cnt > serverInfo._cur_max_queue_cnt)
    {
        //分配失败
        continue;
    }
    serverInfo._queue_req_cnt ++;
 
    serverInfo._req_count++;
    serverInfo._rsp_time+=rsp_time;//响应时间
 
    serverInfo._total_req_count++;
    serverInfo._total_rsp_time+=rsp_time;//响应时间
 
    total_req_count++;
    total_rsp_time += rsp_time;
 
    if(! 需要重建权重)
    {
        continue;
    }
 
    //按照第三种方法重新分配权重
    //按照文中第二种方式重建权重数组
 
    //其实和上面的循环合并成一个
    for(vector::iterator it = vecServer.begin();it!=vecServer.end();++it)
    {
        it->_total_rsp_avg_time = (float)it->_total_rsp_time / (float)it->_total_req_count;
        if(it->_rsp_avg_time > it->_total_rsp_avg_time)
        {
            it->_cur_max_queue_cnt = int(comxQueueSize*it->_total_rsp_avg_time/it->_rsp_avg_time);
        }
        else
        {
            it->_cur_max_queue_cnt *= 1.2;
        }
        it->_cur_max_queue_cnt = it->_cur_max_queue_cnt < maxQueueSize ? it->_cur_max_queue_cnt : maxQueueSize;
        it->_queue_req_cnt = 0;
    }
}

      上面的代码为了演示方便,所以把两个for循环拆开了,实际上是应该合到一个里面写的。

02 / 02 / 2012 buling

      今天买了一个蓝牙适配器,结果插在电脑上,右下角一点提示都没有,搜索半天无果,最终看到一篇文章,写下来吧。我电脑win7.
      第一方法:【开始】->【设备和打印机】->Intel(R) Centrino(R) Wireless Bluetooth(R) 3.0 + High Speed Adapter(类似文件名)->【右键】->【Bluetooth设置】->【在通知区域显示Bluetooth图标】。
      第二方法:【控制面板】->【设备和打印机】…