Android课程复习资料/提纲

96
Azur_wxj
2017.05.23 21:33* 字数 9999

2017年5月17日 Kylin_Wu

标注(★☆)为考纲明确给出考点(必考)

常见手机系统(★☆)

  1. Android(安卓) 由Google公司和开放手机联盟领导及开发。是一种基于Linux开发的操作系统,主要使用于移动设备
  2. iOS(苹果) 由苹果公司开发的移动操作系统。以Darwin为基础的,属于类Unix的商业操作系统。
  3. Symbian(塞班) 塞班公司为手机而设计的操作系统。
  4. Windows Phone(WP) 微软发布的一款手机操作系统。
  5. BlackBerry(黑莓) 由Research In Motion为其智能手机产品BlackBerry开发的专用操作系统。

Android重要版本代号(★☆)

  1. 4.1&4.2&4.3 Jelly Bean(软心豆粒糖)
  2. 4.4 KitKat(奇巧巧克力)
  3. 5.0/5.1 Lollopop(棒棒糖)
  4. 6.0 Marshmallow(棉花糖)
  5. 7.0 Android Nougat(牛轧糖)

AVD、dex文件、apk文件、gradle(★☆)

  • AVD Android Virtual Device(安卓模拟器)
  • dex文件 Android平台上可执行文件类型
  • apk文件 AndroidPackage的缩写,即Android安装包文件。
  • gradle AS内置的项目构建工具名称

Android SDK目录和文件(★☆)

SDK目录(★☆)

C:\Users\用户名\AppData\Local\Android

该目录下的文件

  • add-ons 存放Google API,比如Google Maps
  • build-tools 存放各版本SDK编译工具(★☆)
  • docs 离线开发者文档 Android SDK API
  • extras 扩展开发包,如HAXM加速
  • 【platforms】 各版本SDK,如android-25、android-24等。每一个目录里面主要结构为(★☆)
    • data 保存一些系统资源
    • skins 安卓模拟器皮肤
    • templates 工程创建的默认模板
    • android.jar 该版本的主要framework文件
    • 其他文件
  • 【platforms-tools】 各版本SDK通用工具,如adb、sqlite等(★☆)
  • samples 各版本API样例
  • skins Android模拟器的皮肤
  • sources 各版本SDK源码
  • 【tools】 重要的工具,比如ant、ddms、logcat、emulator等(★☆)
  • system-images AVD模拟器映像文件

该目录下的两个管理器(★☆)

  • AVD Mananger.exe 用于管理安卓虚拟机的管理器
  • SDK Manager.exe 用来下载sdk的管理android sdk更新的.。安卓开发人员,使用工具eclipse 或者android studio需要它。

Android项目的res子目录作用(★☆)

  • layout 放置布局资源文件
  • values 放置常量资源文件
  • menu 放置菜单资源文件
  • drawable 放置图片资源文件

资源文件访问格式(★☆)

  • 自定义资源文件访问格式 R.资源文件类型.资源文件名称
  • 系统资源文件访问格式 android.R.资源文件类型.资源文件名称

Toast、Notification特点、Log基本用法(★☆)

  • Toast特点 浮动显示信息,一定时间自动消失

      Toast.makeText(MainActivity.this,"info",Toast.LENGTH_SHORT).show();
    
  • Notification特点 在通知栏中显示通知消息

  • Log基本用法

      Log.x("Tag","Message");
    

其中"Tag"是自定义的日志标签用于过滤,"Message"是的日志信息。x可以取一下五个值(级别从大到小,级别高的可以屏蔽级别低的,但不能屏蔽级别更高的,如Log.w可以屏蔽Log.i、Log.d、Log.v,但是不能屏蔽Log.e)
* e 输出错误(error)级别的日志信息
* w 输出警告(warning)级别的日志信息
* i 输出一般提示性的消息(information )
* d 输出调试(debug)级别的日志信息
* v 输出任何消息verbose

Activity

1. Activity生成上下文菜单的方法(★☆)

上下文菜单,是指如长按列表项目后弹出的菜单

上下文菜单
@Override
public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) {
    // 设置上下文菜单的标题
    menu.setHeaderTitle("文件操作");
    // 添加菜单项
    menu.add(0, 1, Menu.NONE, "发送");
    menu.add(0, 2, Menu.NONE, "标记为重要");
    menu.add(0, 3, Menu.NONE, "重命名");
    menu.add(0, 4, Menu.NONE, "删除");
}
  • 第一个int类型的group ID参数,代表的是组概念,你可以将几个菜单项归为一组,以便更好的以组的方式管理你的菜单按钮。

  • 第二个int类型的item ID参数,代表的是项目编号。这个参数非常重要,一个item ID对应一个menu中的选项。在后面使用菜单的时候,就靠这个item ID来判断你使用的是哪个选项。

  • 第三个int类型的order ID参数,代表的是菜单项的显示顺序。默认是0,表示菜单的显示顺序就是按照add的显示顺序来显示。

  • 第四个String类型的title参数,表示选项中显示的文字。

2. 上下文菜单响应回调方法

@Override
public boolean onContextItemSelected(MenuItem item) {
    switch(item.getId()){
    case 1:
    .....

3. Activity生成选项菜单的方法(★☆)

@Override
public boolean onCreateOptionsMenu(Menu menu){
    //调用基类的方法,以便调出系统菜单(如果有的话)
    super.onCreateOptionsMenu(menu);
    menu.add(0,1,0,“重新开始”).setIcon(R.drawable.reflash);
    ....
    //返回true,使其可以显示
    return true;
}

与上下文菜单生成类似,其中setIcon方法设置菜单项目的图标

4. Activity一些方法功能(★☆)

  • setContentVIew(); 设置显示界面
  • finish(); 结束活动

5. Activity中一些回调函数特点(★☆)

运行周期
  • onCreate Activity启动时第一个被调用的方法

  • onStart Activity有不可见变为可见时调用的方法

  • onResume Activity准备好与用户交互时调用的方法

  • onPause Activity准备去启动或者恢复另一个活动时调用(部分可见)

  • onStop Activity在完全不可见时调用

  • onDestroy Activity被终止前被调用的方法

  • onRestart Activity被重新启动(停止状态到运行状态之前)时调用

  • onActivityResult 接受Activity回传数据时的回调函数

UI部分

1. 调整组件大小(★☆)

  • wrap_content 根据内部内容自动扩展以适应大小
  • match_parent 强制扩展组件大小以填充布局单元内部尽可能多的空间

2. 设置响应监听器的方法(★☆)

  • 点击事件的监听 除了Button也可以设置在ImageView等其他组件上

      btn.setOnClickListener(new View.OnClickListener() {        
          @Override  
          public void onClick(View v) {  
              // TODO Auto-generated method stub  
              ... ....
          }  
      });  
    
  • 选择状态改变事件的监听 主要是CheckedBox、ToggleButton、RadioGroup

       @Override
       public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
              // TODO Auto-generated method stub
              if (isChecked) {
                  ....
              }else {
                  ....
              }
       });
    
  • 项目点击事件监听 一般用于ListView

      ListView listView=(ListView)findViewById(R.id.listView);
      listView.setAdapter(adapter);
      listView.setOnItemClickListener(new Adapter.OnItemClickListener(){
          @Override
          public void onItemClick(AdapterView<?> parent,View view,int position,long id){
              //position代表被点击项是列表中的第几个元素
          }
      });
    

