Android - HsuJv/Note GitHub Wiki

安卓开发者网站

Android应用项目的各个文件(夹)

  1. .gradle, .idea: 这两个目录下放置的都是Android Studio自动生成的一些文件, 无需关心

  2. app: 项目中的代码, 资源等内容几乎全部都放置在这个目录下

  3. build: 这个目录也不要过多关心, 它主要包含了一些在编译时自动生成的文件

  4. gradle: 这个目录下包含了gradle wrapper的配置文件, 使用gradle wrapper的方式不需要提前将gradle下载好, 而是会自动根据本地的缓存情况决定是否需要联网下载gradle。Android Studio默认没有启动gradle wrapper的方式, 如果需要打开, 可以点击Android Studio导航栏→File→Setting→Build, Execution, Deployment→Gradle, 进行配置更改

  5. .gitignore: 这个文件用来将指定的目录或者文件排除在版本控制之外

  6. build.gradle: 这是项目全局的.gradle构建脚本, 通常这个文件的内容是不需要修改

  7. gardle.properties: 这个文件是全局的.gradle配置文件, 在这里配置的属性将会影响到项目中所有的.gradle编译脚本

  8. gradlew, gradlew.bat: 这两个文件是用来在命令行界面中执行gradle命令的, 其中gradlew是在linux或者Mac系统中使用的, gradlew.bat是在windows系统中使用的

  9. MyApplication.iml: iml文件是所有IntelliJ IDEA都会自动生成的一个文件, (Android Studio是基于IntelliJ IDEA开发的), 用于标识是一个IntelliJ IDEA项目

  10. local.properties: 这个文件用于指定本机中的Android SDK路径

  11. setting.gradle: 这个文件用于指定项目中所有引入的模块, 通常情况下模块的引入都是自动完成

Android Activity生命周期

