0%

Android语法笔记(Java)

Android笔记

工程目录结构

└── HelloWorld
├── app 项目代码,资源
│ ├── build 编译自动生成
│ ├── build.gradle
│ ├── libs 项目中引用的第三方jar包
│ ├── proguard-rules.pro 加密混淆,防逆向破解
│ └── src  资源目录
├── build.gradle 项目全局的gradle构架脚本
├── gradle  gradle wrapper配置文件
│ └── wrapper
├── gradle.properties gradle 全局配置文件
├── gradlew
├── gradlew.bat
├── local.properties 指定本机中android sdk路径
└── settings.gradle  指定项目中所有引入的模块

资源目录src

├── androidTest   测试用例
│ └── java
│ └── com
├── main
│ ├── AndroidManifest.xml  整个android项目配置文件
│ ├── java
│ │ └── com
│ └── res          资源目录
│ ├── drawable      放图片,drawable开头
│ ├── drawable-v24
│ ├── layout       布局文件
│ ├── mipmap-anydpi-v26 由mipmap开头的都是用来放图标的
│ ├── mipmap-hdpi
│ ├── mipmap-mdpi
│ ├── mipmap-xhdpi
│ ├── mipmap-xxhdpi
│ ├── mipmap-xxxhdpi
│ ├── values
│ └── values-night
└── test   用于Unit test
└── java
└── com

res目录中定义的xml文件可以在新的xml文件或者java代码中被引用

例如res下有一个 strings.xml 文件

1
2
3
<resources>
<string name="app_name">HelloWorldApplication</string>
</resources>

则在AndroidManifest.xml文件中作为顶栏应用名显示

1
android:label="@string/app_name"

而在代码中引用则用R.String.app_name

安卓的log

log.v() verbose

log.d() debug

log.i() info

log.w() warinning

log.e() error

直接类比输出语句,输出在logcat中

activity

活动是一种可以包含用户界面的组件,用于和用户交互

  • 手动创建活动

    1. 在app/src/main/java下创建一个新的活动firstActivity.java

    2. /app/src/main/res下新建layout目录,并且在目录中新建一个布局,命名为firt_layout,在布局中新建按钮

    3. 在firstActivity.java的OnCreate重载函数中加载布局setContentView(R.layout.first_layout)

    4. 在AndroidManifest.xml中注册为主活动

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <activity
      android:name=".firstActivity"
      android:label="this is firstactivaty">  <!--应用程序显示的名字 -->
      <intent-filter>
      <!-- 注册为主活动 -->
      <action android:name="android.intent.action.MAIN" />
      <!-- 注册为启动 -->
      <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      </activity>
  • tips: ctrl + o AS中触发函数重写模板

1
2
3
4
5
//toast 使用方法
//param: context , text ,show_time
//context为toast显示的上下文,this表示当前的活动
//第三个参数表示toast显示的时长,有两个LENGTH_SHORT,LENGTH_LONG
Toast.makeText(this, "toast显示的内容", Toast.LENGTH_SHORT).show();

菜单使用方法

在res文件夹中创建menu目录,并且创建一个新的资源文件(main.xml)

@+id/test是注册一个新的id选项,可以被java调用

R.id.test

1
2
3
4
5
6
7
8
9
10
<!--   定义了菜单两个项目,add,remove     -->
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//    创建菜单栏 /res/menu/main.xml
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}

// 相应菜单选项触发的事件
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.add_item:
Toast.makeText(this, "you clicked add", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "you click Remove", Toast.LENGTH_SHORT).show();
break;
default:
}
return true;
}

销毁活动只需一个函数finsh()

intent启动activity

显式调用

1
2
3
4
5
6
7
8
9
10
//事先创建好的secondActivity.java
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// intent 显式调用
Intent intent = new Intent(firstActivity.this,secondActivity.class);
startActivity(intent);
}
});

隐式调用

为创建好的第二个活动注册

1
2
3
4
5
6
  <activity android:name=".secondActivity">
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

在firstActivity.java中调用

1
2
3
4
5
6
7
8
9
 Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// intent 隐式掉用
Intent intent = new Intent("com.example.activitytest.ACTION_START");
startActivity(intent);
}
});
  • 使用隐式调用其他系统功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Button button1 = (Button) findViewById(R.id.button1);
    button1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    //拉起系统浏览器
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setData(Uri.parse("http://www.baidu.com"));
    //拉起电话界面
    Intent intent = new Intent(Intent.ACTION_TEL);
    intent.setData(Uri.parse("tel:10086"));
    startActivity(intent);
    }
    });

intent数据传递

向下一个活动传递

1
2
3
4
5
6
7
8
9
10
//fitstActivity.java
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(firstActivity.this,secondActivity.class);
intent.putExtra("extra_Data","sent");//给intent加上额外的信息
startActivity(intent);
}
});
1
2
3
4
5
6
7
8
9
//secondActivity.java   
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Intent intent = getIntent();
String data = intent.getStringExtra("extra_Data");//因为传送消息是String所以getStringExtra,如果bool就getBooleanExtra
Log.d("secondActivity" ,data );

向上一个活动传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//fitstActivity.java
//创建需要返回结果的活动
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(firstActivity.this,secondActivity.class);
//startActivityForResult函数:当启动的活动销毁时返回结果给上一个活动
startActivityForResult(intent,1);//这里的1是requestcode ,只要是个唯一值都可以
}
});
}

//接受来自销毁活动的结果
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
switch (requestCode){
case 1:
if(resultCode == RESULT_OK){
String returnData = data.getStringExtra("data_return");//接受来自销毁活动返回的字符串,放在returnData中
Log.d("firstActivity", returnData);
}
break;
default:
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//secondActivity.java
setContentView(R.layout.activity_second);
Button button2 = (Button) findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//构造一个新的intent用于回传,并且将结果的值置为RESULT_OK
Intent intent = new Intent();
intent.putExtra("data_return","hello firstactivity");
setResult(RESULT_OK,intent) ;
finish();//销毁
}
});