3. EditText属性(★☆)

  • android:ems="10" 宽度为十个字符宽度
  • android:maxEms="10" 宽度最多十个字符,超出则换行
  • android:inputType 输入内容的类型,比如
    • android:inputType="none" 输入普通字符(none或者text)
    • android:inputType="textPassword" 密码格式
    • android:inputType="number" 数字格式
    • android:inputType="numberSigned" 有符号数字格式
    • android:inputType="numberDecimal" 可以带小数点的浮点格式
    • android:inputType="textMultiLine" 多行输入
  • android:digits="12abc" 输入只接受“12abc”这五个字符,其他不接受
  • android:hint="提示语" 输入框显示提示语
  • android:maxLength="5" 限制输入最多5个字符

【注意!】 在java代码中,使用

    ((EditText)findViewById(R.id.text)).getText().toString();

来获得字符,仅仅是.getText()是无法获取String的。

4. Spinner(下拉控件)属性(★☆)

Spinner提供了从一个数据集合中快速选择一项值的办法。点击Spinner会弹出一个包含所有可选值的dropdown菜单,从该菜单中可以为Spinner选择一个新值。

Spinner的dropdown模式
  • spinnerMode 选择下拉列表的模式

    • dropdown 如上图所示,点击后下拉
    • dialog 点击后,列表会以对话框形式弹出。
  • entris 下拉列表的数据源设置

      //在values/arrays.xml中
      ?xml version="1.0" encoding="utf-8"?>
      <resources>
          <string-array name="languages">
              <item>c语言</item>
              <item>java </item>
          </string-array>
      </resources>
    

.

    //在布局文件中
    <Spinner
        android:entries="@array/languages"
        ....

5. 常用布局特点(★☆)

  • RelativeLayout(相对布局) 相对布局是一种非常灵活的布局方式;通过指定界面元素与其他元素的相对位置关系,来确定界面中所有元素的布局位置;优点:能够最大程度保证在各种屏幕尺寸的手机上正确显示界面布局。

  • LinearLayout(线性布局) 线性布局是常用的一种布局方式;通过设置

      android:orientation
    

来确定是水平布局(默认)

    android:orientation="horizontal"

还是垂直布局

    android:orientation="vertical"   水平布局 

垂直布局时,每行仅包含一个界面元素。水平布局时,所有界面元素都在一行

Android四大组件(★☆)

  • Activity(活动) 是用户和应用程序交互的窗口
  • BroadCast Recevicer(广播接收器) 接受一种或者多种Intent作触发事件,接受相关消息,做一些简单处理,转换成一条Notification。
  • Service(服务) 是一种程序,它可以运行很长的时间,相当于后台的一个服务。
  • Content provider(内容提供者) 可通过它来共享自己的数据给外部调用,给第三方应用提供数据访问的接口。

Intent(★☆)

作用(★☆)

Intent 是一种组件之间消息传递机制,它是一个动作的完整描述:包含了动作产生组件、接收组件和传递的数据信息。

主要用途

启动Activity、Service,在Android系统上发布Broadcast消息。

使用方法

    startActivity(intent);
    startActivityForResult(intent,requestCode);

放入数据
intent中其实维护了一个Bundle对象,所以可以直接把一个装填了数据的Bundle放进去

    //注意Extra后面有s
    void putExtras(Bundle bundle);
    
    //取出Bundle
    Bundle getExtras();

存数据时使用putExtra。Xxx表示基础数据类型如Int、Char等等,xxx表示int、char等等。值得注意的是,当填入一个对象时,该对象要是序列化的,比如实现Serializable接口。

    //注意这里Extra后面没有s
    void putExtra(String key,xxx value);
    
    //取出数据,取出序列化对象要用getSerializableExtra(String key);
    xxx getXxxExtra(String key);
    xxx[] getXxxArrayExtra(String key);

显示调用intent(★☆)

显示调用intent

隐式调用intent(★☆)

无需指明具体启动哪一个Activity,而由Android系统根据Intent的动作和数据来决定启动哪一个Activity。

例如:希望启动一个浏览器,却不知道具体应该启动哪一个Activity,此时则可以使用Intent的隐式启动,由Android系统决定启动哪一个Activity来接收这个Intent。

隐式启动的可以是Android系统内置的Activity,也可是程序本身的Activity,还可是第三方应用程序的Activity.

隐式启动

注意到上图中最重要是添加下面代码:

<category android:name="android.intent.category.DEFAULT"/>

如果不添加导致程序崩溃,这是因为在调用startActivity方法时,会自动把这个category添加到intent中,所以,如果没有添加这个语句,则会出现category无法匹配的情况,自然会导致崩溃。

  • 一个活动的清单中可以包括多个intent-filter
  • 一个intent-filter中可以有多个action、category。
  • 一个intent对象中只能有一个action,但是可以携带多个category。

常见动作(★☆)

常见动作

在java类中,使用如上方法定义intent,在构造函数中传递动作,也可以调用相应的设置方法设置动作

    Intent intent=new Intent(Intent.ACTION_VIEW,Uri.parse(".."));
    
    //或
    
    Intent intent=new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(".."));
    
    //或
    
    Intent intent=new Intent();
    intent.setAction(Intent.ACTION_VIEW);
    intent.setData(Uri.parse(".."));

比如,启动返回桌面

Intent intent=new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
startActivity(intent);

因为上述调用的是系统应用的活动,所以不必配置相应的<action>和<category>标签。启动部分活动需要权限设置,参考后面“权限”部分。

启动主活动的过滤器

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

Intent传递数据机制

Intent传递数据机制

返回数据给上一个活动

返回数据

使用startActivityForResult来启动其他活动,因为一个主活动可能打开不同的其他的活动,为了区分哪个活动传回的intent需要设置一个请求码requestCode,只要不同活动不同就可以。

其他活动接受了主活动传来的intent,进行相应的操作后要传回数据给主活动。创建一个intent,塞入数据,然后使用setResult()来传回给主活动。注意到创建的intent并没有明确指明是传回给主活动,事实上这不需要指明,setResult会为我们完成这个工作。

在主活动中重写onActicityResult,每次由主活动调用的其他活动传回intent时便会调用这个方法。第一个参数请求码用于区别是哪一个活动传回的;第二个参数结果码用于标识结果状态,一般是RESULT_OK或者RESULT_CANCEL,第三个参数就是传回的intent,它里面携带有传回的数据。

