分类 Develop 下的文章

Android中常用的应用都会大量使用网络,比如QQ、微信、访问网页等等。通过使用网络,可以展示网页,发起Http请求获取数据或提交数据。

WebView控件

有时我们可能需要让我们自己的应用展示网页,我们可以使用Android内置的控件WebView

public class MainActivity extends AppCompatActivity {

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

        WebView webView = (WebView)findViewById(R.id.web_view);
        webView.getSettings().setJavaScriptEnabled(true);
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl("http://www.baidu.com");
    }
}

布局文件:

<?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">

    <WebView
        android:id="@+id/web_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

由于使用了网络功能,需要声明网络权限。在AndroidManifest.xml添加android.permission.INTERNET:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jyoryo.app.android.study">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Android主线程、子线程

  • 主线程,不允许在主线程中进行网络请求,如果需要发起网络请求,方法是开启一个线程,然后在子线程里使用HttpURLConnection发起HTTP请求。例如下面使用HttpURLConnection发起HTTP请求。
  • 子线程,不允许在子线程中进行UI操作,如果需要进行UI操作必须切换到主线程中。可以使用runOnUiThread()方法将线程切换到主线程。

使用runOnUiThread()

private void showResponse(final String response) {
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            responseText.setText(response);
        }
    });
}

HttpURLConnection

HttpURLConnection是jdk自带的访问网络方式。

private void sendRequestWithHttpURLConnection(final String strUrl) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "run: start http request");
            HttpURLConnection connection = null;
            BufferedReader reader = null;
            try {
                URL url = new URL(strUrl);
                connection = (HttpURLConnection)url.openConnection();
                connection.setRequestMethod("GET");
                connection.setConnectTimeout(8000);
                connection.setReadTimeout(8000);
                InputStream in = connection.getInputStream();
                reader = new BufferedReader(new InputStreamReader(in));
                StringBuilder sbd = new StringBuilder();
                String line = null;
                while (null != (line = reader.readLine())) {
                    sbd.append(line);
                }
                showResponse(sbd.toString());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    if(null != reader) {
                        reader.close();
                    }
                    if(null != connection) {
                        connection.disconnect();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }).start();
}

如果使用POST方式,比如提交用户名、密码:

connection.setRequestMethod("POST");
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.writeBytes("username=user&password=123");

OkHttp

通过HttpURLConnection可以完成网络请求,但是比较原始,我们可以通过使用OkHttp更便捷的完成网络请求。

private void sendRequestWithOkHttp(final String url) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            OkHttpClient client = new OkHttpClient();
            Request request = new Request.Builder()
                    .url(url)
                    .build();
            try {
                Response response = client.newCall(request).execute();
                String responseData = response.body().string();
                showResponse(responseData);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }).start();
}

网络请求封装

HttpUtil网络请求封装类:

public class HttpUtil {

    public interface HttpCallbackListener {
        void onFinish(String response);
        void onError(Exception e);
    }

    /**
     * HttpURLConnection封装
     * @param address
     * @param listener
     */
    public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                try {
                    URL url = new URL(address);
                    connection = (HttpURLConnection)url.openConnection();
                    connection.setConnectTimeout(30000);
                    connection.setReadTimeout(30000);
                    connection.setDoInput(true);
                    connection.setDoOutput(true);
                    InputStream in = connection.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
                    StringBuilder sbd  = new StringBuilder();
                    String line = null;
                    while (null != (line = reader.readLine())) {
                        sbd.append(line);
                    }
                    // return sbd.toString();
                    if(null != listener) {
                        listener.onFinish(sbd.toString());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    // return e.getMessage();
                    if(null != listener) {
                        listener.onError(e);
                    }
                } finally {
                    if (null != connection) {
                        connection.disconnect();
                    }
                }
            }
        }).start();
    }

    /**
     * OkHttpClient封装
     * @param address
     * @param callback
     */
    public static void sendOkHttpRequest(final String address, Callback callback) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(address).build();
        client.newCall(request).enqueue(callback);
    }
}

HttpUtil使用:

/**
 * HttpURLConnection方式
*/
HttpUtil.sendHttpRequest(url, new HttpUtil.HttpCallbackListener() {
    @Override
    public void onFinish(String response) {
        showResponse(response);
    }
    @Override
    public void onError(Exception e) {
        e.printStackTrace();
    }
});

/**
 * OkHttp方式
*/
HttpUtil.sendOkHttpRequest(url, new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        e.printStackTrace();
    }
    @Override
    public void onResponse(Call call, Response response) throws IOException {
        showResponse(response.body().string());
    }
});

内容提供器Content Provider用于不同程序之间进行数据共享:允许其它程序访问操作提供内容提供器的程序,同时保证提供被访问的数据的安全性。

