【Android面试要点系列一】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shakespeare001/article/details/51648778

1、广播的两种注册方法,有什么区别

广播接受者BroadcastReceiver通过注册自己感兴趣的广播意图,当有该广播发出时,我们自定义的BroadcastReceiver就能够接收到该广播,进行我们自己的逻辑处理。一个简单的自定义BroadcastReceiver如下:
public class BroadcastReceiverTest extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到我们注册的广播类型
        String action = intent.getAction();
        Log.d("TAG",action);
        //TODO 处理自己逻辑...
    }
}
注意:Android对广播接受者中的onReceive()方法执行时间有限制,因此我们在onReceive中不能做一些非常耗时的操作,如果超过了规定的时间,会自动跳出onReceive方法的执行。

自定义好了BroadcastReceiver之后,为了让我们自定义的BroadcastReceiver能够接收到广播,我们就需要注册我们感兴趣的广播类型。注册广播分为下面两种:
(1)静态注册:
静态注册是在AndroidManifest.xml文件中进行配置,如下:         
<receiver android:name=".BroadcastReceiverTest">
            <!-- 订阅我们这个广播接收者感兴趣的广播类型 -->
            <intent-filter >
                <action android:name="com.scu.lly.action.MyBROADCAST"/>
                <action android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
其中这个“com.scu.lly.action.MyBROADCAST”是我们自定义的一个广播类型,以后只要有地方通过 sendBroadcast(Intent)发送出该类型的广播,我们的BroadcastReceiverTest就能够接收到。当然,我们也可以订阅一些Android系统内置的广播,比如开机启动广播,如下:         
<receiver android:name=".BroadcastReceiverTest">
            <!-- 订阅我们这个广播接收者感兴趣的广播类型 -->
            <intent-filter >
                <!-- 这是我们自定义的广播类型 -->
                <action android:name="com.scu.lly.action.MyBROADCAST"/>
                <!-- 这是我们系统的开机广播 -->
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
我们订阅了两个感兴趣的广播,不论发出了哪一个广播我们都能够接收到,此时,只需要在onReceive()判断一下,当前是哪种广播类型即可,如下:
public class BroadcastReceiverTest extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到我们注册的广播类型
        String action = intent.getAction();
        Log.d("TAG",action);
        if("com.scu.lly.action.MyBROADCAST".equals(action)){//处理我们接收到的自定义的广播
            //TODO
        }else if("android.intent.action.BOOT_COMPLETED".equals(action)){//处理开机广播
            //TODO
        }
    }
}
注意: 静态注册这种方式是常驻型的,即使我们的应用程序退出了,如果系统中有注册的广播类型发出,我们自定义的BroadcastReceiverTest 也会被系统调用自动运行。比如,开机广播,当我们重启手机时,虽然此时我们的应用还未启动,但是BroadcastReceiverTest 也能够接收到该广播。

(2)动态注册
动态注册是直接在代码中,注册我们感兴趣的广播类型,一般在Activity、Service、或传入了Context的自定义View中注册,如下:         
BroadcastReceiverTest receiver = new BroadcastReceiverTest();
        IntentFilter filter = new IntentFilter();
        filter.addAction("com.scu.lly.action.MyBROADCAST");
        registerReceiver(receiver, filter);
注意动态注册的广播接收者不是常驻型的,它会跟随Activity或Service的生命周期,因此,在Activity或Service的onDestroy()方法中需要解除注册。否则,系统会提示异常信息。如下:     
@Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);
    }
两种注册方式的区别:
(1)注册方式不一样,静态注册是在AndroidMnifest.xml中配置,动态注册是在代码中设置;
(2)生命周期不同,静态注册是常驻型的,即使应用程序退出,也能够收到发出的广播,而动态注册会跟随所依附的Activity或Service的生命周期,需要在onDestroy()中解除注册。因此, 静态注册适合注册一些系统广播,而代码注册适合注册一些开发者自定义的一些广播
(3)功能受限方面,有些 系统广播只能够通过 静态注册的方式注册该广播类型,通过动态注册不能够接收到广播信息,比如,监听电池电量的广播、网络状态变化的广播...。