Android的外观界面开发

  • ViewGroup和View:

    • ViewGroup相当于一个放置View的容器, 在写布局xml的时候, 会告诉容器(凡是以layout为开头的属性, 都是为用于告诉容器的), 宽度(layout_width), 高度(layout_height), 对齐方式(layout_gravity), margin等
    • ViewGroup的职能为: 给childView计算出建议的宽和高和测量模式, 决定childView的位置
    • View的职责: 根据测量模式和ViewGroup给出的建议的宽和高, 计算出自己的宽和高, 并且在ViewGroup为其指定的区域内绘制自己的形态
  • 通过代码和脚本管理外观

    • 脚本: 创建效率高, 减少业务逻辑代码和外观代码的耦合
    • 代码: 通过findViewById找到控件, 通过setXXX来设置相应属性
  • 资源管理

    • 通过@索引资源
    • 通过@+将控件增加到R.java的索引库
  • 几种布局的对齐方式

    • LinearLayout(线性布局): 提供了控件水平(vertical)垂直(horizonta)排列的模型, 同时可以通过设置子控件的weight布局参数控制各个控件在布局中的相对大小
    fill-parent: 占满整个屏幕
    wrap-content: 刚好适合控件内容的大小
    对齐方式gravity取值:
    top: 不改变大小, 位置置于容器的顶部
    bottom: 不改变大小, 位置置于容器的底部
    left: 不改变大小, 位置置于容器的左边
    right: 不改变大小, 位置置于容器的右边
    center_vertical: 不改变大小, 位置置于容器的纵向中央部分
    center_horizontal: 不改变大小, 位置置于容器的横向中央部分
    center: 不改变大小, 位置置于容器的横向和纵向的中央部分
    fill_vertical: 可能的话, 纵向延伸可以填满容器
    fiil_horizontal: 可能的话, 横向延伸可以填满容器
    fiil: 可能的话, 纵向和横向延伸填满容器
    
    • AbsoluteLayout(坐标布局): 可以让子元素指定准确的x/y坐标值, 并显示在屏幕上, (0, 0)为左上角, 当向下或向右移动时, 坐标值将变大, AbsoluteLayout没有页边框, 允许元素之间互相重叠(尽管不推荐)
    android:layout_x="40px"
    android:layout_y="56px"    //确定控件位置
    
    • RelativeLayout(相对布局): 允许子元素指定他们相对于其它元素或父元素的位置(通过ID指定), 因此, 可以左右对齐, 或上下, 或置于屏幕中央的形式来排列两个元素, 元素按顺序排列, 如果第一个元素在屏幕的中央, 那么相对于这个元素的其它元素将以屏幕中央的相对位置来排列。如果使用XML来指定这个layout, 定义它之前, 被关联的元素必须定义。
    android:layout_centerInparent, 将当前控件放置于起父控件的横向和纵向的中央部分
    android:layout_centerHorizontal, 使当前控件置于父控件横向的中央部分  
    android:layout_centerVertical, 使当前控件置于父控件纵向的中央部分
    android:layout_alignParentBottom, 使当前控件的底端和父控件底端对齐
    android:layout_alignParentLeft, 使当前控件的左端和父控件左端对齐
    android:layout_alignParentRight, 使当前控件的右端和父控件右端对齐
    android:layout_alignParentTop, 使当前控件的顶端和父控件顶端对齐
    android:layout_alignParentBottom, 使当前控件的底端和父控件底端对齐
    android:layout_above, 将该控件的底部至于给定ID的控件之上
    android:layout_below, 将该控件的顶部至于给定ID的控件之下
    android:layout_toLeftOf, 将该控件的右边缘和给定ID的控件的左边缘对齐
    android:layout_toRightOf, 将该控件的左边缘和给定ID的控件的右边缘对齐
    android:layout_alignBaseline, 该控件的baseline和给定ID的控件的baseline对齐
    android:layout_alignBottom, 将该控件的底部边缘与给定ID控件的底部边缘
    android:layout_alignLeft, 将该控件的左边缘与给定ID控件的左边缘对齐
    android:layout_alignRight, 将该控件的右边缘与给定ID控件的右边缘对齐
    android:layout_alignTop, 将给定控件的顶部边缘与给定ID控件的顶部对齐
    Android:layout_marginBottom/layout_marginLeft/layout_marginRight/layout_marginTop=”n px”, 使当前控件底部/左边/右边/顶部空出相应像素空间
    
    • FrameLayout(单帧布局): 是最简单的一个布局对象, 它被定制为屏幕上的一个空白备用区域, 之后可以在其中填充一个单一对象, 所有的子元素将会固定在屏幕的左上角, 不能为FrameLayout中的一个子元素指定一个位置, 后一个子元素将会直接在前一个子元素之上进行覆盖填充, 把它们部份或全部挡住(除非后一个子元素是透明的)
    android:src=”@drawable/”, 属性指定所需图片的文件位置, 用ImageView显示图片时, 也应当用android:src指定要显示的图片
    
    • TableLayout(表格布局): 以行列的形式管理子控件, 每一行为一个TableRow的对象, TableRow也可以添加子控件
    android:collapseColumns=“n”, 隐藏TableLayout里面的TableRow的列n
    android:stretchColumns=“n”, 设置列n为可延伸的列
    android:shrinkColumns=“n”, 设置列n为可收缩的列
    

事件处理

  • 事件处理器(event handler)

    • 很多控件本身就带有事件处理器, 例如Button.onTouchEvent()
    • 需要重新定义一个控件继承现有控件, 再重写原有控件的事件处理器
    • 一般很少使用, 除非要引用一种非标准行为的控件
  • 事件监听器(event listner)

    • 创建一个类, 具有接收事件的功能
    • 在类当中定义某种事件处理的方法
    • 形式1:
    XXXListner implements OnClickListner{
        OnClick(){
            ....
        } // 事件处理方法
    }
    
    MyActivity extends Activity{
        OnCreate(){
            Button b = XXX
            b.setOnClickListner(new XXXListner) // 将指定的监听器和有事件
                                                // 处理需求的控件关联起来
        }
    }
    
    • 形式2:
    MyActivity extends Activity implements OnClickListner{ // 某个程序的界面
                                                           // 既是界面, 又是
                                                           // 事件监听器
        OnCreate(){
            Button b = XXX
            b.setOnClickListner(this) // 它自身就是一个监听器
        }
    
        OnClick(){
            ....
        } // 事件处理方法
    }
    
    • 形式3(常用):
    MyActivity extends Activity{
        OnCreate(){
            Button b = XXX
            b.setOnClickListner(new OnClickListner(){
                    OnClick(){
                        ...
                    }
                }) // 使用匿名内部类进行监听器的定义
        }
    }
    