内容提供器Content Provider一般有两种用法:

  • 使用其他现有提供内容提供器的程序来读取和操作该程序的数据;
  • 自身程序创建内容提供器供其他程序读取和操作自身程序的数据,即对外提供接口。

比如常用的电话簿、短信、媒体库等都提供了内容提供器供其他程序读取和操作数据。

内容提供器Content Provider基本用法

通过Context中的getContentResolver()获取ContentResolver类的实例来访问其他程序的内容提供器的数据,进行相关CRUD操作。相关方法与SQLiteDatabase基本相同,只不过方法参数有些不同。

  • insert():添加数据;
  • update():更新数据;
  • delete():删除数据;
  • query():查询数据。

ContentResolverCRUD操作不接受表名,而是使用Uri参数(内容URI)。内容URI给内容提供器提供了唯一标志符,它是有两部分组成:authoritypath

  • authority:用于区分应用程序,一般为了更好的区分,都会采用程序包名再加上provider来命名。比如程序的包名com.demo.app,那么该程序的authority可以命名为com.demo.app.provider
  • path:用于区分同一程序中不同表,通常都会添加在authority后面。比如程序存在2张表table1table2,对应的path就可以分别命名为/table1/table2
  • 内容URI:是将上面的authoritypath进行组合,然后再在头部加上协议声明。比如content://com.demo.app.provider/table1content://com.demo.app.provider/table2

- 阅读剩余部分 -

Android自发布以来就已经存在权限,但是之前版本(Android 6.0以前)对用户安全和隐私数据保护是很有限的,特别是常用离不开的App,很容易造成用户数据泄露。为了解决这个问题,Android6.0开始引入运行时权限

Android权限机制

Android中如果要访问网络、监听开机广播、监听来电或者短信息、监听网络变化等等,就必须在AndroidManifest.xml文件中添加权限声明,比如:

<!-- 网络状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 系统开机 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

运行时权限:用户在安装软件的时候可以不用一次性授权所有权限,而是在软件的实际使用过程中再逐一对某一权限进行申请授权。
Android的权限可以归为2类:普通权限、危险权限。

普通权限:不会直接威胁到用户安全和隐私的权限,对于这部分权限,系统会自动帮我们进行授权,而不用用户进行手动全操作授权;

危险权限:是指会获取用户敏感数据或对设备安全性造成影响的权限,如联系人、定位位置、拨打电话等,对于这部分权限,必须由用户进行手动点击进行授权,否则程序无法使用该权限对应的功能。

危险权限一共是9组24个权限,9组分别为日历、拍照、联系人、位置、麦克风、电话、传感器、短信及存储:

- 阅读剩余部分 -

Android中针对数据的持久化存储主要提供了3种:文件存储SharedPreference存储SQLite数据库存储。除了上面3种外,也可也将数据保存在设备的SD卡中,不过使用这3种相对更简单易用。

文件存储

Android中对文件存储的内容不进行任何格式化处理,所有的数据都是原样保存至文件中,因此比较适合存储一些文本内容或二进制数据。
存储的文件默认都是存储在路径:/data/data/{package_name}/files这个目录下,{package_name}为程序的包名。

将数据保存至文件

Context类中提供openFileOutput()方法,获取向文件写入数据的输出流。方法需要2个参数:第一个参数是文件名;第二个参数是写入模式,主要有2种(MODE_PRIVATEMODE_APPEND)。
MODE_PRIVATE:是默认的写入模式,该模式下,如果存在同名的时候,会将写入的内容完全覆盖源文件的内容。
MODE_APPEND:如果已存在该文件,就往该文件追加写入数据;如果文件不存在,则新建该文件。

    private void save(String content) {
        FileOutputStream out = null;
        BufferedWriter writer = null;
        try {
            out = openFileOutput("data", Context.MODE_PRIVATE);
            writer = new BufferedWriter(new OutputStreamWriter(out));
            writer.write(content);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(null != writer) {
                    writer.close();
                }
                if(null != out) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

从文件中读取数据

Context类中提供openFileInput()方法,获取从文件读取数据的输入流。

    private String load() {
        FileInputStream in = null;
        BufferedReader reader = null;
        StringBuilder sbd = new StringBuilder();
        try {
            in = openFileInput("data");
            reader = new BufferedReader(new InputStreamReader(in));
            String line = "";
            while (null != (line = reader.readLine())) {
                sbd.append(line);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(null != reader) {
                    reader.close();
                }
                if(null != in) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sbd.toString();
    }

- 阅读剩余部分 -

在上篇文章中对四大组件之Activity进行进行总结,这篇文章是对另一个四大组件Broadcast 广播进行总结。

广播类型

广播可以分为两种类型:

  • Normal broadcasts 标准广播:完全异步的,广播发出后,所有的广播接收器几乎都会在同一时刻收到这条广播消息。它的特点是,广播效率高,但是无法被截断。
    NormalBroadcasts.jpg
  • Ordered broadcasts 有序广播:同步执行的,广播发出后,同一时刻只能有一个广播接收器能够收到这条广播消息,只有当该广播接收器的逻辑处理完成后,广播才会继续传递给下一个广播接收器。优先级越高的广播接收器会优先接收到广播消息,另外广播接收器还可以对广播进行截断,这样后面的广播接收器就无法收到该广播消息。
    OrderBroadcasts.jpg

- 阅读剩余部分 -

Fragment碎片是可嵌入到活动中的UI片段,能让程序更加合理利用大屏幕。

Fragment基本用法

在一个活动中添加2个Fragment,这两个Fragment平方活动空间。
1、创建左侧的Fragment布局left_fragment.xml

<?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/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button"/>
</LinearLayout>

创建右侧的Fragment布局right_fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#00ff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="This is right fragment."/>
</LinearLayout>

2、创建Fragment类
左侧Fragment类LeftFragment:

public class LeftFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.left_fragment, container, false);
        return view;
    }
}

右侧Fragment类RightFragment:

public class RightFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.right_fragment, container, false);
        return view;
    }
}