//对back键的情况进行处理,逻辑同OnClick
@Override
public void onBackPressed() {
// 不可以有默认创建的这个继承,会导致无效 super.onBackPressed();
Intent intent = new Intent();
intent.putExtra("data_return","hello firstacticity");
setResult(RESULT_OK,intent);
finish();
}

UI

控件

在LinearLayout布局的情况下,只有android:orientation=”vertical”才可以不重叠的显示

  • TextView

    1
    2
    3
    4
    5
    6
    7
    8
    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:textSize="24sp"
    android:textColor="#00ff00"
    android:text="this is Text view"
    />
  • Button

    1
    2
    3
    4
    5
    6
    7
    8
    <Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Button"
    android:textAllCaps="false"
    />

  • EditText

    1
    2
    3
    4
    5
    6
    <EditText
    android:id="@+id/edit_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="type atuoa"
    android:maxLines="2"/>

    hint指的是未输入文字之前的提示

    maxline表示的是最大显示的行数

  • ImageView

    1
    2
    3
    4
    5
    6
    <ImageView
    android:id="@+id/image_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/img_1"
    />

    src为资源目录下的图片

  • ProgressBar

    转圈圈进度条

    1
    2
    3
    4
    5
    <ProgressBar
    android:id="@+id/progress_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />
    1
    2
    3
    //定义一个状态栏
    ProgressBar progress_bar = (ProgressBar)findViewById(R.id.progress_bar);

    1
    2
    3
    4
    5
    6
    7
    //visible可见  gone消失   invisible不可见
    //get visibility
    progress_bar.getVisibility() == View.VISIBLE
    //set gone
    progress_bar.setVisibility(View.GONE);
    //set visible
    progress_bar.setVisibility(View.VISIBLE);

    水平进度条

    1
    2
    3
    4
    5
    6
    7
    <ProgressBar
    android:id="@+id/progress_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    style="?android:attr/progressBarStyleHorizontal"
    android:max="100"
    />
    1
    2
    3
    4
    //设置进度条的值
    int progerss = progressBar.getProgress();
    progeress = Progress +10 ;
    progressBar.setProgress(progress);
  • AlertDialog

    点击按钮弹出对话框

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
    dialog.setTitle("this is dialog");
    dialog.setMessage("context");
    dialog.setCancelable(false);
    dialog.setPositiveButton("ok", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {

    }
    });
    dialog.setNegativeButton("cancel", new DialogInterface.OnClickListener() {
    @Override
    public void onClick(DialogInterface dialog, int which) {

    }
    });
    dialog.show();

    }
    });
  • ProgressDialog

    1
    2
    3
    4
    5
    6
    7
    ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
    progressDialog.setTitle("this is progressdialog");
    progressDialog.setMessage("loading..");
    progressDialog.setCancelable(true);
    progressDialog.show();
    //大致和对话框相同
    //当Setcancelable为假时,不能通过back取消,在调用完之后使用dismiss()来关闭对话框

四种布局

LinearLayout

edittext自适应,button仅包裹文字

1
2
3
4
5
6
7
8
9
10
11
12
13
<EditText
android:id="@+id/edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="type atuoa"/>
<Button
android:id="@+id/button"
android:layout_width="warp_content"
android:layout_height="wrap_content"
android:text="Button"
/>

edittext:button = 3:2比例布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<EditText
android:id="@+id/edit_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:hint="type atuoa"/>
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_weight="2"
android:layout_height="wrap_content"
android:text="Button"
/>

RelativeLayout

  • 相对父元素布局

    android:layout_alignParentButtom

    android:layout_alignParentTop

    android:layout_alignParentLeft

    android:layout_alignParentRight

    android:layout_centerInParent

    取值均为布耳值

  • 相对控件布局

    android:layout_above=”@id/id值”

    android:layout_below=”@id/id值”

    android:layout_toRightOf=”@id/id值”

    android:layout_toLeftOf=”@id/id值”

    ps:当一个控件引用另一个控件id时必须要定义在引用控件的后面。

FrameLayout

PercentFrameLayout

在app/build.gradle文件的dependencies

添加 compile 'com.android.support:percent:24.2.1'

sync now

例子:一屏占满四个按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<Button
android:id="@+id/button1"
android:layout_gravity="left | top"
app:layout_wedthPercent="50%"
app:layout_heightPercent="50%"
android:text="Button1"
/>
<Button
android:id="@+id/button2"
android:layout_gravity="right | top"
app:layout_wedthPercent="50%"
app:layout_heightPercent="50%"
android:text="Button2"
/>
<Button
android:id="@+id/button3"
android:layout_gravity="left | bottom"
app:layout_wedthPercent="50%"
app:layout_heightPercent="50%"
android:text="Button3"
/>
<Button
android:id="@+id/button4"
android:layout_gravity="right | buttom"
app:layout_wedthPercent="50%"
app:layout_heightPercent="50%"
android:text="Button4"
/>

自定义系统控件

  • 自定义布局顶栏ActionBar

将写好的标题栏xml放入layout文件夹中,假设为title.xml

在activity_main.xml布局文件中添加

<include layout = "layout/title">

把系统自带的标题栏隐藏

1
2
3
4
5
// in func OnCreate()
ActionBar Actionbar = getSupportAcitonBar();
if(actionbar != null)
actionbar.hide();

  • 自定义控件

在title.xml已经建立好的情况下,新建一个TitleLayout继承自LinearLayout,让他成为自定义的标签栏控件

1
2
3
4
5
6
public class TitleLinearout extends LinearLayout{
public TitileLayout(Context context , AttributeSet attrs)
super(context,attrs);
//动态加载一个布局文件,inflate接受两个参数,id和加载好的布局添加一个父布局
LayoutInflater.from(context).inflate(R.layout.title,this);
}

在布局文件中添加自定义控件