保存临时数据——Bundle对象

主活动启动次活动,次活动可以使用intent回传给主活动数据。但是次活动处于返回栈栈顶时,主活动面临着会被销毁的风险。主活动不被销毁最好,若是销毁,必须将其内的临时数据进行保存,以便在主活动重新创建时能够复原。
onSaveInstanceState方法保证了在主活动被销毁之前一定能够被调用,他可以作为保存临时数据的方法,保存数据的载体是Bundle。使用Bundle对象的putXxx方法来存储数据。

在主活动中重写下列方法:

@Override
protected void onSaveInstanceState(Bundle bundle){
    super.onSaveInstanceState(state);
    String username="Tom";
    int age=16;
    bundle.putInt("age",age);//键值对
    bundle.putString("username",username);//键值对
}

当主活动被销毁时,临时数据通过这种方法被保存了;下一次为了复原,主活动会被创建,此时会调用onCreate,我们发现这个方法的参数列表中恰好有Bundle对象。第一次被创建时,因为不可能有临时数据,所以这个Bundle对象是null,若下一次被创建,则一般不可能为null(除非没有onSaveInstanceState方法被重写),此时就可以取出数据了:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState!=null){   //若不为空
        String username=savedInstanceState.getString("username");
        int age=savedInstanceState.getInt("age");
    }
    ......

Bundle类常用方法(★☆)

  1. putBundle 将一个Bundle对象存入

     void putBundle (String key, Bundle value);
    
  2. putAll 把另一个Bundle对象中的数据全部添加进来

     void putAll(Bundle bundle);
    
  3. putXxx或putXxxArray Xxx代表基本类型,如Byte、Int、Short、String、Char等等,而XxxArray代表可以接受Byte[]、Int[]、String[]等基本类型的数组类型

     void putXxx(String key,Xxx obj);
     void putXxxArray(String key,Xxx[] objs);
    
  4. get或getXxx 通过键直接取值,或者指定范围如getInt、getBundle等再用键取值。注意前者要类型转换。

     Object get(String key);
     xxx getXxx(String key);
     xxx[] getXxxArray(String key);
    
  5. remove 移除某个属性

     void remove(String key);
    
  6. clear 移除所有属性

     void clear();
    

Android广播(★☆)

分类

  • 系统广播 如系统启动完成了、拨打电话了、收到短信了、手机没电了等系统发送的消息。
  • 自定义广播 将自定义的消息广播给应用程序

三种广播发送方法

  1. 普通广播 完全异步。接收器的执行顺序不确定。

  2. 有序广播 sendOrderedBroadcast()发送。所有的receiver依次执行。

  3. 粘性消息 sendStickyBroadcast()发送。在发送后就一直存在于系统的消息容器里,等待对应的receiver去处理,如果暂时没有receiver处理,则一直在消息容器里面处于等待状态。这个广播需要权限:

     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
    

实质(★☆)

用sendBroadcast方法讲一个Intent对象发出去。

触发BroadcastReceiver的方法(★☆)

    .... extends BroadcastReceiver{
    
        @Override
        public void onReceive(Context context, Intent intent){
            ...
        }
    }

自定义广播发送端+接收端主要框架(★☆ 简答题)