发送广播:
在通过上面两种方式任意一种注册完广播之后,我们的BroadcastReceiverTest 就能够接收到广播了,此时,在需要的地方,我们就可以通过以下形式发出广播:        
 Intent intent = new Intent("com.scu.lly.action.MyBROADCAST");//发送指定类型的广播
        intent.putExtra("name", "lly");//在发送广播的同时,还可以携带一些参数过去
        sendBroadcast(intent);
广播的种类:
一个广播,可以被多个注册了该广播的广播接收者接收,通过sendBoroadcast()方式发送出的广播,多个广播接收者之间都可以接收到该广播,并且不会相互影响。如果要定义这些广播接收者之间的接收顺序,可以使用有序广播。

(1)普通广播
普通广播对于所有订阅该广播的接收者来说是异步的,每个广播接收者几乎同时接收到广播,相互之间也不会影响,因此,某个广播接收者也就无法终止广播的传递。
通过sendBroadcast()方式发出的广播为普通广播。

(2)有序广播
有序广播,通过 sendOrderedBroadcast()方式发送 ,会根据广播接收者的优先级来传递广播,广播首先传递到优先级最高的广播接收者中,处理完之后,会传递到优先级 低一级的广播接收者中。
在传递过程中,
<1>优先级较高的可以通过 abortBroadcast() 终止广播的传递,广播也就不会继续往下传播了;
<2>传递过程中可以通过 setResultExtras(Bundle)的方式往下一级传递处理结果信息。

我们先定义两个广播接收者,如下:
public class BroadcastReceiverTest extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到我们注册的广播类型
        String action = intent.getAction();
        Log.d("TAG",action);
        if("com.scu.lly.action.MyBROADCAST".equals(action)){//处理我们接收到的自定义的广播
            //往下一级传递时,将这里的处理结果传过去
            Bundle bundle = new Bundle();
            bundle.putString("result", "resultinfo");
            setResultExtras(bundle);
//            abortBroadcast();
        }
    }
}

public class BroadcastReceiverTest2 extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        //拿到我们注册的广播类型
        String action = intent.getAction();
        Log.d("TAG",action);
        if("com.scu.lly.action.MyBROADCAST".equals(action)){//处理我们接收到的自定义的广播
            //拿到上一级接收者的处理结果
            Bundle bundle = getResultExtras(true);
            String res = bundle.getString("result");
            Log.d("TAG", res);
        }
    }
}
上面我们通过setResultExtras和getResultExtras的方式,在广播接收者之间传递了参数,根据具体需求可要可不要。
在广播注册的地方,我们再定义广播接收者之间的优先级:         
<receiver android:name=".BroadcastReceiverTest">
            <intent-filter android:priority="100">
                <!-- 这是我们自定义的广播类型 -->
                <action android:name="com.scu.lly.action.MyBROADCAST"/>
                <action android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>

        <receiver android:name=".BroadcastReceiverTest2">
            <intent-filter android:priority="90">
                <!-- 这是我们自定义的广播类型 -->
                <action android:name="com.scu.lly.action.MyBROADCAST"/>
                <action android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>
注册好了之后,我们通过sendOrderedBroadcast方式发送有序广播,如下:        
 Intent intent = new Intent("com.scu.lly.action.MyBROADCAST");
        intent.putExtra("name", "lly");//在发送广播的同时,还可以携带一些参数过去
        sendOrderedBroadcast(intent, "com.scu.lly.permission.MyBROADCAST_PERMISSION");
通过sendOrderedBroadcast方式发送有序广播,第二个参数为permission权限参数,
<1>可以为null,表示注册该广播时不需要在AndroidManifest.xml中声明指定权限;
<2>不为null,那么,我们在注册该广播时,需要在AndroidManifest.xml中声明该指定的权限;这种方式更加安全,特别是针对一些隐私广播,比如我们在注册监听电话来电,读取短信的广播时,都需要在AndroidManifest.xml中声明相应的权限。