1
2
3
4
<com.example.uicustomviews.TitleLayuot>
android:layout_width="match_parent"
android:layout+_height="warp_content"
/>

在事件触发的时候直接调用就行

ListView

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

</LinearLayout>

//MainAcitivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private String[] data = {
"1","2","3","4","5","6","7","8","9","10",
"1","2","3","4","5","6","7","8","9","10"
};

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

ArrayAdapter<String> adapter = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1,data
);
ListView listview = (ListView) findViewById(R.id.list_view);
listview.setAdapter(adapter);
}

// listview点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private String[] data = {
"1","2","3","4","5","6","7","8","9","10",
"1","2","3","4","5","6","7","8","9","10"
};

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

ArrayAdapter<String> adapter = new ArrayAdapter<String>(
MainActivity.this, android.R.layout.simple_list_item_1,data
);
ListView listview = (ListView) findViewById(R.id.list_view);
listview.setAdapter(adapter);

listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, position+" is clicked ",Toast.LENGTH_SHORT).show();
}
});

  • 进阶:
    1. 结合图片的listerview
    2. 提升效率

RecyclerView

文件

context类使用java文件流读写文件

懒的看,用到时再写,不如用sharePreferences

SharePreferences(通过键值对存储数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//事先定义两个按钮,一个存储数据按钮一个恢复数据按钮
//存储数据
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = getSharedPreferences("data",MODE_PRIVATE).edit();
editor.putString("name","tom");
editor.putInt("age",23);
editor.putBoolean("married",false);
editor.apply();

}
});

//恢复数据
Button button1 = (Button)findViewById(R.id.restore_button);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String TAG = "mylog";
SharedPreferences pref = getSharedPreferences("data",MODE_PRIVATE);
String name = pref.getString("name","");
int age = pref.getInt("age",1);
boolean married = pref.getBoolean("married",true);
Log.d(TAG, "name is "+name);
Log.d(TAG, "age is "+age);
Log.d(TAG, "married is" +married);
}
});

数据库

adb操作

  • adb shell进入调试
  • su提权获得root权限
  • cd到程序文件目录,一般为/data/data/包名/

sqlite操作

  1. sqlite3 databse.db 进入名为database.db的数据库
  2. .table 列出表   .schema 列出所有表结构   .exit  退出sqlite交互式命令
  3. 查询   select * from table_name

litepal操作sqlite数据库

  • 配置

    1. Include library

    Edit your build.gradle file and add below dependency.

    1
    2
    3
    dependencies {
    implementation 'org.litepal.guolindev:core:3.2.2'
    }

    2. Configure litepal.xml

    Create a file in the assets folder of your project and name it as litepal.xml. Then copy the following codes into it.

    再项目文件夹下右键新建一个asset folder - 然后添加一个文件命名为litepal.xml,复制粘贴以下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <?xml version="1.0" encoding="utf-8"?>
    <litepal>
    <!--
    Define the database name of your application.
    By default each database name should be end with .db.
    If you didn't name your database end with .db,
    LitePal would plus the suffix automatically for you.
    For example:
    <dbname value="demo" />
    -->
    <dbname value="demo" />

    <!--
    Define the version of your database. Each time you want
    to upgrade your database, the version tag would helps.
    Modify the models you defined in the mapping tag, and just
    make the version value plus one, the upgrade of database
    will be processed automatically without concern.
    For example:
    <version value="1" />
    -->
    <version value="1" />

    <!--
    Define your models in the list with mapping tag, LitePal will
    create tables for each mapping class. The supported fields
    defined in models will be mapped into columns.
    For example:
    <list>
    <mapping class="com.test.model.Reader" />
    <mapping class="com.test.model.Magazine" />
    </list>
    -->
    <list>
    <mapping class="包名.类名" />
    </list>

    <!--
    Define where the .db file should be. "internal" means the .db file
    will be stored in the database folder of internal storage which no
    one can access. "external" means the .db file will be stored in the
    path to the directory on the primary external storage device where
    the application can place persistent files it owns which everyone
    can access. "internal" will act as default.
    For example:
    <storage value="external" />
    -->

    </litepal>

    This is the only configuration file, and the properties are simple.

    • dbname configure the database name of project.
    • version configure the version of database. Each time you want to upgrade database, plus the value here.
    • list configure the mapping classes.
    • storage configure where the database file should be stored. internal and external are the only valid options.

    3. Configure LitePalApplication

    You don’t want to pass the Context param all the time. To makes the APIs simple, just configure the LitePalApplication in AndroidManifest.xml as below:

    1
    2
    3
    4
    5
    6
    7
    8
    <manifest>
    <application
    android:name="org.litepal.LitePalApplication"
    ...
    >
    ...
    </application>
    </manifest>

    Of course you may have your own Application and has already configured here, like:

    1
    2
    3
    4
    5
    6
    7
    8
    <manifest>
    <application
    android:name="com.example.MyOwnApplication"
    ...
    >
    ...
    </application>
    </manifest>

    That’s OK. LitePal can still live with that. Just call LitePal.initialize(context) in your own Application:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyOwnApplication extends Application {

    @Override
    public void onCreate() {
    super.onCreate();
    LitePal.initialize(this);//直接使用这个也行,或者按照上面步骤去注册全局配置
    }
    ...
    }

    Make sure to call this method as early as you can. In the onCreate() method of Application will be fine. And always remember to use the application context as parameter. Do not use any instance of activity or service as parameter, or memory leaks might happen.

  • 使用

    创建一个类,名字就为mapping中的类名,

    假设有一个类book,

    1
    2
    3
    4
    5
    6
    7
    8
    public class Book extends LitePalSupport {
    private int id;
    private double price;
    private String author;
    private int pages;
    private String name;
    private String press;
    //省略所有属性的set & get

    然后再MainActivity中Connector.getDatabase();即完成了对数据库的创建,结构就是Book中定义的

    • 对于增加数据的类比如Book需要继承 extends LitePalSupport 才可以增加记录.

      1
      2
      3
      4
      5
      6
      7
      8
      //MainActivity
      Book book =new Book();
      book.setName("The Da Vinci Code");
      book.setAuthor("Dan Brown");
      book.setPages(454);
      book.setPress("Unknow");
      book.setPrice(16.96);
      book.save();
    • 更新

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
                      Book book = new Book();
      book.setPrice(14.59);
      book.setPress("Anchor");
      book.updateAll("name = ? and author = ?","The Da Vinci Code","Dan Brown");
      //更新表中书名为The Da Vinci Code 且作者为Dan Brown的书出版社,价格

      //更新所有书的页数初始值
      Book book = new Book();
      book.setToDefault("pages");
      book.updateAll();
    • 删除

      1
      2
      //删除价格低于15的记录
      LitePal.deleteAll(Book.class,"price < ?","15");
    • 查询

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      List<Book> books = LitePal.findAll(Book.class);
      for(Book book:books)
      {
      Log.d("MainActivity", "book name is "+book.getName());
      Log.d("MainActivity", "book author is "+book.getAuthor());
      Log.d("MainActivity", "book pages is "+book.getPages());
      Log.d("MainActivity", "book price is "+book.getPrice());
      Log.d("MainActivity", "book press is "+book.getPress());

      }

    • 更多

Context Provider

主要用于在不同的应用程序之间的数据共享功能,它提供一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问程序的安全性。

运行时权限

  • 安卓6开始对使用的运行时权限,在程序运行时对权限提出申请

例子拨打电话

1
2
<!-- Manifest.xml中添加权限申请-->
<uses-permission android:name="android.permission.CALL_PHONE"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//MainActivity 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button makeCall = (Button) findViewById(R.id.make_call);
makeCall.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//查看是否有拨打电话的权限,如果有,调用call()进行拨打;否则申请权限
if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE)!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.CALL_PHONE},1);

}
else
call();
}
});

}
//使用intent来拨打电话
private void call(){
try{
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
catch (SecurityException e)
{
e.printStackTrace();
}
}
//checkSelfPermission的回调函数,会将结果返回到这个函数中,进行进一步的处理
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
call();
else
Toast.makeText(this,"you denied the permission",Toast.LENGTH_SHORT).show();
default:
}
}