3、在活动布局中添加Fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/left_fragment"
        android:name="com.jyoryo.app.android.study.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>

    <fragment
        android:id="@+id/right_fragment"
        android:name="com.jyoryo.app.android.study.RightFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"/>
</LinearLayout>

- 阅读剩余部分 -

通过Android Studio可视化编辑器可以通过拖拽方式完成界面布局,但这种方式生成的界面通常不具有很好的屏幕适配性,而且编写较为复杂界面时,通过可视化编辑器实现效果也不好。建议新手还是通过xml方式编写界面。通过Android Studio强大的智能提示,使用xml方式编写界面也是很方便的。

Android常用控件

TextView

TextView应该是最常用的一个控件,用来显示一段文本内容。简单的TextView用法如下:

    <TextView
        android:id="@+id/text_view_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a TextView1!" />

    <TextView
        android:id="@+id/text_view_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="This is a TextView2!" />

    <TextView
        android:id="@+id/text_view_3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textSize="24sp"
        android:textColor="#00ff00"
        android:text="This is a TextView3!" />
  • android:id:给控件定义一个唯一标识符;
  • android:layout_widthandroid:layout_height:定义控件宽度和高度,可选择有wrap_contentmatch_parent,和fill_parent,一般用wrap_contentmatch_parent
  • android:text:定义控件显示的文本内容;
  • android:gravity:指定控件文字的对齐方式,可选择有topbottomleftrightcentercenter_horizontalcenter_vertical等,可以用管道符"|"来同时指定多个值;
  • android:textSize:定义文字的大小;
  • android:textColor:定义文字颜色。

- 阅读剩余部分 -

Android四大组件:Activity(活动)Service(服务)Broadcast Receiver(广播接收器)Content Provider(内容提供器)

  • Activity(活动)
    Activity活动是所有Android应用的界面显示,凡是应用中能看到的内容,都是放在活动中。
  • Service(服务)
    Service服务无法看到,但是它会一直在后台默默地运行。
  • Broadcast Receiver(广播接收器)
    Broadcast Receiver广播接收器运行应用接收来自各处的广播消息,比如:电话、短信等。我们应用也可以自己定义广播消息对外广播发送。
  • Content Provider(内容提供器)
    Content Provider内容提供器为应用之间共享数据提供了可能。比如访问系统电话簿中的联系人,就需要通过内容提供器来实现。

本文是对Activity(活动)的总结。

活动的创建

  1. 创建活动Activity的类。例如:

    public class MainActivity extends AppCompatActivity {
       private static final String TAG = "MainActivity";
       
       @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           setContentView(R.layout.activity_main);
       }
    }
  2. 创建活动的布局(视图)

    <?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">
    
       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:text="This is a activity!" />
    </LinearLayout>
  3. AndroidManifest.xml中进行注册。

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.jyoryo.app.android.study">
    
       <application
           android:allowBackup="true"
           android:icon="@mipmap/ic_launcher"
           android:label="@string/app_name"
           android:roundIcon="@mipmap/ic_launcher_round"
           android:supportsRtl="true"
           android:theme="@style/AppTheme">
           <activity android:name=".MainActivity">
               <intent-filter>
                   <action android:name="android.intent.action.MAIN" />
                   <category android:name="android.intent.category.LAUNCHER" />
               </intent-filter>
           </activity>
       </application>
    
    </manifest>