上面我们的“com.scu.lly.permission.MyBROADCAST_PERMISSION”就为自定义的一条权限,因此我们需要在AndroidManifest.xml中先自定义,再声明使用,如下:
    <!-- 自定义一条权限 -->
    <permission android:name="com.scu.lly.permission.MyBROADCAST_PERMISSION" android:protectionLevel="normal"/>
    <!-- 声明自定义的权限 -->
    <uses-permission android:name="com.scu.lly.permission.MyBROADCAST_PERMISSION"/>

如果我们想在BroadcastReceiverTest 终止广播的发送,只需要把BroadcastReceiverTest 中的注释去掉,使用 abortBroadcast();来终止即可,这时,BroadcastReceiverTest2 就不能再收到该广播了。

2、Android数据缓存的五种方法

Android保存数据有如下五种方法:
(1)使用SharedPreferences
(2)使用文件存储
(3)SQLite数据库
(4)ContentProvider(严格来说,不能说成是数据保存的一种方法,可以说是数据共享的一种方法)
(5)网络存储

(1)使用SharedPreferences保存数据
SharedPreferences可以保存一些基本类型的数据,如int、boolean、float,long,以及String,可用于保存一些配置信息。SharedPreferences使用如下:         
SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);//第一步
        SharedPreferences.Editor editor = settings.edit();//第二步
        editor.putString("name", "lly");//第三步
        editor.commit();//第四步
SharedPreference的使用主要就是上面四步:
第一步,通过Context拿到 SharedPreferences对象,其中getSharedPreferences(String fileName,int mode),第一个参数是SharedPreferences文件保存在手机中的文件名称,SharedPreferences文件保存在手机中是以.xml格式保存的,系统首先会去判断是否有这个文件,没有就回去创建;第二个参数是这个文件的访问权限,有下面几种权限:

                 Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。

                 Context.MODE_WORLD_READABLE:  指定该SharedPreferences数据能被其他应用程序读,但不能写。

                 Context.MODE_WORLD_WRITEABLE:  指定该SharedPreferences数据能被其他应用程序读,

第二步,通过 SharedPreferences 获取Editor
第三步,拿到Editor之后,就可以保存数据了,主要方法有putInt(),putString()...
第四步,在通过Editor写入数据之后,一定要记得调用commit(),这样提交了数据的更改才会生效。

其实,在我们的程序中可能会经常用到 SharedPreferences来保存数据,如果在每个需要保存数据的类里面使用上面四个步骤,就会感觉有一点点冗余,这个时候,我们就可以使用一个统一的SharedPreferences管理类来进行SharedPreferences数据的保存,推荐一个SharedPreferences管理类如下:
/**
 * SharedPreferences参数管理类
 */
public class PrefsUtils {

    // 本APP保存SharedPreferences参数的文件名
    private static final String APP_Prefs_Name = "roadheadline";