例子显示联系人列表

1
2
<!--添加权限声明-->   
<uses-permission android:name="android.permission.READ_CONTACTS"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
ArrayAdapter<String> adapter;
List<String> contactsList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ListView contactsView = (ListView) findViewById(R.id.contacts_view);
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,contactsList);
contactsView.setAdapter(adapter);
if(ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);

}
else
readContacts();

}

//调用getContentResolver的query的方法,会返回一个cursor,需要取出当中的内容
private void readContacts(){
Cursor cursor = null;
try{
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if(cursor != null)
{
while ( cursor.moveToNext())
{
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(displayName+"\n"+number);
}
adapter.notifyDataSetChanged();
}

}
catch (Exception e)
{
e.printStackTrace();
}
finally {
if(cursor != null)
{
cursor.close();//关闭游标
}
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode)
{
case 1:
if(grantResults.length >0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
readContacts();
else
Toast.makeText(this,"you denied the permission",Toast.LENGTH_SHORT).show();
break;

default:
}
}

自定义内容提供器

用于不同应用之间数据库互相访问的接口。

参考书7.4

通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    
//MainActivity.java
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
createNotificationChannel();
Button sendNotice =(Button) findViewById(R.id.send_notice);
sendNotice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(MainActivity.this, "text")//通道名
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("test")
.setContentText("this is a test notification")
.setPriority(NotificationCompat.PRIORITY_DEFAULT);

//显示通知
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(MainActivity.this);

// notificationId is a unique int for each notification that you must define,这里的1可以自定义,只要是独一无二的就可,notificationID
notificationManager.notify(1, builder.build());
}
});
}

@RequiresApi(api = Build.VERSION_CODES.O)
private void createNotificationChannel() {
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "test";
String description = "test";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("text", name, importance);
channel.setDescription(description);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
}
}

webview

MainActivity.java

1
2
3
4
5
6
  WebView webView = findViewById(R.id.web_view);
webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadUrl("https://www.dogedoge.com");
//如果不是https需要在在AndroidManifest中的<application标签中加入android:usesCleartextTraffic="true"
//原因:高版本已经不允许明文传输(使用http)(因为不安全),所以使用android:usesCleartextTraffic="true"强制允许明文传输

manifest.xml

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

activity_main.xml

1
2
3
4
5
<WebView
android:id="@+id/web_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

httpConnection

  • 原生实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
       TextView responseText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button sendRequest = (Button) findViewById(R.id.send_request);
    responseText = (TextView) findViewById(R.id.response_text);
    sendRequest.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    if(v.getId() == R.id.send_request)
    sendrequestWithHttpURLConnection();
    }


    private void sendrequestWithHttpURLConnection(){
    //开启线程来发起网络请求
    new Thread(new Runnable() {
    @Override
    public void run() {
    HttpURLConnection connection = null;
    BufferedReader reader = null;

    try {
    URL url = new URL("https://www.dogedoge.com");
    connection = (HttpURLConnection) url.openConnection();
    connection.setRequestMethod("GET");
    connection.setConnectTimeout(8000);
    connection.setReadTimeout(8000);
    InputStream in = connection.getInputStream();
    // 下面对获取到的输入流进行读取
    reader = new BufferedReader(new InputStreamReader(in));
    StringBuilder response = new StringBuilder();
    String line;
    while((line = reader.readLine()) != null)
    response.append(line);

    showResponse(response.toString());
    }catch (Exception e)
    {
    e.printStackTrace();
    }finally {
    if(reader!=null)
    {
    try{
    reader.close();
    }catch (IOException e)
    {
    e.printStackTrace();
    }

    }
    if(connection != null)
    connection.disconnect();
    }

    }
    }).start();
    }

    private void showResponse(final String response)
    {
    runOnUiThread(new Runnable() {
    @Override
    public void run() {
    // 呈现结果到界面上
    responseText.setText(response);
    }
    });
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <Button
    android:id="@+id/send_request"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="send request"/>
    <ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
    android:id="@+id/response_text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
    </ScrollView>
    <!--ScrollView保证可以滚动-->
    </LinearLayout>
  • okhttp实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50

    TextView responseText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Button sendRequest = (Button) findViewById(R.id.send_request);
    responseText = (TextView) findViewById(R.id.response_text);
    sendRequest.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    if(v.getId() == R.id.send_request)
    sendRequestWithOkHttp();
    }

    private void sendRequestWithOkHttp(){
    new Thread(new Runnable() {
    @Override
    public void run() {
    try{
    //使用okhttp库
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder()
    .url("https://www.dogedoge.com")
    .build();
    Response response = client.newCall(request).execute();
    String responeseData = response.body().string();
    showResponse(responeseData);
    }
    catch (Exception e)
    {
    e.printStackTrace();
    }
    }
    }).start();
    }

    private void showResponse(final String response)
    {
    runOnUiThread(new Runnable() {
    @Override
    public void run() {
    // 呈现结果到界面上
    responseText.setText(response);
    }
    });
    }