Intents and Intent Filters

  • Intent

    • Intent这个英语单词的本意是"目的, 意向, 意图", Android中提供了Intent机制来协助应用间的交互与通讯, 或者采用更准确的说法是, Intent不仅可用于应用程序之间, 也可用于应用程序内部的activity, service和broadcast receiver之间的交互
    • Intent是一种运行时绑定(runtime binding)机制, 它能在程序运行的过程中连接两个不同的组件, 通过Intent, 程序可以向Android表达某种请求或者意愿, Android会根据意愿的内容选择适当的组件响应
  • Intent Filter

    • 用于接收方
    • 应用在Manifest文件中配置了Intent Filter, 告诉系统能够接收什么样的Intent
  • Intent应用

    • 一个Activity请求另一个Activity: 使用startActivity()或startActiviForResult() (有返回)
    • 启动一个Service: 使用startService() (异步启动一个Service)或bindService() (Activity和Service形成CS模式结构, Activity给Service发出请求, Service返回响应)
    • 发出消息(Broadcast): 使用sendBroadcast()或sendOrderedBroadcast() (发送的Intent信息带有优先级)或sendStickyBroadcast() (所有定义了接收相关Intent接收者接收完后, 消息并不消失, 如果有新的接收者启动, 会立即收到消息)
  • Intent类型

    • 显式(Explicit Intent): 在发出的Intent当中指明由哪个类处理
    • 隐式(Implicit Intent): Intent当中仅包含想要什么, 由用户选择满足该Intent的应用

数据存取

安卓当中各种数据存取方式

  • SQLite DB
    • means a Lite SQL, 体积小
    • 开源, 兼容Standard SQL Language
    • Single tier: 应用程序直接访问数据库, 无中间层
    • No Server: 自包含, 数据仅存储在存储器上, 没有服务器端
    • Loose Type: 每一行每一列的数据类型可以不同
    • App Process: 访问数据库的代码就在应用程序的进程当中, 开销小
  • Internal Storage
    • 手机系统存储器的存储
    • 一般, 安卓系统会在ROM中划分一部分专门存储系统文件以及app相关文件
    • 另一部分称为external storage(包括sd存储卡, 如果安装)
    • internal storage里的内容, 若非有root权限, 仅被创建它的app访问, 而external storage无限制
  • External Storage
    • 可以被任何应用程序访问
  • Shared Preferences
    • 保存一些程序偏好
    • 存储的key-value中的value只能是java的8种原始类型(byte, int, short, long, boolean, char, float, double)
  • Remote Storage
    • net I/O