我们假设,收发器的匹配动作是action="com.example.mybroadcast"(自定义)

  1. 定义广播发送器

     Intent intent=new Intent("com.example.mybroadcast");
     Bundle bundle=new Bundle(); bundle.putString("msg","测试信息"); 
     intent.putExtras(bundle);
     
     //由该方法发出(普通异步广播)
     sendBroadcast(intent);
     
     //或发送有序广播(优先级见下图后)
     sendOrderedBroadcast(intent);
    
  2. 定义广播接收器 myBroadcastReceiver 。继承BroadcastReceiver

     public class MyBroadcastReceiver extends BroadcastReceiver {
         public MyBroadcastReceiver() {}
    
         @Override
         public void onReceive(Context context, Intent intent) {
             //代码逻辑,接收到广播后在此触发
             //不能做一些比较耗时的操作,一般限于10s内
             if(intent.getAction.equals("com.example.mybroadcast")){
                 ...
                 //一个广播接收器可以接受多个广播类型,在此可以判断
             }
         }
     }
    
  3. 注册这个接收器

    • 静态注册

      在AndroidManifest.xml文件中添加注册信息

            <receiver
                android:name=".MyBroadcastReceiver"
                //是否这个broadcast receiver能由系统实例化。默认为true。
                android:enabled="true"
                //是否broadcast receiver能够接收来自外部应用的消息。能接收则为true,否则为false。
                android:exported="true" >
                <intent-filter>
                    //广播与action和category相匹配时触发广播
                    <action android:name="com.example.mybroadcast" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </receiver>
      
    • 动态注册

      在xxx.java文件中进行注册。通过这种方式注册的广播称之为非常驻型广播,它会跟随Activity的生命周期。通常,我们会在Activity的onCreate方法中对其进行初始化,在onResume方法中对其进行注册,在onPause方法中对其取消注册。

        onCreate(){
        ....
        //在onCreate方法中初始化广播接收器
            MyBroadcastReceiver myBroadCastReceiver;
            myBroadCastReceiver = new MyBroadCastReceiver();
        
        //然后创建一个过滤器,如果广播动作能匹配这个,则该接收器被触发
            IntentFilter filter = new IntentFilter();
            filter.addAction("com.example.mybroadcast");
        
        //设置过滤器,注册广播
            registerReceiver(myBroadCastReceiver, filter);
        ...
      

      我们在其他方法中可以注销接收器

            @Override
            protected void onPause() {
                super.onPause();
                // 注销广播
                unregisterReceiver(myBroadCastReceiver);
            }
      
广播

有序广播(同步)的接收器优先级

高优先级的触发器组件可以通过

    BroadcastReceiver.abortBroadcast

方法来终止这个广播事件的传播,从而,使低优先级的触发器组件没有机会在处理该事件了。
可以通过在AndroidManifest.xml文件中的标签中增加

    android:priority

属性设置优先级,也可以在IntentFilter类实例中调用

    setPriority

方法设置优先级

常见系统广播(★☆)

常见系统广播

可能需要权限设置,权限设置见后。

Android服务(★☆)

特点(★☆)

长生命周期,,没有可视化界面,后台运行的程序。

分类

  1. 本地服务:用于应用程序内部
    • 服务通过调用startService(intent)启动,stopService(intent)结束。
    • 在服务内部可以调用stopSelf() 或 stopSelfResult()来自己停止。
    • 无论调用了多少次startService(),都只需调用一次stopService()来停止。
  2. 远程服务:用于应用程序之间
    • 过调用bindService()方法建立连接并启动服务,调用unbindService()关闭连接。
    • 多个客户端可以绑定至同一个服务。如果服务此时还没有加载, bindService()会先加载它。
    • 远程服务可被其他应用复用。
服务生命周期

上图说明

  • 如果使用startService,则第一次会执行onCreate方法,然后执行onStartCommand(android2.0以下的版本中使用onStart),销毁时,调用onDestroy方法。
  • 如果使用bindService,则第一次会执行onCreate方法,然后执行onBind方法,不会执行onStart方法,完成后调用onUnbind方法,然后onDestroy销毁。

服务如何启动和结束(★☆)

    startService(intent);
    
    stopService(intent);
    stopSelf(); //服务内部终止
    stopSelfResult; //服务内部终止
    
    /*---------------------------------------*/
    
    bindService() //建立连接开启服务
    unbindService() //关闭连接
【注意】

第一次startService()后会调用服务的onCreate()、onStart()方法,如果服务已经启动,通过startService()再次开启服务时,只会执行onStart()方法。

服务的框架

  1. 创建服务
    自定义的服务类继承Service类,并要在配置文件中说明

     public class MyService extends Service {
         public MyService() {}
     
         @Override       //由startService()触发,注意这里参数列表为空,没有Bundle
         public void onCreate(){
             super.onCreate
             ....
         }
         
         //通常将service的主要代码写在onStart()/onStartCommand()中
     /*------------------以下两个版本可以共存------------------------*/
     
         //在android2.0以下的版本中使用
         @Override       
         public void onStart(Intent intent, int startId) {   
             //由启动服务的活动传来的intent保存在参数中,可获得数据
             super.onStart(intent, startId);
             ...
         }
     
         //在android2.0以上的版本中使用       
         @Override   
         public int onStartCommand(Intent intent, int flags ,int startId) {
             //由启动服务的活动传来的intent保存在参数中,可获得数据
             super.onStartCommand(intent, int flags ,startId);
             ...
             return 返回值见下
         }
         
     /*-----------------------------------------------------------*/ 
         @Override       //由stopService()或stopSelf()触发
         public void onDestroy() {
             super.onDestroy
         }
         
         @Override       //其使用见“活动与服务的通信”
         public IBinder onBind(Intent intent) {
             throw new UnsupportedOperationException("Not yet implemented");
         }
     }
    

在清单文件中声明

    <service  android:name=".MusicService"
        //是否启用这个服务
        android:enabled="true"
        //是否允许除了当前程序之外的其它的程序访问这个服务
        android:exported="true" >
    </service>
  1. 启动服务

     .....
     Intent intent = new Intent( MainActivity.this, MyService.class );
     startService(intent);
     .....
     stopService(intent);
    

3.onStartCommand的返回值
* START_STICKY
>如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
* START_NOT_STICKY
>“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务
* START_REDELIVER_INTENT
>重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
* START_STICKY_COMPATIBILITY
>START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

活动与服务的通信——使用onBind方法(可选)

onBind机制

原理与流程如上图所示,关键一点是纽带。即,继承了Binder类的对象,会被服务中的onBind方法(该方法参数列表接受活动传来的数据携带体intent,经过处理后得到回传对象x)返回这个纽带x;x进入服务连接对象(继承了ServiceConnection并重载两个关键方法)的onServiceConnected方法的参数列表,从而被活动类所持有。从而,二者进行了关联,相关交互可以在这个纽带中进行。当然关键是在onServiceConnected方法中,对binder进行类型转换

Android数据存储(★☆)

SharedPreferences

特点(★☆)
  • 轻量级数据存储方式,能够实现不同应用程序间数据共享。
  • 基于XML文件来存储key-value键值对数据,通常用来存储一些简单的配置信息。
  • 只能存储 int、long、float、boolean和String这5种简单数据类型。
存放位置(★☆)

/data/data/包名/shared_prefs/

基本框架
  1. 首先有一个数据的映射表(xml),因为它只能存基础数据

     <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
     <map>
         <string name="password">123456</string>
         <int name="age" value="42" />
     </map>
    
  2. 可以写入数据

     //获得SharedPreferences对象(如果不存在则新建)
     SharedPreferences sp = getSharedPreferences(XML文件名, 访问模式);
     //获得可编辑对象
     SharedPreferences.Editor editor = sp.edit();
     //put方法写数据(key-value) 
     editor.putString("Name", "hello");       
     //一定要提交才能保存
     editor.commit();
    
  1. 可以读取数据

     //获得SharedPreferences对象(如果不存在则新建)
     SharedPreferences sp = getSharedPreferences(XML文件名, 访问模式); 
     //get方法读数据,在无法获取值的时候使用的缺省值
     String name = sp.getString("Name", "默认值");
    

SQLite

Android 在运行时集成了 SQLite数据库,所以每个 Android 应用程序都可以使用 SQLite 数据库。

基本特点(★☆)

开源的、轻量级的、嵌入式的、关系型数据库。

存放位置(★☆)

/data/data/包名/databases/

框架
  1. SQLiteOpenHelper类 Android提供的用来管理数据库的创建和版本更新。继承此类,实现它的一些方法来对数据库进行管理。

     public class DBHelper extends SQLiteOpenHelper {
         @Override    
         public void onCreate(SQLiteDatabase db) { 
             //数据库第一次被创建时将调用onCreate 
             //通常将创建表的操作放在这里
         }
         
         @Override    
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             //当数据库版本发生变化时会调用onUpgrade
             //这里可写更新数据表的操作(谨慎使用),也可空着不写     
         }   
    
  2. 实例

     public class DBHelper extends SQLiteOpenHelper { 
     
         //context:上下文对象 name:数据库名字 factory:游标工厂 version:数据库版本号
         //版本号为整数值,以后设置需递增,不要设置为0,0表示每次都创建数据库
         DBHelper (Context context, String name, CursorFactory factory, int version) { 
             super(context, name, factory, version);   //必须的
         }
         
         @Override    
         public void onCreate(SQLiteDatabase db) { 
             //创建一个数据库
             db.execSQL(
                         "CREATE TABLE IF NOT EXISTS person ("
                         +"id INTEGER PRIMARY KEY AUTOINCREMENT,"
                         +"name VARCHAR(20),"
                         +"age SMALLINT)");
         }
         
         @Override       
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             db.execSQL("DROP TABLE IF EXISTS person");   //删除数据表,谨慎使用
             onCreate(db);  //重新建表
         }
     }
    
  3. 使用方法

     /*--------------BEGIN---------------*/
     int DB_VERSION=1;
     
     //使用辅助类,游标工厂一般设置为null
     //一旦版本号改变,则会触发onUpgrade方法!!!!
     DBHelper helper = new DBHelper(getApplicationContext(), "test.db", null,DB_VERSION);
     
     //调用getWritableDatabase()或getReadableDatabase()才会真正创建或打开
     SQLiteDatabase db=helper.getWritableDatabase();
     
     .....//操作,见下方execSQL和rawQuery
     
     db.close(); //操作完成后关闭数据库连接
     /*--------------END-----------------*/
     
     //或直接打开,不要辅助类~~~~~~~~~~~~~~~~~
     
     /*-------------BEGIN------------------*/
     
     //数据库名、访问权限(只能是被创建它的包访问)、游标工厂(一般为null)
     SQLiteDatabase db = openOrCreateDatabase("test.db",Context.MODE_PRIVATE, null);
     
     ....
     
     db.close();
     /*-------------END--------------------*/
    