解析xml

pull解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try{
//使用okhttp实现
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://10.0.2.2:88/get_data.xml")//模拟器访问宿主机本地服务localhost
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithPull(responseData);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}

private void parseXMLWithPull(String xmlData){
try {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
XmlPullParser xmlPullParser = factory.newPullParser();
xmlPullParser.setInput(new StringReader(xmlData));
int eventType = xmlPullParser.getEventType();
String id="";
String name ="";
String version = "";
while(eventType != xmlPullParser.END_DOCUMENT)
{
String nodename = xmlPullParser.getName();
switch (eventType)
{
case XmlPullParser.START_TAG:
{
if("id".equals(nodename))
id = xmlPullParser.nextText();
else if ("name".equals(nodename))
name = xmlPullParser.nextText();
else if("version".equals(nodename))
version = xmlPullParser.nextText();
break;
}
case XmlPullParser.END_TAG:
{
if("app".equals(nodename))
{
Log.d("MainActivity", "id is"+id);
Log.d("MainActivity", "name is"+name);
Log.d("MainActivity", "version is"+version);
}
break;
}
default:
break;
}
eventType = xmlPullParser.next();
}
}catch (Exception e)
{
e.printStackTrace();
}
}

sax解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//MainActivity
private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://10.0.2.2:88/get_data.xml")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseXMLWithSAX(responseData);
// parseXMLWithPull(responseData);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}

private void parseXMLWithSAX(String xmlData)
{
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
XMLReader xmlReader = factory.newSAXParser().getXMLReader();
ContentHandler handler= new ContentHandler();
//将ContentHandler的实例设置到XMLReader中
xmlReader.setContentHandler(handler);
//开始执行解析
xmlReader.parse(new InputSource(new StringReader(xmlData)));
}catch (Exception e)
{
e.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//ContentHandler
public class ContentHandler extends DefaultHandler {
private String nodeName;
private StringBuilder id;
private StringBuilder name;
private StringBuilder version;

@Override
public void characters(char[] ch, int start, int length) throws SAXException {
//根据当前结点将内容添加到哪一个StringBuilder对象中
if("id".equals(nodeName))
id.append(ch,start,length);
else if("name".equals(nodeName))
name.append(ch,start,length);
else if("version".equals(nodeName))
version.append(ch, start, length);
}

@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 记录当前结点名
nodeName = localName;
}

@Override
public void endDocument() throws SAXException {
super.endDocument();
}

@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if("app".equals(localName))
{
Log.d("ContentHandler", "id is "+id.toString().trim() );
Log.d("ContentHandler", "name is "+name.toString().trim() );
Log.d("ContentHandler", "version is "+version.toString().trim() );
//清空StringBuilder
id.setLength(0);
name.setLength(0);
version.setLength(0);
}
}

@Override
public void startDocument() throws SAXException {
id = new StringBuilder();
name = new StringBuilder();
version = new StringBuilder();
}
}

解析json

JSONobject解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://10.0.2.2:88/get_data.json")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithJSONObject(responseData);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}

private void parseJSONWithJSONObject(String jsonData)
{
try{
JSONArray jsonArray = new JSONArray(jsonData);
for(int i=0;i<jsonArray.length();i++)
{
JSONObject jsonObject = jsonArray.getJSONObject(i);
String id = jsonObject.getString("id");
String name = jsonObject.getString("name");
String version = jsonObject.getString("version");
Log.d("MainActivity", "id is"+id);
Log.d("MainActivity", "name is"+name);
Log.d("MainActivity", "version is"+version);
}
}catch (Exception e)
{e.printStackTrace();}
}

GSON解析

1
implementation 'com.google.code.gson:gson:2.8.6'

新建一个App类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example.networktest;

public class App {

private StringBuilder id;
private StringBuilder name;
private StringBuilder version;

public void setId(StringBuilder id) {
this.id = id;
}

public void setName(StringBuilder name) {
this.name = name;
}

public void setVersion(StringBuilder version) {
this.version = version;
}

public StringBuilder getId() {
return id;
}

public StringBuilder getName() {
return name;
}

public StringBuilder getVersion() {
return version;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
   

private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://10.0.2.2:88/get_data.json")
.build();
Response response = client.newCall(request).execute();
String responseData = response.body().string();
parseJSONWithGSON(responseData);
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}


private void parseJSONWithGSON(String jsonData){
Gson gson = new Gson();
List <App> appList = gson.fromJson(jsonData,new TypeToken<List<App>>(){}.getType());
for(App app: appList)
{
Log.d("MainActivity", "id is"+app.getId());
Log.d("MainActivity", "name is"+app.getName());
Log.d("MainActivity", "version is"+app.getVersion());
}
}

封装网络请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override
public void onClick(View v) {
if(v.getId() == R.id.send_request)
sendRequestWithOkHttp();
}


private void sendRequestWithOkHttp(){
new Thread(new Runnable() {
@Override
public void run() {
try{
String address = "http://www.baidu.com";
Httputil.sendOkHttpRequest(address,new Callback()
{
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
// 异常处理情况
}

@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
try {

//得到服务器的返回的具体内容
String responseData = response.body().string();
Log.d("Main", responseData);
}
catch (Exception e1)
{
e1.printStackTrace();
}
}
});
}
catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}

服务

例子,子线程更改ui,安卓多线程

android在子线程中不能直接对ui进行操作,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private TextView text;
public static final int UPDATE_TEXT = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView) findViewById(R.id.text);
Button changeText = (Button) findViewById(R.id.change_text);
changeText.setOnClickListener(this);
}