SQLite I/O:

  • Using SQLite Database for data storage
    • 数据库位于 /data/data/<package-name>/databases
    • 默认情况下为private
    • Notice:
      • While storing big files, a URL of it should be stored in the database instead of the bin file itself
      • Appropriate design should be done for the database
      • There should be a main key ordered 1, 2, 3....
  • Creating a database
    • Code
      • android.database.sqlite.SQLiteDatabase
        • directly use in your code
        • eg:
        SQLiteDatabase sqlDB = openOrCreateDatabase("test.db", SQLiteDatabase.CREATE_IF_NECESSARY, null);
        if (sqlDB != null){
            tv.setText(getString(R.string.activity_main_db_created));
        }
        sqlDB.execSQL("create table tb(id integer primary key, name text, phone long);");
        sqlDB.close();
        
      • android.database.sqlite.SQLiteOpenHelper
        • 通过一个组件(自定义类)间接访问, 解耦
        • eg:
        public class DataHelper {
            SQLiteDatabase sqlDb;
            OpenHelper     oh;
            String         DBNAME;
            int            primary_key_id;
        
            public DataHelper(Context context, String DBNAME){
                this.DBNAME = DBNAME + ".db";
                oh = new OpenHelper(context, DBNAME);
                sqlDb = oh.getWritableDatabase();
                Cursor cursor = sqlDb.rawQuery("select count(id) from tb;", new String[0]);
                if (cursor.moveToFirst()){
                    primary_key_id = cursor.getInt(0);
                }
                else
                    primary_key_id = 0;
            }
        
            public boolean insert (String name, String phone){
                primary_key_id += 1;
                sqlDb.execSQL("insert into tb (id,name,phone) values (" + Integer.toString(primary_key_id) + ", '"+ name + "', '"+ phone + "');");
                return true;
            }
        
            public boolean delete(int primary_key_id){
                sqlDb.execSQL("delete from tb where id = " + Integer.toString(primary_key_id) + ";");
                return true;
            }
        
            public boolean update (int id, String name, String phone){
                sqlDb.execSQL("update tb set name = '" + name + "', phone = '" + phone + "' where id = " + Integer.toString(id) + ";");
                return true;
            }
        
            private static class OpenHelper extends SQLiteOpenHelper{
                private OpenHelper(Context context, String DBNAME){
                    super(context, DBNAME, null, 1, null);
                }
        
                @Override
                public void onCreate(SQLiteDatabase db){
                    db.execSQL("create table tb(id integer primary key, name text, phone long);");
                }
        
                @Override
                public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                    db.execSQL("drop table if exists tb;");
                    onCreate(db);
                }
            }
        }
        调用: DataHelper dh = new DataHelper(MainActivity.this, "test2");
        
    • Linux Shell
      • 使用adb
      • e.g.
      > adb shell
      # cd /data/data/<package-name>/databases
      # sqlite3 test3.db
      > create table tb(id integer primary key, name text, phone long);
      > .quit
      # exit
      
  • Storing / Retrieving
    • Insert, Delete, Update
      • 直接用数据库对象的execSQL方法执行相应的sql语句
    • ContentValue
      • 安卓提供个一个特殊的key-value数据结构(value只为8个基本类型)
      • 常用于数据库记录的添加
      • e.g.
      ContentValues cv = new ContentValues();
      cv.put(col, value);
      ......
      sqlDB.insert(table, null, cv); // SQLiteDatabase sqlDB = openOrCreateDatabase("test.db", SQLiteDatabase.CREATE_IF_NECESSARY, null);
      
    • Retrieve records from a table
      • 主要通过query方法
      • 返回一个访问指针(Cursor)

Using Content Providers for Data access

  • Created to enable sharing of the app's data with other apps. To make other apps can access the database of this app.
  • Custom Content Provider
    • Content Provider(提供者)
      • Create a custom content provider(a class extends ContentProvider)
      • Specify the URI of a content provider
      • Implement query handling methods(e.g. insert)
      • Register a custom content provider: Manifest.xml(label <provider> )
    • Customer(使用方)
      • Declare an instance of ContentResolver class
      • uri = Uri.parse("content://<provider.authorities>/<table name>")
      • Call ContentResolver.query() (wiht a cursor returned)
      • Traversal with the returned cursor(Similar to SQLite)
  • Native Content Provider
    • All native content providers can be used wherever required
    • the uri like content://contacts/people/20 , means to get the 20th people in the contacts (specially, this app must have the READ_CONTACTS permission)

数据存取II

Internal

  • 使用了java.IO类
  • 一般位于 /data/data/<app-package>/files
  • 属于APP所有, 其他应用一般无法访问
  • 关键类或方法:
    • 写:
    openFileOutput(fileName, flag)
    FileOutputStream.write(buffer)
    
    • 读:
    openFileInput(fileName)
    FileInputStream.read(buffer)
    
  • 静态资源文件
    • 一般是一旦创建就不再修改的文件
    • 此类文件一般在 res/raw
    • 一般作为资源文件, 读取利用:
    Resources obj = getResources()
    FileInputStream fis = obj.openRawResource(R.raw.fileName)
    
    • 优点: 将APP外观, 文字等存入资源文件, 高效实现国际化

External

  • 指在系统及应用程序之外的存储空间, Internal空间有限, 所以External一般用于存放大文件
  • SD Card 属于External Data Storage, 一般挂载到 /mnt/sdcard
  • External下的文件一般是公共的, 任何人可以读取
  • 使用External存储之前, 需要用Environment类确认外存是否存在, 并返回外存挂载的路径
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED){
    PrintWriter pw = new PrintWriter(new FileOutputStream(Environment.getExternalStorageDirectory() + "//" + filename));
    pw.println(....);
}
  • 一个APP如果要写数据到外存中, 需要在Manifest文件中声明 <uses-permission 权限
  • 外存文件读取用FileInputStream和FileOutputStream