SQLiteOpenHelper类的getReadableDatabase()、getWritableDatabase()运行情况(★☆)
  1. 如果数据库不存在, 就会调用onCreat(), 不会调用onUpgrade();
  2. 如果数据库存在, 但是版本不一样, 就调用onUpgrade(), 不会调用onCreate()
  3. 如果数据库存在, 版本一样, 不会调用onCreate()和onUpgrade(),此时如果数据库没有打开, 就调用onOpen方法打开,如果打开了就不调onOpen。
SQLiteDatabase类的execSQL()和rawQuery方法区别(★☆)
  1. db.execSQL(...)

     public void execSQL(String sql, Object[] args);
    

    执行insert、delete、update和create table等有更改行为的SQL语句。

  2. db.rawQuery(…)

     //返回Cursor(类似于JDBC中的ResultSet)
     public Cursor rawQuery(String sql, String[] args)
    

    执行select语句

举个例子

    //因为id是设置自增的,所以不用设置id,只要NULL就可以
    //另外,因为要用后面数组填充,所以要用占位符?
    db.execSQL("INSERT INTO person VALUES (NULL, ?, ?)",new Object[ ] { "Ok", Integer(15) } ); 

下面是查询例子

    //获得结果游标(注意占位符?使用)
    Cursor cursor = db.rawQuery( "SELECT * FROM person where age>?",  new String[]{"10"} );
    
    while (cursor.moveToNext()) {    //遍历结果集
    
            //cursor.getColumnIndex("id") 获得id所在列的标号
            //cursor.getInt 通过标号获得值并转为int
        int id = cursor.getInt(cursor.getColumnIndex("id") );
        
        String name = cursor.getString(cursor.getColumnIndex("name") );
    }
    
    //关闭cursor!!!!!!!!!!!!!!!
    cursor.close();   
    
    db.close();
改进

注意到,db.execSQL(...)方法返回值是void,也就是不知道更新操作是否成功,SQLiteDatebase本身提供了insert()、delete()、update()、 query()四个方法对数据进行操作。

  1. 插入数据(insert:返回值为插入行的row ID)

     //ContentValues以键值对的形式存放基本类型数据
     ContentValues cv = new ContentValues();  
     cv.put("name", "Tom");
     cv.put("age", 26);
     
     DBHelper helper = new DBHelper(getApplicationContext(), "test.db", null,1);  
     
     SQLiteDatabase db=helper.getWritableDatabase();
     
             //将ContentValues对象添加到数据表中
     //返回值为插入行的row ID
     int rowId=db.insert("person", null, cv);    //第二个参数一般设置为null
     
     db.close();
     
     if(rowId>0)//说明查询成功
    
  2. 删除数据(delete:返回值为删除行的row ID)

     //返回值为删除行的row ID
     int count=db.delete("person", "age < ? and name=?",new String[ ]{ "40","Tom" });
    
  3. 更新数据(update:返回值为更新行的row ID)

     ContentValues cv = new ContentValues(); 
     cv.put("name", "QQ");    //键值对
     cv.put("age", 35);    //键值对
     
     //用ContentValues中的数据更新记录条件值
     int rowId=db.update("person", cv, "id = ?", new String[]{"1"});
     
     db.close();
     
     if(rowId>0) //说明插入成功
    
  4. 查询数据(query:返回Cursor)

     Cursor cursor = db.query(
         "person",new String[]{ "id", "name" }, //SELECT id,name FROM person
         "id > ?",new String[]{ "10" }, //WHERE id>10
         null,null,          //String groupBy=null,String having=null
         "id desc",          //order by id desc
         "1,3");         //LIMIT 1,3
    

    请注意上面这个LIMIT。SQL语法是

         LIMIT offset,lines; 
    

    意思是,从偏移量offset开始查询,最多查lines行,即限定查询范围

         LIMIT 1,3; //从第二行开始查,最多三行。即第二行到第四行范围内查询
    
    • 初始记录行的偏移量是 0(而不是 1)
    • 为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1
    • LIMIT n 等价于 LIMIT 0,n

ContentProvider(★☆)

作用(★☆)

ContentProvider提供了应用程序之间共享数据的方法。也就是说一个应用程序可以通过ContentProvider将自己的数据暴露出去

ContentResolver

应用程序使用 ContentResolver 对象,利用URI,才能访问ContentProvider提供的数据集。

一个ContentProvider可以提供多个数据集,可为多个ContentResolver服务

ContentProvider数据集

ContentProvider可以形象地看作是"数据库", ContentProvider数据集类似于数据库的"表"。

ContentProvider数据集的每条记录包含一个long型的字段_ID,用来唯一标识每条记录

一个ContentProvider表并不要求必须有一个 _ID 列,不过如果想要把查询的数据放在ListView中显示(使用SimpleCursorAdapter填充),则必须有 _ID 列

URI

统一资源标识符,用来标识资源的逻辑位置,不提供资源的具体位置。

(可用Uri.parse(串)转换为Uri)

    content://<authority>/<data_path>/<id>
  • content:固定前缀
  • <authority>:ContentProvider名称(唯一标识)(近似理解为数据库名)
  • <data_path>:数据集名称(近似理解为表)
  • <id>:数据编号,用来匹配数据集中_ID字段的值(用来唯一确定数据集中的一条记录)如果请求的数据并不只限于一条数据,则<id>可以省略
URI示例(★☆)
    content://contacts/people/    //表示全部联系人信息的URI
    content://contacts/people/1     //表示ID=1的联系人信息的URI

了解ContentResolver的query方法(★☆)

  1. 创建一个内容提供器

     ContentResolver  resolver = getContentResolver();
    
  2. 增删改查

    • query(Uri uri, String[] projection, String selection, String[] selectionAr gs,String sortOrder):通过Uri进行查询,返回一个Cursor。
    • insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方,返回最新添加那个记录的Uri。
    • update(Uri uri, ContentValues values, String where, String[] selectio nArgs):更新Uri指定位置的数据,返回更新的行数。
    • delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据,返回删除的行数。

         Cursor cursor = resolver.query(
             "content://com.android.contacts/contacts",          
             //FROM "content://com.android.contacts/contacts"
             
             new String[]{"_id","display_name","has_phone_number"},  
             //SELECT _id,display_name,has_phone_number
             //(null表示所有字段)
             
             "display_name like ?", new String[]{"Tom"},     
             //WHERE display_name like "Tom"
             //(null表示无条件)
             
             "display_name asc"                      
             //ORDER BY display_name asc
             //(null表示按默认排序)
         );              
    