    /**
     * 保存字符串
     * 
     * @param context
     * @param key
     * @param value
     * @return 保存成功返回true
     */
    public static boolean putString(Context context, String key, String value) {
        return context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE).edit().putString(key, value).commit();
    }

    /**
     * 获取字符串
     * 
     * @param context
     * @param key
     * @return 若不存在该key值对应的value,返回null
     */
    public static String getString(Context context, String key) {
        return getString(context, key, null);
    }

    /**
     * 获取字符串
     * 
     * @param context
     * @param key
     * @param defValue
     * @return 若不存在该key值对应的value,返回用户传入的defValue
     */
    public static String getString(Context context, String key, String defValue) {
        return context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE).getString(key, defValue);
    }


    /**
     * 保存int数据
     * 
     * @param context
     * @param key 
     * @param value 
     * @return 保存成功返回true
     */
    public static boolean putInt(Context context, String key, int value) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putInt(key, value);
        return editor.commit();
    }

    /**
     * 获取int数据
     * @param context
     * @param key
     * @return 不存在返回-1
     */
    public static int getInt(Context context, String key) {
        return getInt(context, key, -1);
    }

    /**
     * 获取int数据
     * 
     * @param context
     * @param key 
     * @param defaultValue 
     * @return 
     */
    public static int getInt(Context context, String key, int defaultValue) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        return settings.getInt(key, defaultValue);
    }

    /**
     * 保存long类型数据
     * 
     * @param context
     * @param key 
     * @param value 
     * @return
     */
    public static boolean putLong(Context context, String key, long value) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putLong(key, value);
        return editor.commit();
    }

    /**
     * 获取long类型数据
     * 
     * @param context
     * @param key The name of the preference to retrieve
     * @return 不存在返回-1
     */
    public static long getLong(Context context, String key) {
        return getLong(context, key, -1);
    }

    /**
     * 获取long类型数据
     * 
     * @param context
     * @param key 
     * @param defaultValue 
     * @return 
     */
    public static long getLong(Context context, String key, long defaultValue) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        return settings.getLong(key, defaultValue);
    }

    /**
     * 保存float类型数据
     * 
     * @param context
     * @param key 
     * @param value 
     * @return 
     */
    public static boolean putFloat(Context context, String key, float value) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putFloat(key, value);
        return editor.commit();
    }

    /**
     * 获取float类型数据
     * 
     * @param context
     * @param key 
     * @return 不存在返回-1
     */
    public static float getFloat(Context context, String key) {
        return getFloat(context, key, -1);
    }

    /**
     * 获取float类型数据
     * 
     * @param context
     * @param key 
     * @param defaultValue 
     * @return 
     */
    public static float getFloat(Context context, String key, float defaultValue) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        return settings.getFloat(key, defaultValue);
    }

    /**
     * 保存boolean类型数据
     * 
     * @param context
     * @param key 
     * @param value 
     * @return 
     */
    public static boolean putBoolean(Context context, String key, boolean value) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putBoolean(key, value);
        return editor.commit();
    }

    /**
     * 获取boolean类型数据
     * 
     * @param context
     * @param key 
     * @return 不存在返回false
     */
    public static boolean getBoolean(Context context, String key) {
        return getBoolean(context, key, false);
    }

    /**
     * 获取boolean类型数据
     * 
     * @param context
     * @param key 
     * @param defaultValue
     * @return 
     */
    public static boolean getBoolean(Context context, String key, boolean defaultValue) {
        SharedPreferences settings = context.getSharedPreferences(APP_Prefs_Name, Context.MODE_PRIVATE);
        return settings.getBoolean(key, defaultValue);
    }
}
有了这个管理类之后,我们在需要保存数据的类里面,直接调用 PrefsUtils. putString(Context context, String key, String value);或其他相应的方法即可,获取保存的数据也是调用里面相应方法即可。

(2)使用文件存储数据

     <1>保存在内部存储器中

        文件被保存在内部存储中时,默认情况下,文件是应用程序私有的,其他应用不能访问。当用户卸载应用程序时这些文件也跟着被删除。

      文件默认存储位置:/data/data/包名/files/文件名。

    Android系统默认提供了两个方法openFileOutput()、openFileInput()将数据保存到内部存储器中。使用方法如下:
(1)往文件中写入数据
String text = "my name is lly";
try {
FileOutputStream fos = openFileOutput("lly.txt", Context.MODE_PRIVATE);
fos.write(text.getBytes());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
其中openFileOutput(String fileName,int mode),第一个参数指定要打开的文件名称(不需要写路径,因为文件 默认存储位置:/data/data/包名/files/文件名),如果没有这个文件,则自动创建一个;
第二个参数指定文件访问权限,有下面四种权限:

Context.MODE_PRIVATE = 0

  为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容

Context.MODE_APPEND = 32768

  该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 

Context.MODE_WORLD_READABLE = 1

  表示当前文件可以被其他应用读取。

MODE_WORLD_WRITEABLE

  表示当前文件可以被其他应用写入。


(2)读取文件内容
try {
FileInputStream fis = openFileInput("lly.txt");
byte[] buf = new byte[1024];
StringBuilder sb = new StringBuilder();
int len = 0;
while((len = fis.read(buf)) != -1 ){
sb.append(new String(buf,0,len));
}
fis.close();
System.out.println(sb.toString());
} catch (Exception e) {
e.printStackTrace();
}

当然,除了使用Android系统提供的这两个方法外,我们可以指定保存到其他自定义路径,这个时候就需要自己创建FileOutputStream或FileInputStream了,此时我们自定义的路径就需要通过下面方法获取:
* getFileDir(), 通过此方法可以获取到你的APP内部存储的文件,路径为/data/data/pacgage_name/files,此方法获得的路径和openFileOutput的输出流路径一致;
* getCacheDir(),通过此方法可以获取到你的APP内部存储的文件,路径为/data/data/package_name/cache,

上面的方法获得的都是内部存储路径,内部存储在你的APP卸载的时候,会一块被删除。因此,我们可以在cache目录里面放置我们的图片缓存,而且cache与files的差别在于,如果手机的内部存储控件不够了,会自行选择cache目录进行删除,因此,不要把重要的文件放在cache文件里面,可以放置在files里面,因为这个文件只有在APP被卸载的时候才会被删除。

     <2>保存在外部存储器
     数据保存外部存储器时,首先需要在AndroidManifest.xml中加入访问权限:
     <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permissionandroid:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>  
    <!-- 往SDCard写入数据权限 -->  
    <uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

     > 判断外部存储器的可用性
    在使用外部存储器存储之前,一定要判断外部存储是否可用,判断方法如下:    
//获取外存储的状态 
    String state = Environment.getExternalStorageState(); 
    if (Environment.MEDIA_MOUNTED.equals(state)) {
        // 可读可写 
         mExternalStorageAvailable = mExternalStorageWriteable =true; 
    } elseif (
        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // 可读 
    } else { 
        // 可能有很多其他的状态,但是我们只需要知道,不能读也不能写
    }

     > 获取自定义路径

    1)外部私有存储路径
     就像我们在前面获取内部存储的方法一样,我们使用Context.getExternalCacheDir()和Context.getExternalFilesDir()(API >= 8)就可以获取到外部存储的私有文件,我们以下面的代码为例。
File file3 = new File(getExternalCacheDir().getAbsolutePath(), "getExternalCacheDir.txt");  
        try {  
            OutputStream outputStream1 = new FileOutputStream(file3);  
            outputStream1.write("getExternalCacheDir".getBytes());  
            outputStream1.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
  
        Log.d("TAG", "file3=" + file3);  
  
        File file4 = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "getExternalFilesDir.txt");  
        try {  
            OutputStream outputStream1 = new FileOutputStream(file4);  
            outputStream1.write("getExternalFilesDir".getBytes());  
            outputStream1.close();  
        } catch (Exception e) {  
            e.printStackTrace();  
        } 
运行结果如下:
  1. 02-03 08:11:38.860    9096-9096/? D/TAG﹕ file3=/storage/emulated/0/Android/data/com.socks.baidudemo/cache/getExternalCacheDir.txt  
  2. 02-03 08:11:38.860    9096-9096/? D/TAG﹕ file4=/storage/emulated/0/Android/data/com.socks.baidudemo/files/Pictures/getExternalFilesDir.txt  
    可以看到, 我们创建的私有文件的地址是/sdcard/Android/date/package_name下面,Android文件夹是隐藏文件夹,用户无法操作。

    如果我们想缓存图片等比较耗空间的文件,推荐放在getExternalCacheDir()所在的文件下面,这个文件和getCacheDir()很像,都可以放缓存文件,在APP被卸载的时候,都会被系统删除,而且缓存的内容对其他APP是相对私有的。

    在使用getExternalFilesDir(int filetype)时,我们指定了这个文件的类型,不同的类型,在路径下会放到不同的文件夹下(如上面的运行结果例子),这样,Android系统的文件扫面器在扫描文件时就可以明确知道这是什么类型文件了。

    2)外部共享存储路径
    

    如果你的APP产生的文件不需要隐藏,即对用户是可见的,那么你可以把文件放在外部的公共存储文件下面。我们可以通过下面的代码获取到公共存储目录:

    Environment.getExternalStorageDirectory()    

     Environment.getExternalStoragePublicDirectory()  
    这两个方法都是Environment的,而不是Context的。 第一个方法获取到的其实是外部存储的根目录,而第二个方法获取到得则是外部存储的公共目录。其实在访问权限上是没有区别的,不同点是getExternalStoragePublicDirectory()在运行的时候,会需要你带有一个特定的参数来指定这些public的文件类型,以便于与其他public文件进行分类,类型和上面的类似。