Shared Preference

  • 适合存储key-value对, 一般用于保存APP设置
  • 有两种类型:
    • Activity-level preference(一个activity一个)
    • Application-level preference(可以存在多个)
  • 只存放boolean, float, int, long, String
  • 读取:
    • Activity-level: SharedPreferences pref = getPreferences(MODE_PRIVATE) // 'Cause it is unique for one activity
    • Application-level: SharedPreferences pref = getPreferences(file_name, MODE_WORLD_READABLE)
  • 存储位置一般在 /data/data/<app-package>/shared_prefs
  • pref文件格式形如:
<map>
    <string name="pref1">test1</string>
    <string name="pref2">test2</string>
</map>
  • 修改:
SharedPreferences obj = getPreferences(...)
SharedPreferences.Editor eObj = obj.edit()
eObj.putValueType("name", value)
eObj.commit()
  • 读取:
SharedPreferences obj = getPreferences(...)
obj.getValueType("name", defaultValue)

Remote Location

  • APP想要联网, 需在 Manifest 中申请权限: <uses-permission android:name="android.permission.INTERNET"></uses-permission>
  • 访问核心代码:
URL obj = new URL("...")
URLConnection objConn = obj.openConnection()
InputStream in = objConn.getInputStream()
BufferedInputStream bis = new BufferedInputStream(in)
char[] ch = new char[1]
while(bis.read(ch) != -1){...}

服务(Services)和消息(Broadcast)

服务(Services)

  • Services是没有界面的运行体, 类似后台进程, 但一般Services存在于调用他的APP的进程中, 本事非进程或线程, 可以类比动态链接库
  • Forms of Services
    • Started: APP想让某些工作在后台做, 通过Context.startService()启动
    • Bound: APP想让自己的功能给其他APP使用, 通过Context.bindService()启动
  • Lifecycle of a Service ServiceLifecycle
  • demo
// MyService.class
public class MyService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    boolean pause = false;

    @Override
    public void onCreate() {
        super.onCreate();
        Toast.makeText(this, "I'm a toast", Toast.LENGTH_LONG).show();
        pause = false;
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++){
                    if (!pause)
                        fun();
                    try{
                        Thread.sleep(2000);
                    }
                    catch (Exception e){
                        ;
                    }
                }
            }
        }).start();
    }

    public void fun(){
        Log.e("Thread", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));}

    @Override
    public void onDestroy() {
        super.onDestroy();
        Toast.makeText(this, "Destroy", Toast.LENGTH_LONG).show();
        pause = true;
    }
}


// MainActivity.class
Button btn1 = findViewById(R.id.button);
Button btn2 = findViewById(R.id.button2);

btn1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        startService(new Intent(MainActivity.this, MyService.class));
    }
});

btn2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        stopService(new Intent(MainActivity.this, MyService.class));
    }
});


// Manifest.xml
<service android:name=".MyService"></service>

消息(Broadcast)

  • 来源:
    • System
    • Application
  • 类型: (Intent即消息)
    • Normal: 使用Context.sendBroadcast(Intent, rcvPermission)来发送
    • Ordered(接收者按序接收): 使用Context.sendOrderedBroadcast(Intent, rcvPermission)来发送
  • Create a Broadcast Receiver
    • Implement a class extends BroadcastReceiver
    • register in manifest
  • Notice:

On Android O, code like this no longer works the way that you expect:

sendBroadcast(new Intent("this.is.an.implicit.broadcast"));

Normally, this broadcast would be received by all receivers that are registered for that custom action string. Even on O, two sets of receivers will still receive the broadcast:

Those whose apps have targetSdkVersion of 25 or lower

Those that were registered via registerReceiver() of some already-running process

However, manifest-registered receivers of apps with a higher targetSdkVersion will not receive the broadcast. Instead, a message like this one will appear in LogCat:

04-11 14:12:36.340 753-763/? W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_REMOVED dat=package:com.commonsware.cwac.cam2.demo flg=0x4000010 (has extras) } to com.commonsware.android.sysevents.pkg/.OnPackageChangeReceiver

  • Solution: As is shown in demo
  • demo