系统ContentProvider主要功能(★☆)

  • Browser:存储如浏览器的信息。
  • CallLog:存储通话记录等信息。
  • Contacts:存储联系人等信息。(URI:ContactsContract.Contacts.CONTENT_URI)
  • MediaStore:存储媒体文件的信息。(URI:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI)
  • Settings:存储设备的设置和首选项信息。

ContentResolver+SimpleCursorAdapter

    SimpleCursorAdapter adapter = new SimpleCursorAdapter(
        this, R.layout.list_row, cursor, from, to);

其中,this是上下文,第二个参数是列表的行视图布局文件,第三个cursor是查询结果,第四个from是查询字段数组,第五个to是与from对应的视图id,将会把对应的查询内容结果绑定到视图

    //一一对应绑定
    
    String[] from = { "_id","name","age"};  //查询结果字段
    int[] to={R,id,ID,R.id.NAME,R.id.AGE};  //视图控件id

然后可以把适配器绑定至ListView

    ListView li=(ListView)findViewById(R.id.listView1); 
    li.setAdapter(adapter);

MediaPlayer媒体播放器(★☆)

  • 当一个MediaPlayer对象被创建或者调用reset()方法之后,它处于空闲状态,调用release()方法后处于结束状态
  • 调用了reset()方法后,再调用其它方法可能会触发OnErrorListener.onError()事件
  • 不再被使用时,最好调用release()方法对其进行释放,使其处于结束状态,此时它不能被使用
  • 由于种种原因导致错误。此时可通过注册setOnErrorListener方法实现监控。如果发生了错误,Mediaplayer对象将处于多雾状态,可以使用reset()方法来回复错误。
  • 任何Mediaplayer对象都必须先处于准备状态,然后才开始播放
  • 要开始播放Mediaplayer对象都必须成功调用start()方法,可通过isPlaying()方法来检测是否正在播放
  • 当Mediaplayer对象在播放时,可以进行暂停和停止操作,pause()方法暂停播放,stop()方法停止播放。处于暂停暂停时可通过start()方法恢复播放,但是处于停止状态时则必须先调用prepare()方法使其处于准备状态,再调用start()方法。
获取实例

可以使用直接new的方式:

    MediaPlayer mp = new MediaPlayer();

也可以使用create的方式,如:

    MediaPlayer mp = MediaPlayer.create(this, R.raw.music);
    //这时就不用调用setDataSource了
设置要播放的文件
  • 用户在应用中事先自带的resource资源

      MediaPlayer.create(this, R.raw.music);
    
  • 存储在SD卡或其他文件路径下的媒体文件

      mp.setDataSource("/sdcard/test.mp3");
    
  • 网络上的媒体文件

      mp.setDataSource("http://www.citynorth.cn/music/confucius.mp3");
    
主要方法(★☆)
  • start() 启动文件播放
  • pause() 暂停播放
  • stop() 停止播放
  • release() 释放播放器占用的资源
  • setDataSource() 设置数据来源
  • seekTo() 定位方法,可以让播放器从指定的位置开始播放(异步方法,该方法返回时并不意味着定位完成。定位真正完成后会触发OnSeekComplete.onSeekComplete(),如果需要是可以调用setOnSeekCompleteListener(OnSeekCompleteListener)设置监听器来处理的。)
  • isPlaying() 判断是否正在播放
  • prepare() 设置播放器进入prepare状态(如果MediaPlayer实例是由create方法创建的,那么第一次启动播放前不需要再调用prepare()了,因为create方法里已经调用过了。)
  • reset() 播放器从Error状态中恢复过来,重新会到Idle状态。

音乐播放器的一般步骤

  1. mp.reset(); //重置
  2. mp.setDataSource(song_path);
  3. mp.prepare(); //准备
  4. mp.start(); //播放
  5. mp.pause(); //暂停
  6. mp.start(); //继续播放
  7. mp.stop(); //停止
  8. mp.release(); //释放音乐播放器

Handler+Message机制代码框架(★☆简答题)

多线程
  1. 继承Thread

     class MyThread extends Thread {
         public void run() {
             ....
         }
     }
     
     new MyThread().start();
    
  2. 实现Runnable接口

     class MyThread implements Runnable {
         @Override 
         public void run() {
             .......
         }
     }
     
     MyThread mythread=new MyThread(); 
     new Thread(mythread).start(); //把接口放到Thread中跑
    
  3. 简化写法

     new Thread( new Runnable() {
         @Override 
         public void run() {
             ....
         }
     }).start();
    
UI的更新必须在主线程(UI线程)中进行,如果在子线程中更新 UI 会导致异常。
Handler消息处理机制

不直接在子线程中进行 UI 操作,子线程通过 Handler 将Message 传送出去。主线程中的Handler接收这个Message,然后进行 UI 等相关操作。
注意!,导入包是

    import android.os.Handler;

不是(默认)包

    import java.util.logging.Handler;
  1. 首先,在主线程中创建Handler(作为Activity的一个成员变量)

     private Handler mHandler = new Handler() { 
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == 某个消息标识) { 
                 ...
             }
             ....
         }
     };
    
  2. 子线程中发送消息

     new Thread( new Runnable() { 
         @Override
         public void run() { 
             // 子线程完成某耗时操作
             
             //推荐Message m = Message.obtain();
             Message m = new Message(); 
             
             m.what = int值;   //消息标识码
             m.arg1 = int值 //要传递的值如进度 
             
             // 发送消息到指定Handler 
             mHandler.sendMessage(m);
         }
     }
    

    子线程也可以使用主线程生成的Hanlder的
    post(Runnable)方法,将Runnable钩到主线程中运行。(★☆)

     //主线程中
     Button bt=(Button)findViewById(R.id.btn_test);
     bt.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
         
             //子线程
             new Thread(new Runnable() {
                 @Override 
                 public void run() { 
                     while (x<100) {
                         Log.d("test", "x=" + (x++) ); mHandler.post(new Runnable() {
                             @Override
                             public void run() {
                                 tv.setText("x="+x);
                             }
                         });
                     }
                     try {
                         Thread.sleep(500); 
                     } catch (InterruptedException e){
                          e.printStackTrace();
                     }
                 }
             }).start();
         }
     }
    

HttpURLConnection网络编程(★☆填空)

1.远程登录