private Handler handler = new Handler()
{
// @SuppressLint("HandlerLeak")
public void handleMessage(Message msg)
{
switch (msg.what){
case UPDATE_TEXT:
text.setText("nice to meet you");
break;
default:
break;
}
}
};


@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
// 不能这么干,需新建一个Message用于传值 text.setText("nice to meet you");
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default:
break;
}
}
}

异步消息处理机制

  1. Message

    Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message的what 字段,除此之外还可以使用arg1arg2 字段来携带一些整型数据,使用obj 字段携带一个Object 对象。

  2. Handler

    Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage() 方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage() 方法中。

  3. MessageQueue

    MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue 对象。

  4. Looper

    Looper是每个线程中的MessageQueue的管家,调用Looper的loop() 方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage() 方法中。每个线程中也只会有一个Looper 对象。

了解了Message、Handler、MessageQueue以及Looper的基本概念后,我们再来把异步消息处理的整个流程梳理一遍。首先需要在主线程当中创建一个Handler 对象,并重写handleMessage() 方法。然后当子线程中需要进行UI操作时,就创建一个Message 对象,并通过Handler将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage() 方法中。由于Handler是在主线程中创建的,所以此时handleMessage() 方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。整个异步消息处理机制的流程示意图如图

启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//定义两个id为stop_service 和start_service 的按钮,MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startService = (Button)findViewById(R.id.start_service);
Button stopService = (Button) findViewById(R.id.stop_service);
startService.setOnClickListener(this);
stopService.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId())
{
case R.id.start_service:
Intent startIntent = new Intent(this,MyService.class);//构造intent启动服务
startService(startIntent);
break;
case R.id.stop_service:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
default:
break;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//右键新建一个服务,命名为MyService 
public class MyService extends Service {
public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}

@Override
public void onCreate() {
//仅在第一次启动时运行
super.onCreate();
Log.d("MyService", "onCreate: ");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//每次点击开始服务按钮都会运行
Log.d("MyService", "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
//销毁服务时运行
super.onDestroy();
Log.d("MyService", "onDestroy: ");
}
}

活动服务通信绑定(bind)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//MyService 添加一个类
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder
{
public void startDownload(){
Log.d("MyService", "startDownload: ");
}
public int getProgress(){
Log.d("MyService", "getProgress: ");
return 0;
}

}


//重载绝对方法IBinder
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//MainActivity
private MyService.DownloadBinder downloadBinder;//MyService 中的类实例
private ServiceConnection connection = new ServiceConnection() {//连接服务
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;//转型成downloadBinder
//调用服务中定义的方法
downloadBinder.startDownload();
downloadBinder.getProgress();
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};


//in OnClick() 添加按钮,按下绑定服务按钮会启动服务并且执行onServiceConnected中的代码
case R.id.bind_service:
Intent bindIntent = new Intent(this, MyService.class);
bindService(bindIntent, connection, BIND_AUTO_CREATE); // 绑定服务
break;
case R.id.unbind_service:
unbindService(connection); // 解绑服务
break;

前台服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//修改MyService代码为


public class MyService extends Service {

//Channel ID 必须保证唯一,修改点
private static final String CHANNEL_ID = "1";
private static final int NOTIFICATION_ID = 1;


/**
*通过通知启动前台服务,修改点
*/
@androidx.annotation.RequiresApi(api = Build.VERSION_CODES.O)
public void setForegroundService()
{
//设定的通知渠道名称
String channelName = "hello";//getString(R.string.channel_name);
//设置通知的重要程度
int importance = NotificationManager.IMPORTANCE_LOW;
//构建通知渠道
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, channelName, importance);
channel.setDescription("test");
//在创建的通知渠道上发送通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID);
builder.setSmallIcon(R.drawable.ic_launcher_background) //设置通知图标
.setContentTitle("notificationTitle")//设置通知标题
.setContentText("notificationContent")//设置通知内容
.setAutoCancel(true) //用户触摸时,自动关闭
.setOngoing(true);//设置处于运行状态
//向系统注册通知渠道,注册后不能改变重要性以及其他通知行为
NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
//将服务置于启动状态 NOTIFICATION_ID指的是创建的通知的ID
startForeground(NOTIFICATION_ID,builder.build());//两个参数int,Notification,启用前台服务函数,第一个参数是通知id,第二个是通知
}



private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder
{
public void startDownload(){
Log.d("MyService", "startDownload: ");
}
public int getProgress(){
Log.d("MyService", "getProgress: ");
return 0;
}

}

public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate: ");
//调用启动前台函数
setForegroundService();//修改点
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MyService", "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyService", "onDestroy: ");
}
}

1
2
<!--Manifest.xml 别忘了增加前台通知的权限,因为不属于危险权限只需声明,无需运行时权限-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

安卓多线程

服务中的代码都是默认运行在主线程当中的,如果直接在服务里去处理一些耗时的逻辑,就很容易出现ANR(Application Not Responding)的情况。

所以这个时候就需要用到Android多线程编程的技术了,我们应该在服务的每个具体的方法里开启一个子线程,然后在这里去处理那些耗时的逻辑。因此,一个比较标准的服务就可以写成如下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MyService extends Service {

...

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}

}