其中标签activity ,就是申明注册Activity。android:name,用来指定具体注册的活动。比如上面实例中.MainActivity,标识活动的类名com.jyoryo.app.android.study.MainActivity。由于标签manifest中已经通过package申明了包名,因此注册活动时,包名这一部分就可以省略了。

- 阅读剩余部分 -

通过Android Studio创建的Android项目,都有固定的目录结构。

一、项目根目录结构及说明

如下图所示:
AndroidProjectStructure.jpg

  1. .gradle.idea
    这两个目录下都是Android Studio自动生成的一些文件,不用关心,当然也不要去手动编辑。
  2. app
    项目代码、资源等内容几乎都是在这个目录下,我们的开发也基本都是在这个目录下进行。我们会在本会的第二部分还会对该目录下的内容单独说明。
  3. gradle
    这个目录下包含了gradle wrapper的配置文件,使用gradle wrapper的方式不需要提前将gradle下载好,而是自动根据本地缓存情况来确定是否需要联网下载gradle。Android Studio默认没有启用gradle wrapper的方式,如果需要打开,可以点击Android Studio导航栏→File→Settings→Build,Execution,Deployment→Gradle,进行配置修改。
  4. .gitignore
    这个文件是用来指定目录或文件排除在git版本控制之外的。
  5. build.gradle
    项目全局的gradle的构建脚本,通常该文件的内容不需要修改。
  6. gradle.properties
    项目全局的gradle配置文件,在这里配置的属性将会影响到项目中所有gradle编译脚本。
  7. gradlewgradlew.bat
    这两个文件是用在命令行界面中执行gradle命令的。(gradlew:是LinuxMac系统中使用;gradlew.bat:是Windows系统中使用的。)
  8. local.properties
    用于指定本机中的Android SDK路径,通过内容都是自动生成的,我们不需要修改。除非本机中的Android SDK位置发生了变化,可以在这个文件中的路径改为新的位置即可。
  9. settings.gradle
    这个文件指定项目中所有引入的模块。默认新创建的项目只有一个模块,一次该文件仅引入了app这个模块。通常情况下模块引入都是自动完成的,不需要我们手动去修改这个文件。

二、项目模块目录结构及说明

如下图示:
AndroidModuleStructure.jpg

  1. build
    这个目录主要包含了一些在编译时自动生成的文件,一般我们不要关心。
  2. libs
    如果项目中需要使用第三方jar包,就需要将这些jar包都放在这个目录。放在这个目录下的jar包都会被自动添加到构建路径里。
  3. src/androidTest
    用来编写AndroidTest测试用例的,可以对项目进行一些自动化测试。
  4. src/main/java
    这个目录是放置我们所有Java代码的地方。
  5. src/main/res
    这个目录下的内容比较多。项目中用到的所有图片、布局、字符串等资源都放在这个目录下。这个目录下还有很多子目录,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下。
  6. src/main/AndroidManifest.xml
    整个Android项目的配置文件,程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用添加权限申明。
  7. src/test
    该目录用来编写Unit Test测试用例的。
  8. gitignore
    这个文件用于将app模块内指定的目录或文件排除在版本控制之外,作用域上层目录的.gitignore类似。
  9. app.iml
    IDE自动生成的文件。
  10. build.gradle
    是app模块的gradle构建脚本,这个文件会指定很多项目构建相关的配置。
  11. proguard-rules.pro
    这个文件用于指定项目代码的混淆规则。如果不希望代码被别人破解,通常会将代码进行混淆。

在项目开发时,我们用的是本地搭建的开发dev环境,开发完成打包部署到服务器时,用到的是服务器prod环境。可以借用Mavenprofilesfiltersresources,在运行或打包时指定选用的环境,实现不同环境自动使用各自环境的配置文件或配置信息。

  • profiles:定义环境变量的id;
  • filters:定义了变量配置文件的地址,其中地址中的环境变量就是上面profile中定义的值;
  • resources:定义哪些目录下的文件会被配置文件中定义的变量替换,另外可以指定目录下的文件打包到classes目录下。

定义环境变量profiles

一般环境变量分:dev开发环境、prod发布环境,当然也可以类比添加其他的环境标志。
此处详细可参看:maven profile动态选择配置文件maven profile切换正式环境和测试环境

    <profiles>
        <!-- 开发测试环境 -->
        <profile>
            <id>dev</id>
            <activation>
                <!-- 设置默认激活dev环境的配置 -->
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <profile.env>dev</profile.env>
            </properties>
        </profile>
        <!-- 产品发布环境 -->
        <profile>
            <id>prod</id>
            <properties>
                <profile.env>prod</profile.env>
            </properties>
        </profile>
    </profiles>

- 阅读剩余部分 -