客户端工作
  1. AndroidManifest.xml中添加访问权限

     <uses-permission android:name="android.permission.INTERNET"/>
    
  2. 由于Android模拟器已将127.0.0.1指定给自己,因此要访问本机的Web服务器,用它设定的另一个地址:http://10.0.2.2

  3. 在安卓客户端的活动代码

     public class HttpClientActivity extends Activity {
         
         EditText username;
         EditText psd;
         TextView result;
         
         Handler mHandler = new Handler();
         String httpUrl = "http://10.0.2.2:8080/MyServer/login.jsp";  //网站url    
         URL url;      //URL对象
         HttpURLConnection conn;  //连接对象
         
         //自定义登录函数
         public String login(String username, String psd) {
             String resultData = "";  //服务器返回结果
             try {
                 url = new URL(httpUrl); //创建URL对象(必须try/catch)
             } catch (MalformedURLException e){
                 e.printStackTrace();
             }
             if (url != null) {
                 try {  
                     //关键代码
                     conn = (HttpURLConnection)url.openConnection(); //创建网站连接对象
                     conn.setRequestMethod("POST");     //Post方式访问
                     conn.setRequestProperty("Charset", "UTF-8");   //设置uft-8字符集
                     String datastr = 
                             "username=" + URLEncoder.encode(username, "utf-8") 
                             + "&psd=" + URLEncoder.encode(psd, "utf-8");
                     byte[] data = datastr.getBytes();  //将字符串转化为一个字节数组
                     //向服务器发送数据(Java输出流)
                     DataOutputStream outputStream = 
                                     new DataOutputStream(conn.getOutputStream());
                     outputStream.write(data); 
                     outputStream.close();   //关闭流
                     int code = conn.getResponseCode(); //获得服务器反馈信息
                     if( code == HttpURLConnection.HTTP_OK ) {    //HTTP_OK值为200
                         //接收服务器返回的信息(Java输入流)
                         BufferedReader reader = new BufferedReader(
                             new InputStreamReader(conn.getInputStream()));
                         String line = "";
                         //使用循环来获得数据,一次一行
                         while ( ( line = reader.readLine() ) != null) {
                             resultData += line + "\n";
                         } // end while
                         reader.close();  //关闭流
                     }
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
             return resultData;
         }
         
         //在onCreate方法中
             ......//获得登录按钮btn_login
             bt_login.setOnClickListener(new View.OnClickListener() {
             
                 @Override
                 public void onClick(View v) {
                     //启动线程
                     new Thread(new Runnable() {
                     
                         @Override 
                         public void run() {
                             String u=username.getText().toString(); 
                             String p=psd.getText().toString();
                             final String r = login(u,p); //登录
                             mHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
                                     result.setText("结果:"+r);  //显示结果 
                                 }  //end run
                             });
                         }  //end run                            
                     }).start();
                 }
             });
             
             .....
    

2.web服务手机号归属地查询

    public class TelNumberFind extends Activity {
            
        //成员变量
            EditText mobileCode;
            TextView result;
            
            URL url;  
            HttpURLConnection conn; 
            Handler mHandler = new Handler();
            
            //web服务url串
            String httpUrl = 
                "http://ws.webxml.com.cn/WebServices/MobileCodeWS.asmx/getMobileCodeInfo";
        
        //方法
            public String getRemoteInfo(String mobileCode,String userId) { 
                String resultData = "";  //服务器返回结果
                try {
                    //创建URL对象(必须try/catch) 
                    url = new URL(httpUrl);  
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                }
                if (url != null) {
                    try {
                        //创建网站连接对象
                        conn = (HttpURLConnection)url.openConnection(); 
            
                        //必须Post方式访问
                        conn.setRequestMethod("POST");     
            
                        //设置uft-8字符集
                        conn.setRequestProperty("Charset", "UTF-8");   
                        String datastr = 
                                "mobileCode=" + 
                                URLEncoder.encode(mobileCode, "utf-8") +
                                "&userId=" + 
                                URLEncoder.encode(userId, "utf-8");
            
                        //将字符串转化为一个字节数组
                        byte[] data = datastr.getBytes();  
                        //向服务器发送数据
                        DataOutputStream outputStream = 
                            new DataOutputStream(conn.getOutputStream());
            
                        outputStream.write(data);
                        outputStream.close();   //关闭流
            
                        //获得服务器反馈信息
                        int code = conn.getResponseCode();
            
                        if(code ==HttpURLConnection.HTTP_OK) {
                            //接收服务器返回的信息
                            BufferedReader reader = 
                                new BufferedReader(
                                    new InputStreamReader(conn.getInputStream()
                                )
                            );
            
                            String line = "";
                            
                            //使用循环来获得数据,一次一行
                            while ((line = reader.readLine()) != null) {
                                resultData += line + "\n";
                            }
                            reader.close();  //关闭
                        }
                    }catch (IOException e){
                        e.printStackTrace();
                    }
                }
                return filterHtml(resultData);
            }
            
            //使用正则表达式过滤HTML标记
            private String filterHtml(String source) {
                if(null == source){ 
                    return "";  
                }  
                return source.replaceAll("</?[^>]+>","").trim(); 
            }
        
        //在onCreate中
        
            .....//获得点击按钮btn
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    final String m = mobileCode.getText().toString().trim(); 
                    // 简单判断用户输入的手机号码(段)是否合法
                    if ("".equals(m) || m.length() < 7) {
                        mobileCode.setError("您输入的手机号不足7位!");
                        mobileCode.requestFocus();
                        result.setText("查询结果:无");
                        return;
                    }
            
                    //※启动线程※ 
                    new Thread(new Runnable() {
            
                        @Override 
                        public void run() {
                            //个人用户 userId="" 
                            final String r = getRemoteInfo(m,""); 
            
                            mHandler.post(new Runnable() {
                                @Override
                                public void run() {
                                    //显示结果 
                                    result.setText("结果:"+r);  
                                }  //end run
                            });
                        }
                    }).start();
                }
            });
            
            ......

权限

1.常用权限(★☆)

    <uses-permission android:name="…………"/>

其中引号中可以填写的(常用)权限有

  • android.permission.READ_CONTACTS 读取联系人
  • android.permission.READ_EXTERNAL_STORAGE 读取存储权限
  • android.permission.INTERNET 访问网络权限
  • android.permission.BROADCAST_SMS 收到短信时广播权限
  • android.permission.SEND_SMS 发送短信权限
  • android.permission.CALL_PHONE 拨打电话
  • android.permission.RECORD_AUDIO 录音权限

2.运行时权限

所谓运行时权限是指应用在运行时动态申请权限,由用户决定是否授权,当拒绝授权时,仍然可以使用该应用的其他功能。

安卓6.0中,权限分为三种:普通权限危险权限、特殊权限。第三种权限应用的非常少,可以忽略。

A.普通权限

普通权限是指不会威胁到用户安全和隐私的权限,这一类权限系统会自动授权。而我们只需要在配置清单文件中声明

    <uses-permission android:name="…………"/>

即可。

B.危险权限

危险权限是指威胁到用户安全和隐私的权限,如拨打电话、查询地理位置等。除了要在XML文件中声明

    <uses-permission android:name="…………"/>

我们还需要在java代码文件中进行一些操作(以拨打电话为例)

    ......//在onCreate中点击btn按钮进行拨打电话
    ......      
    btn.setOnClickListener(new View.OnClickListener(){

        public void onClick(View v){
            if(
                ContextCompat.checkSelfPermission(MainActivity.this,
                                            Manifest.permission.CALL_PHONE)
                !=PackageManager.PERMISSION_GRANTED
                ){
    
                //说明没有被用户授权,尝试申请权限
                //该方法第一个是上下文,第二个是权限组,第三个是请求码,只要唯一即可
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[]{Manifest.permission.CALL_PHONE},1);
            }else{
                //说明已经获得授权,可以进行相关操作
            }
        }
    });
    .....