// Sender.class
public class MainActivity extends AppCompatActivity {

    final private String MY_ACT = "com.njupt.hsu.action.MY_ACT";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button btn1 = findViewById(R.id.button);

        btn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent i = new Intent(MY_ACT);
                i.putExtra("count", 3);
                sendImplicitBroadcast(MainActivity.this, i);
            }
        });
    }
    
    // Solution for Android O and newer
    private static void sendImplicitBroadcast(Context ctxt, Intent i) {
        PackageManager pm=ctxt.getPackageManager();
        List<ResolveInfo> matches=pm.queryBroadcastReceivers(i, 0);

        for (ResolveInfo resolveInfo : matches) {
            Intent explicit=new Intent(i);
            ComponentName cn=
                    new ComponentName(resolveInfo.activityInfo.applicationInfo.packageName,
                            resolveInfo.activityInfo.name);

            explicit.setComponent(cn);
            ctxt.sendBroadcast(explicit);
        }
    }
}



// Receiver

// Receiver.class
public class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        int i  = intent.getExtras().getInt("count");
        Log.e("Broadcast", i + "");
    }
}

// Manifest.xml
<receiver android:name=".MyReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="com.njupt.hsu.action.MY_ACT"/>
        <category android:name="android.intent.category.APP_MESSAGING"/>
    </intent-filter>
</receiver>

Enhancing the UI

Create menus

  • Options menu
    • 点按手机实体menu键后出现的6个menu, 如果超过6个, 则一层只显示5个, 加一个"more", 点more后出现的菜单叫Expanded menu
    • 在项目 res/menu 文件夹下可以创建menu的外观脚本
    • Inflating a menu(将外观脚本填充成对象, 便可以在界面中出现)
  • Context menu
    • 与PC右键类似, 任何控件(View)都可以有Context menu
  • Sub menu
    • 菜单的一下项中有三角形的右箭头, 点击后弹出的菜单叫子菜单

Create tabs

Implement styles and themes

  • Defining Styles
  • Inheritance
  • Style Properties
  • Applying Style and Themes to the UI
  • Apply a theme to an activity or application
  • Select a theme based on platform version
  • Applying build-in themes(Using platform styles and themes)
  • More Info

Customize views

  • Creating Compound Views
    • Android 中有些控件本身就是用这种技术创建的, 例如Spinner, AutoCompleteTextView
    • Step1: 定义外观
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        ...
        <EditText
            android:id="@+id/editText1"
            ...
        />
        <Button ... >
            ...
        </Button>
    </LinearLayout>
    
    • Step2: 定义代码并充气(将对象与资源关联) // 此控件包含一个 button, 当按下时, 可以将文本框中字符串两边加上 www. 和 .com
    public class MyCompoundView extends LinearLayout{
        EditText eT;
        Button Btn;
        String str;
        public MyCompoundView(Context context, AttributeSet Attr) {
            super(context, Attr);
            
            String service = Context.LAYOUT_INFLATER_SERVICE;
            LayoutInflater li = (LayoutInflater)getContext().getSystemService(service);
            li.inflate(R.layout.main1, this, true);
            eT = (EditText)findViewById(R.id. editText1);
            Btn = (Button)findViewById(R.id. add);
            AddUrl();
        }
        private void AddUrl() {
            Btn.setOnClickListener(new Button.OnClickListener() {
                public void onClick(View v) {
                    str= eT.getText().toString();
                    str="http://www."+str+".com";
                    eT.setText(str);
                }
            });
        }
    }
    
    • Step3: 声明新控件的存在:
    // <merge> 见 http://developer.android.com/guide/topics/resources/layout-resource.html#merge-element
    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android">
        <com.CompoundView.niit.MyCompoundView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />
    </merge>   
    
  • Creating Custom Views
    • step1: 定义控件:
    public class MycustomButton extends View{
        public MycustomButton(Context context){
            super(context);                 
        }
        @Override
        protected void onDraw(Canvas canvas)
        {
            canvas.drawXXXX();
        }
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
            测量控件的大小(控件默认大小取所在容器大小),调用 setMeasuredDimension(int, int) 进行设置
        }
        @Override
        public boolean onKeyUp(int keyCode, KeyEvent keyEvent){
            ... // 让控件具有交互性
        }
    }
    
    • step2: 使用控件:
    <view
         class="com.android.Custom.OutterClass$MyCustomView "   // 若 MyCustomView 是以内部类定义的, 则须写出 外部类名字, 并用 $ 符号分隔
         id="@+id/note"
         android:layout_width="fill_parent"
         android:layout_height="fill_parent"
         android:background="@android:drawable/empty"
         android:padding="10dip"
         android:scrollbars="vertical"
    android:fadingEdge="vertical" />
    