但是,这种服务一旦启动之后,就会一直处于运行状态,必须调用stopService() 或者stopSelf() 方法才能让服务停止下来。所以,如果想要实现让一个服务在执行完毕后自动停止的功能,就可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyService extends Service {

...

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}

}

使用IntentSevice来管理线程

自动开启和关闭线程

1
2
3
//MainActivity,添加一个case,点击按钮
Intent intentService = new Intent(this,MyIntentService.class);
startService(intentService);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//新建一个类继承自IntentService
public class MyIntentService extends IntentService {
public MyIntentService(){
super("MyIntentService");
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d("MyIntentService", "Thread id is "+Thread.currentThread().getId());
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy executed");
}
}

百度定位SDK

获取经纬度信息,地址描述

导入第三方jar包放在/app/libs目录后需要右键鼠标add as library..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//MainActivity
public LocationClient mLocationClient;

private TextView positionText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 声明LocationClient类
mLocationClient = new LocationClient(getApplicationContext());
// 注册监听函数
mLocationClient.registerLocationListener(new MyLocationListener());
setContentView(R.layout.activity_main);
positionText = (TextView) findViewById(R.id.position_text_view);
List<String> permissionList = new ArrayList<>();
// 申请权限,访问位置
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
// 申请权限,读取手机状态
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.READ_PHONE_STATE);
}
// 申请权限,写存储
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()) {
String [] permissions = permissionList.toArray(new String[permissionList.
size()]);
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
} else {
requestLocation();
}
}


@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "必须同意所有权限才能使用本程序",
Toast.LENGTH_SHORT).show();
finish();
return;
}
}
requestLocation();
} else {
Toast.makeText(this, "发生未知错误", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}

private void initLocation(){
LocationClientOption option = new LocationClientOption();
// 每隔五秒刷新定位
option.setScanSpan(5000);
// 强制使用gps定位
// option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
option.setIsNeedAddress(true);
mLocationClient.setLocOption(option);
}

private void requestLocation() {
initLocation();
// 调用LocationClient的start()方法,便可发起定位请求
mLocationClient.start();
}


// baidu sdk v7.2起的新调用接口,异步实现,这里我改了原作者的代码,因为虽然还能用但百度官方文档推荐使用此方法
public class MyLocationListener extends BDAbstractLocationListener {

@Override
public void onReceiveLocation(BDLocation location) {
runOnUiThread(new Runnable() {
@Override
public void run() {
StringBuilder currentPosition = new StringBuilder();
currentPosition.append("纬度:").append(location.getLatitude()).
append("\n");
currentPosition.append("经线:").append(location.getLongitude()).
append("\n");
currentPosition.append("国家:").append(location.getCountry()).
append("\n");
currentPosition.append("省:").append(location.getProvince()).
append("\n");
currentPosition.append("市:").append(location.getCity()).
append("\n");
currentPosition.append("区:").append(location.getDistrict()).
append("\n");
currentPosition.append("街道:").append(location.getStreet()).
append("\n");
currentPosition.append("定位方式:");
if (location.getLocType() == BDLocation.TypeGpsLocation) {
currentPosition.append("GPS");
} else if (location.getLocType() ==
BDLocation.TypeNetWorkLocation) {
currentPosition.append("网络");
}
positionText.setText(currentPosition);
}
});
}


}

@Override
protected void onDestroy() {
// 每五秒刷新会非常的耗电,所以在程序运行结束后停止定位
super.onDestroy();
mLocationClient.stop();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    
<!--sdk所需权限-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>

<service android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >

</service>

显示百度地图并定位到当前位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

// 百度定位sdk文档 http://lbsyun.baidu.com/index.php?title=android-locsdk/guide/get-location/latlng

public class MainActivity extends AppCompatActivity {


// 百度地图对象
private BaiduMap baiduMap;
// 用于地图上显示当前位置小蓝点的初始化变量
private boolean isFirstLocate = true;
// 显示地图用的变量
private MapView mapView;
// 定位用的变量
public LocationClient mLocationClient;
//  存放定位信息的系统UI TextView
// private TextView positionText;

// 定位到当前位置,并以小蓝点显示
private void navigateTo(BDLocation location)
{
if (isFirstLocate) {
LatLng ll = new LatLng(location.getLatitude(), location.getLongitude());
MapStatusUpdate update = MapStatusUpdateFactory.newLatLng(ll);
baiduMap.animateMapStatus(update);
update = MapStatusUpdateFactory.zoomTo(16f);
baiduMap.animateMapStatus(update);
isFirstLocate = false;
}
MyLocationData.Builder locationBuilder = new MyLocationData.Builder();
locationBuilder.latitude(location.getLatitude());
locationBuilder.longitude(location.getLongitude());
MyLocationData locationData = locationBuilder.build();
baiduMap.setMyLocationData(locationData);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 声明LocationClient类
mLocationClient = new LocationClient(getApplicationContext());
// 注册监听函数
mLocationClient.registerLocationListener(new MyLocationListener());

// positionText = (TextView) findViewById(R.id.position_text_view);

// 显示地图
SDKInitializer.initialize(getApplicationContext());

setContentView(R.layout.activity_main);

mapView = (MapView) findViewById(R.id.bmapView) ;

baiduMap = mapView.getMap();

baiduMap.setMyLocationEnabled(true);

List<String> permissionList = new ArrayList<>();
// 申请权限,访问位置
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
// 申请权限,读取手机状态
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.READ_PHONE_STATE);
}
// 申请权限,写存储
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()) {
String [] permissions = permissionList.toArray(new String[permissionList.
size()]);
ActivityCompat.requestPermissions(MainActivity.this, permissions, 1);
} else {
requestLocation();
}
}

@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}