当ActivityCompat.requestPermissions方法调用完后,系统自动弹出申请权限对话框,无论同意与否,最终都要回调onRequestPermissionResult方法。

    protected void onCreate(Bundle savaInstanceState){.....}
    
    ......
    
    @Override
    public void onRequestPermissionResult(
        int requestCode,String[] permissions,int[] grantResults){
    
        //匹配某个权限申请的请求码,如上面的1表示是按钮btn申请拨打电话
        switch(requestCode){
            
            //用户不曾授权,申请权限。
            //用户对第i个权限的授权结果放在grantResults[i]中
            case 1:
            if(grantResults.length>0
                && grantResults[0]==PackageManager.PERMISSION_GRANTED){
                //用户同意了拨打电话权限
                //调用相关方法,如拨打电话
            }else{
                //用户拒绝权限,进行提示性操作
            }
    
            ..........
        }
    }

编程题(★☆)

1.简单音乐播放器

  1. AndroidManifest.xml中添加SD卡访问权限

     <uses-permission 
         android:name="android.permission.READ_EXTERNAL_STORAGE">
     </uses-permission>
    
  2. 注意Android6.0/7.0版本权限问题(一般都放在onCreate中)

     //onCreate
     ......
     if (ActivityCompat.checkSelfPermission( 
             MusicActivity.this, 
             Manifest.permission.READ_EXTERNAL_STORAGE) 
         != PackageManager.PERMISSION_GRANTED ) {
         
         ActivityCompat.requestPermissions(
             MusicActivity.this,
             new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, 
             0x1); 
         return;
     }
     ......
    
  3. 判断是否是AndroidN以及更高的版本(N=24)

     ....//放于上面权限判断之后
     if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ) {
     
     /*Android7.0新的安全策略不推荐使用file://格式来直接访问文件,这里可简单关闭该安全策略*/
     
         StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder(); 
         StrictMode.setVmPolicy(builder.build());
     }
    
  4. 自定义的文件过滤器类

     class MyFilter implements FilenameFilter { 
         private String type; 
         public MyFilter(String type){
             this.type=type;
         }   
         @Override    //实现FilenameFilter接口accept()方法
         public boolean accept(File dir,String name)  {
             //dir当前目录, name文件名
             return name.endsWith(type);//返回true的文件则合格
         }
     }
    
  5. 获取mp3音乐文件路径代码

     ......//放于上面版本判断之后
     ArrayList<String> list = new ArrayList<String>();   //音乐列表
     File sdpath=Environment.getExternalStorageDirectory(); //获得手机SD卡路径
     File path=new File(sdpath+"//mp3//");      //获得SD卡的mp3文件夹
     
     //返回以.mp3结尾的文件 (自定义文件过滤)
     //File[ ] java.io.File.listFiles(FilenameFilter filter):
     //filter是一个文件过滤类,该函数将保留符合filter规定的文件
     File[ ] songFiles = path.listFiles( new MyFilter(".mp3") );
     
     for (File file :songFiles){ 
         list.add( file.getAbsolutePath() );   //获取文件的绝对路径
     }
     
     .....
    
  6. ListView适配器数据填充

     ......//放于上面文件过滤完成之后
     ArrayAdapter<String> adapter = new ArrayAdapter<String>(
             MusicActivity.this,
             android.R.layout.simple_list_item_single_choice, 
             list);
     ListView li=(ListView)findViewById(R.id.listView);
     li.setAdapter(adapter);
     li.setChoiceMode(ListView.CHOICE_MODE_SINGLE);  //单选
    
  7. 音乐播放(设已有成员变量MediaPlayer mp=new MediaPlayer(); String song_path="";)

     li.setOnItemClickListener(new AdapterView.OnItemClickListener() {
         @Override           
         public void onItemClick( AdapterView<?> parent, 
                         View view, int position, long id) {
             song_path="音乐源路径";
             try{
                 mp.reset();    //重置
                 mp.setDataSource(song_path); 
                 mp.prepare();     //准备
                 mp.start(); //播放
             }catch (Exception e){ 
                 e.printStackTrace();
             }
         }
     });
    

8.音乐停止播放(按返回键结束Activity时触发onDestroy())

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(mp!=null ){
            mp.stop(); 
            mp.release();
        }
        //可以输出提示信息
    }

9.音乐暂停和继续

    .....
    //在点击事件中:
    if(song_path.isEmpty()){
        //音乐路径空不合法,输出提示信息
    }
    if( mp.isPlaying() ){//如果播放器正在播放
        mp.pause();  //暂停
    }else if( !song_path.isEmpty() ){ //音乐路径有效不空
        mp.start();   //继续播放
    }
    .......

2.多线程:填充长度100数组,模拟耗时操作

    public class MyActivity extends Activity { 
        private int[] data = new int[100]; //填充长度为100的数组
        private int hasData = 0;    //数组已经填充的元素个数
        ProgressBar p;  //一个进度条组件
        
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 0x111) {     //判断消息标识
                    int progress=msg.arg1;    //取出子线程传来的值
                    p.setProgress(progress);//设置进度条显示进度
                    Log.d("test", "progress=" + progress);//打印日志信息
                    if (progress >= 100){
                        //输出提示信息,进度100%
                        //Toast.makeText(getApplicationContext(),......
                    }
                }
            }
        }
        
        //以填充数组来模拟一个耗时的操作
        public int doWork(){ 
            data[hasData++] = (int)(Math.random() * 100);  //填充一个数组元素
            try{
                Thread.sleep(100);  //线程休息100毫秒,用于模拟其他耗时动作 
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            return hasData;   //返回已填充的个数,亦即进度值  
        }

        //onCreate方法
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState); 
            setContentView(R.layout.activity_my);
            p = (ProgressBar) findViewById(R.id.progressBar); 
            Button btn1=(Button)findViewById(R.id.button);
            btn1.setOnClickListener(new View.OnClickListener() {
                
                @Override
                public void onClick(View v) {
                    hasData=0;    //重新开始
                    
                    //启动线程
                    new Thread(new Runnable()  {    //新建一个(子)线程
                        @Override
                        public void run() {//实现run()方法,完成耗时操作
                            while (hasData < 100){
                                int progress = doWork();  
                                //获取的是耗时操作的完成百分比
                                Message m = new Message();   //创建消息
                                m.what = 0x111;    //设置消息标识
                                m.arg1=progress;   //简单传值
                                mHandler.sendMessage(m);  // 发送消息到Handler
                            }
                        }
                    }).start();
                }
            });
        }

(完)

复习资料
Web note ad 1