Android开发经验分享(4)那些项目中我所犯过愚蠢的错误
【1】FragmentDialog与BaseListAdapter数据传递空引用
2014-09-17 10:24
一个普通的类BaseListAdapter通过在其的onClickListener单项中用Bundle bundle = new Bundle();
bundle.putSerializable("card",card);键值对的方式传递数据
然后 用FragmentDialog.setArguments(bundle); 传递数据
payDialog.show() 显示相应的对话框
【总结】 当时出现NullPoitException,空引用错误。原因是我虽然将bundle中add进去对象的数据了,但是没有setArguments(bundle)调用这句话,导致出现空引用错误。当时以为是传进去的数据card为空,找了半天没找到问题的本质。
【2】用SD卡来存储SQLite数据库的数据
在我还没有折腾出如何才能在adb shell中实现数据库的sql语句查询的时候。我当时想到一个办法,就是把sqlite数据库放到SD卡中读取。然后用Android文件浏览器查看当前sqlite数据库的位置。拿出数据库。中途碰到很多错误。比如公司提供的测试机他压根儿没SD卡,我在那找程序错误,找了很久没找到。写代码中不规范,没有使用单利模式对数据库进行操作。之后附上【SD卡读取Sqlite数据库操作源代码】
【总结】思维定式,出错以为全是程序没写对,没有发现。手机是个便携式设备,也会因为没有硬件资源而导致调用的某些库时,没有相应的硬件资源支持。对单例模式以及数据库的不理解操作不理解导致代码结构不清晰。
【注意】
SQLite中保存在SD卡中需要加如下权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
package cn.dxy.inderal.dao; import java.io.File; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteException; import android.os.Environment; import android.util.Log; public abstract class SDSQLiteOpenHelper { private static final String TAG = SDSQLiteOpenHelper.class.getSimpleName(); private final String mName; private final CursorFactory mFactory; private final int mNewVersion; protected Context mContext; protected SQLiteDatabase mDatabase = null; private boolean mIsInitializing = false; /** * Create a helper object to create, open, and/or manage a database. The * database is not actually created or opened until one of * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called. * * @param context * to use to open or create the database * @param name * of the database file, or null for an in-memory database * @param factory * to use for creating cursor objects, or null for the default * @param version * number of the database (starting at 1); if the database is * older, {@link #onUpgrade} will be used to upgrade the database */ public SDSQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); mContext = context; mName = name; mFactory = factory; mNewVersion = version; } /** * Create and/or open a database that will be used for reading and writing. * Once opened successfully, the database is cached, so you can call this * method every time you need to write to the database. Make sure to call * {@link #close} when you no longer need it. * * <p> * Errors such as bad permissions or a full disk may cause this operation to * fail, but future attempts may succeed if the problem is fixed. * </p> * * @throws SQLiteException * if the database cannot be opened for writing * @return a read/write database object valid until {@link #close} is called */ public synchronized SQLiteDatabase getWritableDatabase() throws SQLiteException { if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { return mDatabase; // The database is already open for business } if (mIsInitializing) { throw new IllegalStateException("getWritableDatabase called recursively"); } // If we have a read-only database open, someone could be using it // (though they shouldn't), which would cause a lock to be held on // the file, and our attempts to open the database read-write would // fail waiting for the file lock. To prevent that, we acquire the // lock on the read-only database, which shuts out other users. boolean success = false; SQLiteDatabase db = null; try { mIsInitializing = true; if (mName == null) { db = SQLiteDatabase.create(null); } else { // File dbFile = getDatabasePath(mName); String path = getDatabasePath(mName).getPath(); db = SQLiteDatabase.openOrCreateDatabase(path,mFactory); // if (!dbFile.exists()) { // Log.e(TAG, "DB file is not exist !"); // return null; // } // db = SQLiteDatabase.openOrCreateDatabase(dbFile.getPath(), mFactory); } int version = db.getVersion(); if (version != mNewVersion) { db.beginTransaction(); try { if (version == 0) { onCreate(db); } else { onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } onOpen(db); success = true; return db; } finally { mIsInitializing = false; if (success) { if (mDatabase != null) { try { mDatabase.close(); } catch (Exception e) { } } mDatabase = db; } else { if (db != null && db != mDatabase) db.close(); } } } /** * Create and/or open a database. This will be the same object returned by * {@link #getWritableDatabase} unless some problem, such as a full disk, * requires the database to be opened read-only. In that case, a read-only * database object will be returned. If the problem is fixed, a future call * to {@link #getWritableDatabase} may succeed, in which case the read-only * database object will be closed and the read/write object will be returned * in the future. * * @throws SQLiteException * if the database cannot be opened * @return a database object valid until {@link #getWritableDatabase} or * {@link #close} is called. */ public synchronized SQLiteDatabase getReadableDatabase() { if (mDatabase != null && mDatabase.isOpen()) { return mDatabase; // The database is already open for business } if (mIsInitializing) { throw new IllegalStateException("getReadableDatabase called recursively"); } try { return getWritableDatabase(); } catch (SQLiteException e) { if (mName == null) throw e; // Can't open a temp database read-only! Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); } SQLiteDatabase db = null; try { mIsInitializing = true; String path = getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READWRITE); if (db.getVersion() != mNewVersion) { throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " + mNewVersion + ": " + path); } onOpen(db); Log.w(TAG, "Opened " + mName + " in read-only mode"); mDatabase = db; return mDatabase; } finally { mIsInitializing = false; if (db != null && db != mDatabase) db.close(); } } /** * Close any open database object. */ public synchronized void close() { if (mIsInitializing) throw new IllegalStateException("Closed during initialization"); if (mDatabase != null && mDatabase.isOpen()) { mDatabase.close(); mDatabase = null; } } public File getDatabasePath(String name) { String EXTERN_PATH = null; if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) == true) { EXTERN_PATH = android.os.Environment.getExternalStorageDirectory().getAbsolutePath() + "/dxy/inderal/"; File f = new File(EXTERN_PATH); if (!f.exists()) { f.mkdirs(); } } return new File(EXTERN_PATH + name); } /** * Called when the database is created for the first time. This is where the * creation of tables and the initial population of the tables should * happen. * * @param db * The database. */ public abstract void onCreate(SQLiteDatabase db); /** * Called when the database needs to be upgraded. The implementation should * use this method to drop tables, add tables, or do anything else it needs * to upgrade to the new schema version. * * <p> * The SQLite ALTER TABLE documentation can be found <a * href="http://sqlite.org/lang_altertable.html">here</a>. If you add new * columns you can use ALTER TABLE to insert them into a live table. If you * rename or remove columns you can use ALTER TABLE to rename the old table, * then create the new table and then populate the new table with the * contents of the old table. * * @param db * The database. * @param oldVersion * The old database version. * @param newVersion * The new database version. */ public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); /** * Called when the database has been opened. Override method should check * {@link SQLiteDatabase#isReadOnly} before updating the database. * * @param db * The database. */ public void onOpen(SQLiteDatabase db) { } }
【3】我们做的是基于物联网的项目,所以要用到Android和wifi硬件模块的交互。那天模块直接发不出数据,嵌入式组也在积极排查错误,但是无论是程序还是wifi本身都没有障碍。真是丈二和尚摸不清头脑。结果换了一个USB口,将wifi模块的USB接口插入到机箱后面的端口,居然好了。
【总结】电脑插入WIFI模块 由于前置USB电流太小导致读取不了wifi模块的SSID,换成后置的USB接口就OK了
【4】Android端无法打开Socket,无法传输数据
【总结】原来是我将程序的Socket端口给弄错了,我们的项目有2个子项目,智能钥匙和手机支付。我做的是手机支付。结果把Socket的端口换成了钥匙的端口
【5】今天在做Android摇一摇功能优化,我想了很久很久。首先,对Android不同的机型和不同Android型号进行测试,发现决定手机摇一摇的灵敏度是由重力感应器的三个方向上的值 x ,y ,z的灵敏度所决定的。但是不同机型不同系统的每一个x , y ,z相关的灵敏度也完全不同。Android触发摇一摇功能的原理是这样的:当x y z达到一个阀值,如三星的是19.那就可以触发。于是我写了一个程序去测各个x y z的灵敏度。思路大概是这样:开一个定时器,当你手摇动的时候,就会触发onResume()方法,然后在一段时间内记录X Y Z变化的次数。固定时间内 数据变化的次数来测灵敏度。然后筛选一些有用的数据add进数据库,通过查询语句进行查询。虽然,最终没有达到非常好的用户体验。但是较之前有了一定改善。程序还有不恰当的地方,请各位博友指正!!
package com.example.test; import java.util.Timer; import java.util.TimerTask; import com.example.test.ShakeListener.OnShakeListener; import android.app.Activity; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.Vibrator; import android.telephony.TelephonyManager; import android.util.FloatMath; import android.util.Log; import android.widget.EditText; import android.widget.Toast; /** * 安卓晃动手机监听--“摇一摇” * * @author jason0539 * */ public class Test extends Activity { private SensorManager sensorManager; private Vibrator vibrator; private ShakeListener mShakeListener = null; private static final String TAG = "TestSensorActivity"; private static final String TAG2="Time"; private static final int SENSOR_SHAKE = 10; private EditText editX = null; private EditText editY = null; private EditText editZ = null; private EditText editDeviceId = null; private EditText EditPhoneName = null; private static final int start = 1; private static int count = 0 ; private long startTime; private long lastUpdate; static final int UPDATE_INTERVAL = 100; private boolean flag = false; private boolean isSensor = true; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test_sensor); editX = (EditText) findViewById(R.id.EditX); editY = (EditText) findViewById(R.id.EditY); editZ = (EditText) findViewById(R.id.EditZ); editDeviceId =(EditText)findViewById(R.id.deviceId); EditPhoneName =(EditText)findViewById(R.id.PhoneName); sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); mShakeListener = new ShakeListener(this); mShakeListener.setOnShakeListener(new OnShakeListener() { @Override public void onShake() { mShakeListener.start(); } }); } private Timer timer; @Override protected void onResume() { super.onResume(); if (sensorManager != null) {// 注册监听器 sensorManager.registerListener(sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL); // 第一个参数是Listener,第二个参数是所得传感器类型,第三个参数值获取传感器信息的频率 startTime = System.currentTimeMillis(); Log.d(TAG2,"startTime>>>"+String.valueOf(startTime)); count = 0; if(timer != null) { if(timerTask!=null) { timerTask.cancel(); } timer = new Timer(); timer.schedule(timerTask, 1500, 10000); } } } private TimerTask timerTask = new TimerTask(){ @Override public void run() { // TODO Auto-generated method stub if(!flag){ flag = true; }else{ if(timerTask != null) { timer.cancel(); } Log.d(TAG2,"10秒内摇动的次数count="+count); } } }; @Override protected void onStop() { super.onStop(); if (sensorManager != null) {// 取消监听器 sensorManager.unregisterListener(sensorEventListener); } } /** * 重力感应监听 */ private SensorEventListener sensorEventListener = new SensorEventListener() { private int temp; @Override public void onSensorChanged(SensorEvent event) { if(timer == null){ timer = new TimerTask(); timer.schedule(task, when); } float[] values = event.values; float x = values[0]; // x轴方向的重力加速度,向右为正 float y = values[1]; // y轴方向的重力加速度,向前为正 float z = values[2]; // z轴方向的重力加速度,向上为正 int x1 = (int)x; int y1 = (int)y; int z1 = (int)z; temp = x1; if(x1 == temp){ return; }else{ count ++; temp = x1; } //当前加速度和前一次加速度差值不等于0 表示有晃动 count++; // Log.d(TAG2,"每次摇动count="+String.valueOf(count)); // if( x1 == start){ if( x1 >= 10) { lastUpdate = System.currentTimeMillis(); Log.d(TAG2,"current X is "+x1+">>>lastUpdateTime>>>"+lastUpdate); Log.d(TAG2,"The cost time is >>>>"+String.valueOf(lastUpdate-startTime)); if (sensorManager != null) { // 取消监听器 sensorManager.unregisterListener(sensorEventListener); } } editX.setText(Integer.toString(x1)); editY.setText(Integer.toString(y1)); editZ.setText(Integer.toString(z1)); editDeviceId.setText(readDevice()); EditPhoneName.setText(readphoneName()); String Device = editDeviceId.getText().toString(); String PhoneName=EditPhoneName.getText().toString(); String X = editX.getText().toString(); String Y = editY.getText().toString(); String Z = editZ.getText().toString(); Log.d(TAG, "x轴方向的重力加速度" +X+ ";y轴方向的重力加速度" + Y+ ";z轴方向的重力加速度" + Z); // 一般在这三个方向的重力加速度达到40就达到了摇晃手机的状态。 int medumx = 17;// 如果不敏感请自行调低该数值,低于10的话就不行了,因为z轴上的加速度本身就已经达到10了 int medumy =17; int medumz = 19; if(count <100) { if(readphoneName().equals("HUAWEI")) { medumx = 11; medumy = 16; medumz= 19; } else { medumx = 17; medumy = 16; medumz= 20; } }else if(count < 150) { medumx = 13; medumy = 15; medumz = 13; }else if(count <190) { medumx = 19; medumy = 19; }else if(count <220 ) { medumx = 20; medumy = 16; } /*switch(x1) { case 19: Log.d(TAG2, "medumx 19"); break; case 18: Log.d(TAG2, "medumx 18"); break; case 17: Log.d(TAG2,"medumx 17"); break; case 16: Log.d(TAG2,"medumx 16"); break; case 15: Log.d(TAG2, "medumx 15"); break; case 14: Log.d(TAG2,"medumx 14"); break; case 13: Log.d(TAG2,"medumx 13"); break; case 12: Log.d(TAG2,"medumx 12"); break; case 11: Log.d(TAG2,"medumx 11"); break; case 10: Log.d(TAG2,"medumx 10"); } switch(y1) { case 19: Log.d(TAG2, "medumy 19"); break; case 18: Log.d(TAG2, "medumy 18"); break; case 17: Log.d(TAG2,"medumy 17"); break; case 16: Log.d(TAG2,"medumy 16"); break; case 15: Log.d(TAG2, "medumy 15"); break; case 14: Log.d(TAG2,"medumy 14"); break; case 13: Log.d(TAG2,"medumy 13"); } switch(z1) { case 19: Log.d(TAG2, "medumz 19"); break; case 18: Log.d(TAG2, "medumz 18"); break; case 17: Log.d(TAG2,"medumz 17"); break; case 16: Log.d(TAG2,"medumz 16"); break; case 15: Log.d(TAG2, "medumz 15"); break; case 14: Log.d(TAG2,"medumz 14"); break; case 13: Log.d(TAG2,"medumz 13"); }*/ if(Math.abs(x)>medumx || Math.abs(y)>medumy || Math.abs(z)>medumz) { vibrator.vibrate(200); Message msg = new Message(); msg.what = SENSOR_SHAKE; handler.sendMessage(msg); //isSensor = false; Log.d(TAG2,"MedumX="+medumx); Log.d(TAG2,"MedumX="+medumy); Log.d(TAG2,"MedumX="+medumz); } /*if (Math.abs(x) > medumx || Math.abs(y) > medumy || Math.abs(z) > medumz) { vibrator.vibrate(200); Message msg = new Message(); msg.what = SENSOR_SHAKE; handler.sendMessage(msg); }*/ /*long endTime = System.currentTimeMillis(); long time = endTime-startTime;*/ // 内容值实例 /* ContentValues values1 = new ContentValues(); values1.put("phoneName", PhoneName); values1.put("X", X); values1.put("Y", Y); values1.put("Z", Z); values1.put("time", System.currentTimeMillis()); DBService.getInstance(TestForSensor.this).insert(values1);*/ } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } public String readDevice() { TelephonyManager tm = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); StringBuilder sb = new StringBuilder(); sb.append(tm.getDeviceId()); return sb.toString().substring(1,2); } public String readphoneName() { TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); StringBuilder sb = new StringBuilder(); sb.append(android.os.Build.MODEL); return sb.toString().substring(0,6); } }; public static float readNumber(float number) { return (float)(Math.round(number*10))/10; } /** * 动作执行 */ Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case SENSOR_SHAKE: Toast.makeText(Test.this, "检测到摇晃,执行操作!", Toast.LENGTH_SHORT).show(); Log.i(TAG, "检测到摇晃,执行操作!"); break; } } }; }
【总结】在实施过程中遇到这个错误:java.lang.IllegalStateException: TimerTask is scheduled already错误的解决方法 解决办法是参考了CSDN一位博友的文章
原因是这样:
这里需要注意两个问题:
if (mTimerTask != null){ mTimerTask.cancel(); //将原任务从队列中移除 }
每次放定时任务前,确保之前任务已从定时器队列中移除
mTimerTask = new MyTimerTask(); // 新建一个任务
每次放任务都要新建一个对象,否则出现一下错误:
ERROR/AndroidRuntime(11761): java.lang.IllegalStateException: TimerTask is scheduled already
所以同一个定时器任务只能被放置一次
【注意】
要实现摇一摇功能需要加如下权限
<uses-permission android:name="android.permission.VIBRATE" />
【6】【SQLite错误】
百度了一下 说明:数据库的表名字错了。其中说没有这样的数据库表名,说明是写数据库的表的名称取错了
原因:手机的SD卡有问题 所以找不到指定的SD卡的路径
今天碰到的问题:数据库的ID 设置为主键的时候,要将其设置为自动增长 并且不为空 ,且数据类型是Integer才可以
【7】今天代码导入到eclipse 出现乱码了,搞的焦头烂额。
【解决办法:】
http://blog.csdn.net/zhthl20091003/article/details/6847704
在该工程上右击propertitys->Recourses->other-> 然后选择 UTF-8 全部搞定
【8】
2014-3-7 16:00
今天碰到2个问题
1 在同一个文件里 两个监听事件发生冲突的时候的解决方法
2 如何给一个EditText设置只能输入数字和字母
(1)在XML里面直接设置
<EditText android:singleLine="true" android:numeric="integer"
/> (2)
直接生成 DigitsKeyLinstener()
et_1.setKeyListener(new DigitsKeyListener(false,true)); 设置只允许输入数字的代码
cardnumber.setInputType(EditorInfo.TYPE_CLASS_PHONE);