@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "必须同意所有权限才能使用本程序",
Toast.LENGTH_SHORT).show();
finish();
return;
}
}
requestLocation();
} else {
Toast.makeText(this, "发生未知错误", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}

private void initLocation(){
LocationClientOption option = new LocationClientOption();
// 每隔五秒刷新定位
option.setScanSpan(5000);
// 强制使用gps定位
// option.setLocationMode(LocationClientOption.LocationMode.Device_Sensors);
option.setIsNeedAddress(true);
mLocationClient.setLocOption(option);
}

private void requestLocation() {
initLocation();
// 调用LocationClient的start()方法,便可发起定位请求
mLocationClient.start();
}


// baidu sdk v7.2起的新调用接口,异步实现,这里我改了原作者的代码,因为虽然还能用但百度官方文档推荐使用此方法
public class MyLocationListener extends BDAbstractLocationListener {

@Override
public void onReceiveLocation(BDLocation location) {
// 在地图上显示位置
navigateTo(location);
// runOnUiThread(new Runnable() {
//
// @Override
// public void run() {
// StringBuilder currentPosition = new StringBuilder();
// currentPosition.append("纬度:").append(location.getLatitude()).
// append("\n");
// currentPosition.append("经线:").append(location.getLongitude()).
// append("\n");
// currentPosition.append("国家:").append(location.getCountry()).
// append("\n");
// currentPosition.append("省:").append(location.getProvince()).
// append("\n");
// currentPosition.append("市:").append(location.getCity()).
// append("\n");
// currentPosition.append("区:").append(location.getDistrict()).
// append("\n");
// currentPosition.append("街道:").append(location.getStreet()).
// append("\n");
// currentPosition.append("定位方式:");
// if (location.getLocType() == BDLocation.TypeGpsLocation) {
// currentPosition.append("GPS");
// } else if (location.getLocType() ==
// BDLocation.TypeNetWorkLocation) {
// currentPosition.append("网络");
// }
// 在显示地图的时候一定要注释下一句话,不然会因为找不到textView控件崩溃
// positionText.setText(currentPosition);
// }

// });
}


}

@Override
protected void onDestroy() {
// 每五秒刷新会非常的耗电,所以在程序运行结束后停止定位
super.onDestroy();
mLocationClient.stop();
mapView.onDestroy();
baiduMap.setMyLocationEnabled(false);
}
}

杂项

ctrl + o 快速重载

alt + insert 自动补全get,set方法

adb & fastboot command

常用 ADB 命令

代码 含义
adb reboot bootloader 在 bootloader 模式下重启
adb push 将文件从本地系统复制到 Android 手机的位置
adb pull 将文件从 Android 复制到您的系统
adb devices 显示所有连接的 adb 兼容设备
adb backup 备份 Android 设备
adb install 将应用程序从系统的 apk 文件位置安装到 Android 设备上
adb reboot 在正常模式下重新启动 Android 手机
adb connect 通过 WiFi 网络使用adb命令
adb shell screencap 获取设备的屏幕截图

常用 Fastboot 命令

代码 含义
fastboot devices 显示连接的 Android 设备的序列号
fastboot oem unlock 解开 bootloader 锁(Android 5.0 及以下)
fastboot oem lock 恢复 bootloader 锁(Android 5.0 及以下)
fastboot flashing unlock 解开 bootloader 锁(Android 6.0 及以上)
fastboot flashing lock 恢复 bootloader 锁
fastboot flash recovery (filename) 在 bootloader 模式中向设备刷入文件
fastboot oem device-info 查看bootloader锁

fastboot刷写需要root权限

一般刷机直接根据厂商提供的shell脚本一把梭完事

1
2
3
adb shell pm list packages         //应用程序列表
adb push [文件] /sdcard/Download      //将文件推送到download目录
pm uninstall -k --user 0 [package_name]  //为用户卸载应用程序

网络连接adb

adb connect 设备地址

刷第三方recovery

1
2
#需要root权限
fastboot flash recovery xxx.img
1
2
3
4
5
6
7
8
9
adb backup -f <filename> --twrp <options>
<filename> 是备份数据文件名,如果不填则为默认名称 backup.ab;
<options> 包含下列命令:
--compress: 对备份文件进行压缩
system: 备份根目录下 system 数据
cache: 备份根目录下 cache 数据
data: 备份根目录下 data 数据
boot: 备份根目录下 boot 数据
(其他分区同理)

避免在主线程发送http 请求修改ui

启动一个子线程进行网路请求操作,然后再用Handler更新界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.share_mblog_view);
new Thread(runnable).start(); //启动子线程
}

//handler 处理返回的请求结果
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Bundle data = msg.getData();
String val = data.getString("value");
//
// TODO: 更新界面
//
Log.i("mylog","请求结果-->" + val);
}
}

//新线程进行网络请求
Runnable runnable = new Runnable(){
@Override
public void run() {
//
// TODO: http request.
//
Message msg = new Message();
Bundle data = new Bundle();
data.putString("value","请求结果");
msg.setData(data);
handler.sendMessage(msg);
}
}

android 9以上开启定位服务

1
2
3
4
5
6

Enable:
adb shell settings put secure location_mode 3
Disable:
adb shell settings put secure location_mode 0

1
2
3
4
5
6
#模拟滑动解锁
adb shell input swipe 300 300 500 1000 100
#输入密码
adb shell input text 1234
#或者模拟输入
input keyevent KEYCODE_5

adb shell 调用autojs(root情况下)

am start -n org.autojs.autojs/.external.open.RunIntentActivity -d "file:///storage/emulated/0/脚本/test.js"

autojs 向tasker传值

1
2
3
4
5
6
7
8
9
10
11
//autojs文件test.js
auto
console.show()
app.sendBroadcast({
action: "autojs.intent.action.MAIN",
extras: {
from: "Autoxjs",
version: "5.3.0",
info:"you get it",
},
});

然后tasker中配置文件选择收到的意图,操作填autojs.intent.action.MAIN然后指向一个可以显示变量的控件,通过%from , %version , %info取出