(关于文件路径获取,可以参考我这篇文章: http://blog.csdn.net/shakespeare001/article/details/50546809

(3)使用SQLite存储数据
SQLite是Android系统内置的一个小型数据库,如果我们的APP需要保存一些表结构类的数据,可以考虑使用SQLite数据库保存数据。
SQLite的使用方法如下:

第一步:实现一个继承 SQLiteOpenHelper的子类,里面进行数据库的创建工作。如下:  
public class DBOpenHelper extends SQLiteOpenHelper {
    private static final String DATABASENAME = "itcast.db"; //数据库名称
    private static final int DATABASEVERSION = 2;//数据库版本

    public DBOpenHelper(Context context) {
        super(context, DATABASENAME, null, DATABASEVERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE person (personid integer primary key autoincrement, name varchar(20), amount integer)");//执行有更改的sql语句
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS person");
        onCreate(db);
    }
}
第二步: 编写业务逻辑处理类
按照MVC模型,我们在处理数据时最好编写一个业务逻辑处理类,进行数据的增删改查操作,在这里面,当我们 第一次调用 getWritableDatabase()或 getReadableDatabase()时,数据库会被创建。如下例子:
public class PersonService {
    private DBOpenHelper dbOpenHelper;

    public PersonService(Context context) {
        this.dbOpenHelper = new DBOpenHelper(context);
    }

    public void payment(){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.beginTransaction();//事启事务
        try{
            db.execSQL("update person set amount=amount-10 where personid=?", new Object[]{1});
            db.execSQL("update person set amount=amount+10 where personid=?", new Object[]{2});
            db.setTransactionSuccessful();//设置事务标志为成功,当结束事务时就会提交事务
        }finally{
            db.endTransaction();
        }
    }

    public void save(Person person){
        //如果要对数据进行更改,就调用此方法得到用于操作数据库的实例,该方法以读和写方式打开数据库
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.execSQL("insert into person (name,amount) values(?,?)",
                new Object[]{person.getName(),person.getAmount()});
    }

    public void update(Person person){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.execSQL("update person set name=? where personid=?", 
                new Object[]{person.getName(),person.getId()});
    }

    public void delete(Integer id){
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        db.execSQL("delete from person where personid=?", new Object[]{id.toString()});
    }

    public Person find(Integer id){
        //如果只对数据进行读取,建议使用此方法
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from person where personid=?", new String[]{id.toString()});
        if(cursor.moveToFirst()){
            int personid = cursor.getInt(cursor.getColumnIndex("personid"));
            String name = cursor.getString(cursor.getColumnIndex("name"));
            int amount = cursor.getInt(cursor.getColumnIndex("amount"));
            Person person = new Person(personid, name);
            person.setAmount(amount);
            return person;
        }
        return null;
    }

    public List<Person> getScrollData(Integer offset, Integer maxResult){
        List<Person> persons = new ArrayList<Person>();
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from person limit ?,?",
                new String[]{offset.toString(), maxResult.toString()});
        while(cursor.moveToNext()){
            int personid = cursor.getInt(cursor.getColumnIndex("personid"));
            String name = cursor.getString(cursor.getColumnIndex("name"));
            int amount = cursor.getInt(cursor.getColumnIndex("amount"));
            Person person = new Person(personid, name);
            person.setAmount(amount);
            persons.add(person);
        }
        cursor.close();
        return persons;
    }

    public Cursor getCursorScrollData(Integer offset, Integer maxResult){
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        return db.rawQuery("select personid as _id, name, amount from person limit ?,?",
                new String[]{offset.toString(), maxResult.toString()});
    }

    public long getCount() {
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select count(*) from person", null);
        cursor.moveToFirst();
        return cursor.getLong(0);
    }
}
可以看到,和我们平常使用的SQL非常类似。

第三步:保存数据
有了数据库、业务逻辑处理类之后,就可以进行数据的我保存操作了,如下:         
PersonService personService = new PersonService(this.getContext());
        Person person = new Person();
        person.setName("xiaoxiao");
        person.setAmount(100);
        personService.save(person);

(4)使用ContentProvider共享数据
严格来说,ContentProvider并不是保存数据的一种方法,而是不同APP之间进行数据共享的一种方式。比如,我们可以通过ContentProvider来获取通讯录APP中的联系人的数据。当一个APP想让该APP中的某些数据共享时,就可以通过ContentProvider来实现,所谓共享,就是另一个APP可以通过ContentProvider对另一个APP中的数据进行增删改查操作。如下面这个实例:

第一步:当我们的APP准备共享一些数据时,可以编写一个ContentProvider的子类,在子类中提供对需要共享数据的操作的方法。
public class PersonProvider extends ContentProvider {
    private DBOpenHelper dbOpenHelper;
    private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
    private static final int PERSONS = 1;
    private static final int PERSON = 2;
    //对外公布两种对共享数据的操作方式:集合操作方式(批量方式)、单条记录操作方式
    static{
        MATCHER.addURI("cn.itcast.providers.personprovider", "person", PERSONS);//以集合方式进行操作(批量操作)
        MATCHER.addURI("cn.itcast.providers.personprovider", "person/#", PERSON);//单条记录操作
    }   

    @Override
    public boolean onCreate() {
        this.dbOpenHelper = new DBOpenHelper(this.getContext());
        return false;
    }

    @Override
    public String getType(Uri uri) {//返回当前操作的数据的mimeType
        switch (MATCHER.match(uri)) {
        case PERSONS:            
            return "vnd.android.cursor.dir/person";//对集合的操作

        case PERSON:            
            return "vnd.android.cursor.item/person";//对单条记录的操作

        default:
            throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
        }
     }
 
    //删除person表中的所有记录   /person
    //删除person表中指定id的记录 /person/10
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        int count = 0;
        switch (MATCHER.match(uri)) {
        case PERSONS:
            count = db.delete("person", selection, selectionArgs);
            return count;

        case PERSON:
            long id = ContentUris.parseId(uri);
            String where = "personid="+ id;
            if(selection!=null && !"".equals(selection)){
                where = selection + " and " + where;
            }
            count = db.delete("person", where, selectionArgs);
            return count;

        default:
            throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
        }
    }
   
    @Override
    public Uri insert(Uri uri, ContentValues values) {// /person
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        switch (MATCHER.match(uri)) {
        case PERSONS:
            long rowid = db.insert("person", "name", values); 
            Uri insertUri = ContentUris.withAppendedId(uri, rowid);//得到代表新增记录的Uri
            this.getContext().getContentResolver().notifyChange(uri, null);
            return insertUri;

        default:
            throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
        }
    }

    
    //查询person表中的所有记录   /person
    //查询person表中指定id的记录 /person/10
    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {
        SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
        switch (MATCHER.match(uri)) {
        case PERSONS:
            return db.query("person", projection, selection, selectionArgs, null, null, sortOrder);

        case PERSON:
            long id = ContentUris.parseId(uri);
            String where = "personid="+ id;
            if(selection!=null && !"".equals(selection)){
                where = selection + " and " + where;
            }
            return db.query("person", projection, where, selectionArgs, null, null, sortOrder);

        default:
            throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
        }
    }

    //更新person表中的所有记录   /person
    //更新person表中指定id的记录 /person/10
    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
        int count = 0;
        switch (MATCHER.match(uri)) {
        case PERSONS:
            count = db.update("person", values, selection, selectionArgs);
            return count;

        case PERSON:
            long id = ContentUris.parseId(uri);
            String where = "personid="+ id;
            if(selection!=null && !"".equals(selection)){
                where = selection + " and " + where;
            }
            count = db.update("person", values, where, selectionArgs);
            return count;

        default:
            throw new IllegalArgumentException("Unkwon Uri:"+ uri.toString());
        }
    }
}
可以看到,一个ContentProvider主要有三部分:
(1)onCreate()数据初始化
在这里面进行数据的初始化,比如创建出数据库...
(2)对外提供操作共享数据的路径机制
一个APP想要对另外一个APP进行共享数据的操作功能,首先要能够拿到访问操作方法的URI,类似于我们访问WEN服务器的接口地址一样。Android系统内部对共享数据的操作模式有集合形式的操作(批量操作)、单条记录的操作模式,分别用“ vnd.android.cursor. dir /自定义和“vnd.android.cursor.item/自定义”来区分。在对共享数据进行操作之前,ContentProvider首先会判断是哪种操作模式。因此,我们自定义的eContentProvider里面需要进行如下工作:
    > 首先,在我们实现的ContentProvider子类中,对外提供这个共享数据能够被操作的方式,如:
     //对外公布两种对共享数据的操作方式:集合操作方式(批量方式)、单条记录操作方式
    static{
        MATCHER.addURI("cn.itcast.providers.personprovider", "person", PERSONS);//以集合方式进行操作(批量操作)
        MATCHER.addURI("cn.itcast.providers.personprovider", "person/#", PERSON);//单条记录操作
    }   
    
    > 在使用ContentProvider进行数据操作前,默认会进行getType(Uri uri)方法,在这里面,拿到外部APP传进来的Uri进行匹配,判断出操作类别。
    