Notifying the User

Use notifications

  • Toast
    • Standard toast
      • step 1: android.widget.Toast.makeToast(context, text, duration)
      • step 2: Toast.makeToast(...).show
      • step 3: Change the position (default is the bottom)
        • toast.setGravity(Gravity.BOTTOM|Gravity.LEFT, 0, 0);
    • Custom toast
      • step 1: Create the view
      <LinearLayout
          ...
          <TextView android:id="@+id/textView1" android:text=".."
                  android:layout_width="wrap_content">
          </TextView>
          <EditText android:text="" android:layout_height="wrap_content"
              ..>
          </EditText>
      <LinearLayout>
      
      • step 2: Inflating
      LayoutInflater inflater = getLayoutInflater();
      View toastView = inflater.inflate(R.layout.ctoast, (ViewGroup)findViewById(R.id.cToast));
      
      • step 3: Use the toast
      Toast toast = new Toast(getApplicationContext());
      // 设置文字、duration等
      toast.setView(toastView);
      toast.show()
      
  • Notification area and drawer
    • Note: the code has changed after several versions change. To get the newest api guide, go and see.
    • 'cause service do not have UI interface, notification drawer is a applicable way for it to notify user.
    • Two classes:
      • Notification: the object which is show in notification drawer
      • NotificationManager: call notify(int id, Notification-object) to display the notification. Note: if there are two notifications share the same id, the later one will cover the earlier one.
    • step 1: Obtain the NotificationManager service
    NotificationManager nm = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    
    • step 2: Create the instance of Notification
    //The message to be shown on the notification drawer
    String msg = "Notification msg"; // Text
    long when = System.currentTimeMillis(); // When to display
    int icon = R.drawable.icon; // Icon
    Notification notification = new Notification(icon, ticker, when);
    
    • step 3: Set the context which is to be show when drop down the bar
    setLatestEventInfo(Context con, CharSequence title, CharSequence text, 
                                    PendingIntent i); // 对notification window 中的项, 点击的话要做什么
    
    • step 4: Sound
    notification.defaults |= Notification.DEFAULT_SOUND;
    notification.sound = Uri.parse("file:///sdcard/alarm.mp3");
    
    • step 5: Vibration
    notification.defaults|= Notification.DEFAULT_VIBRATE;
    long[] vibrate = {0,100,200,300}; // {震动前等待,1st震动时长,第一次震动后等待时长,2nd震动时长} , 单位ms
    notification.vibrate = vibrate;
    
    • step 6: Flash Lights
    notification.defaults |= Notification.DEFAULT_LIGHTS;
    notification.ledARGB = 0xffff0000;  // 颜色
    notification.ledOnMS = 250;         // 亮灯时长 ms
    notification.ledOffMS = 500;        // 闭灯时长
    notification.flags |= Notification.FLAG_SHOW_LIGHTS;
    
  • Dialog box

Use alarms

  • When alarm triggered -> Android send an Intent -> Automatically start corresponding broadcast
  • Alarms will be deleted when the machine is shutdown.
  • Alarm can wake the system up or just keep it sleeping when the system is sleeping.
  • AlarmManager will hold a cpu lock which ensures the system sleep after handling this alarm.
  • The essence of the alarm is: Independent services that can be used to fire intents at predetermined times.
  • Use:
    • step 1: Obtain AlarmManager
      • References
      • AlarmManager alarms = (AlarmManager)getSystemService(Context. ALARM_SERVICE);
    • step 2.1: Create one-time alarms
    int Type = AlarmManager.ELAPSED_REALTIME_WAKEUP;
    long timeOfWait = 10000;    // 单位ms
    String ALARM_ACTION = "ALARM_ACTION"; // Name
    Intent intentToFire = new Intent(ALARM_ACTION);
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intentToFire, 0);
    alarms.set(Type, timeOfWait, pendingIntent)
    
    • step 2.2: Create repeating alarms
    alarms.setRepeating(AlarmManager.RTC_WAKEUP,
                    System.currentTimeMillis() + (5 * 1000),        // triggerAtMillis
                    10 * 1000,      //intervalMillis
                    pendingIntent);
    
    • step 3: Cease
    alarms.cancel(pendingIntent);
    