(3)调用相应的操作方法
    在执行完第二步后,根据第三方的请求调用的方法,调用ContentProvider中相应的方法。

第二步:在AndroidManifest.xml中进行注册
ContentProvider属于Android四大控件之一,需要在AndroidManifest.xml中进行注册,如下:
<provider android:name=".PersonProvider" android:authorities="cn.itcast.providers.personprovider"/>
其中, android:authorities="cn.itcast.providers.personprovider"中的值“cn.itcast.providers.personprovider”需要与ContentProvider中的MATCHER.addURI("cn.itcast.providers.personprovider", "person", PERSONS)第一个参数相同。

第三步:外部APP访问另一个APP通过ContentProvider共享的数据
当一个APP通过ContentProvider提供好了共享数据的操作方法之后,另一个APP就可以进行夸APP访问了,如下:
                ContentResolver contentResolver = getContentResolver();
                Uri insertUri = Uri.parse(" content://cn.itcast.providers.personprovider/person");
                ContentValues values = new ContentValues();
                values.put("name", "itcastliming");
                values.put("amount", 100);
                Uri uri = contentResolver. insert(insertUri, values);
                Toast.makeText(MainActivity.this, "添加完成", 1).show();

(5)使用网络存储数据
当我们的APP需要将数据保存到服务器时,就需要通过网络请求的方式,访问服务器,并保存传递过来的数据,这是一种非常常用的数据保存方式。
关于网络请求这一块,我们可以自己去封装一些简单的请求框架,也可以使用现成的网络请求框架。
现在比较好用的网络请求框架有:Volley、OkHttp、 Afinal、XUtils、 android-async-http等
具体框架的使用可以搜索相关知识。



猜你喜欢

转载自blog.csdn.net/shakespeare001/article/details/51648778