Using Location-based Services

Identify location-based service

  • Three LBS technique: Global Positioning System (GPS); Cell tower triangulation; Public Wireless Fidelity (Wi-Fi) hotspots
  • GPS:
    • Trilateration 2D/3D, computes the distance to each satellite using the speed of light
    • A-GPS (Assisted GPS): An expansion of GPS, using internet to accelerate the speed.
  • Cell Tower Triangulation
    • computing its location by identifying neighboring cells and their signal strengths.
  • Public Wi-Fi Hotspots
    • This technique asks the provider to refresh the change of Wi-Fi hotspots
  • Working with Location-based Service
    • Two significant classes: LocationManager, LocationProvider
  • Accessing Location-based Services
    • In order to prevent power consumption, privacy leaks, applications need to apply for permission to use LBS
    • step 1: apply for permission
    // for precise location
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    // for roughly location
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    
    • step 2: Get Location Manager
    String serviceString = Context.LOCATION_SERVICE;
    LocationManager lm = (LocationManager)getSystemService(serviceString);
    

Select a location provider

Access location-based services in an emulator

  • demo:
requestPermissions(new String[] {
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION }, 1
        );

final LocationManager lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
boolean gpsIsOk = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
if(gpsIsOk){
    try{
        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5, new LocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                Toast.makeText(MainActivity.this,"location updated", Toast
                        .LENGTH_SHORT).show();
            }

            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {

            }

            @Override
            public void onProviderEnabled(String provider) {

            }

            @Override
            public void onProviderDisabled(String provider) {

            }
        });
    }
    catch (SecurityException e){
        Log.e("Location:", e.toString());
    }
}

Button bt = findViewById(R.id.button);
bt.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        try{
            Location loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            String locInfo = Double.toString(loc.getLongitude()) + " " + Double.toString
                    (loc.getLatitude()) +
                    " " + Double.toString(loc.getAltitude());
            Toast.makeText(MainActivity.this, locInfo, Toast.LENGTH_LONG).show();
        }
        catch (SecurityException e){
            Log.e("location: ", e.toString());
        }
    }
});

Create map-based application

  • To ways to use Google map: web, Google Map API
  • Using the MapView Class
    • MapView offers a wealth of map features such as zoom, marker position, etc., supports a number of view types (route, satellite, traffic flow, etc.)
    • step 1: declaration ('cause it isn't in the standard library)
    <uses-library android:name="com.google.android.maps" />
    
    • step 2: Use MapView as simple widget
    MapView mapView;
    mapView = (MapView)findViewById(R.id.map_view);
    mapView.setSatellite(true);
    mapView.setTraffic(true);
    
    • step 3: Apply for a Google Map agreement with Google by a unique developer sign; and then obtain a Maps API key (Google will know who is using the Map service)
    <com.google.android.maps.MapView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapview"
        ...
        android:layout_height="fill_parent"
        android:clickable="true"
        android:apiKey="<generated key>"
    />
    
    • step 4: Use MapController to handle zoom.
  • demo:
public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    setContentView(R.layout. main);
    mapView = (MapView) findViewById(R.id. mapView);
    mapView.setBuiltInZoomControls(true);
    mapView.setStreetView(true);
    mapController = mapView.getController();
    mapController.setZoom(14); // Zoom 1 is world view
    locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    String coordinates[] = {"1.352566007", "103.78921587"};
    double lat = Double.parseDouble(coordinates[0]);
    double lng = Double.parseDouble(coordinates[1]);                
    GeoPoint gp = new GeoPoint((int) (lat * 1E6), (int) (lng * 1E6));
    mapController.animateTo(gp);
}
⚠️ **GitHub.com Fallback** ⚠️