目前分類:Android-勞動部課程 (54)

瀏覽方式: 標題列表 簡短摘要

●程式說明

(一)DAO與SQLite的教學可參考此篇文章
 

(二)MemoryDAO Implement (關掉app就沒了,所以沒有實用性,這邊只是知道該怎麼做)
public class CusomerDAOMemoryImpl implements CustomerDAO {
    ArrayList<customer> datalist=new ArrayList<>();
    int id=1;
    @Override
    public void addOne(customer c) {
        //因為不是寫進資料庫,所以id不會自動給定,要自己寫入
        c.id this.id;
        this.id++;
        datalist.add(c);
    }
 
    @Override
    public customer getOne(int id) {
        //因為可能會找不到資料,所以會是null
        customer rtValue = null;
        //跑回圈找id對的回傳
        for (customer tmp : datalist)
        {
            if (tmp.id == id)
            {
                rtValue = tmp;
                break;
            }
        }
        return rtValue;
    }
 
    @Override
    public void clearAll() {
        datalist.clear();
    }
 
    @Override
    public customer[] getList() {
        return datalist.toArray(new customer[datalist.size()]);
    }
 
    @Override
    public void delete(customer c) {
        datalist.remove(c);
    }
 
    @Override
    public void update(customer c) {
        for (customer tmp : datalist)
        {
            if (tmp.id == c.id)
            {
                tmp.name = c.name;
                tmp.addr = c.addr;
                tmp.tel = c.tel;
                break;
            }
        }
    }
}
 
**寫測試程式,因為MemoryDAO和手機無關,可以直接寫一般的測試程式,不用寫androidTest
public class MyMemorDAOTest {
 
@Test
public void clearAndAddOneDataAndGetTest() {
CusomerDAOMemoryImpl dao = new CusomerDAOMemoryImpl();
customer c = new customer();
c.name = "BBB";
c.tel = "123";
c.addr = "aabb";
dao.clearAll();
dao.addOne(c);
customer cArray[] = dao.getList();
assertEquals("BBB", String.valueOf(cArray[0].name));
 
}
 
@Test
//測試刪除
public void testDelete1() {
CusomerDAOMemoryImpl dao = new CusomerDAOMemoryImpl();
customer c1 = new customer("ccc", "333", "123456");
customer c2 = new customer("ddd", "444", "123456");
dao.clearAll();
dao.addOne(c1);
dao.addOne(c2);
customer cArray[] = dao.getList();
//因為前面new c1時沒有給id,所以這邊設定idc1
c1.id = cArray[0].id;
dao.delete(c1);
customer cArray2[] = dao.getList();
assertEquals("ddd", cArray2[0].name);
}
@Test
//測試更新
public void testUpdate() {
CusomerDAOMemoryImpl dao = new CusomerDAOMemoryImpl();
customer c1 = new customer("ccc", "333", "123456");
 
dao.clearAll();
dao.addOne(c1);
customer carray[] = dao.getList();
c1.id = carray[0].id;
c1.name = "fff";
dao.update(c1);
customer carray2[] = dao.getList();
assertEquals("fff", carray2[0].name);
}
}
 
(三)寫到app中要大改,不能直接用舊的程式,因為memory每new一次就會是一個新的,這樣會找不到資料
(1)新增一個DAOApplication並修改mainfeast
android:name=".DAOApplication"
public class DAOApplication extends Application {
public CustomerDAO dao = new CusomerDAOMemoryImpl();
}
 
 
(2)新增一個DAOFactory
public class CustomerDAOFactory {
public static CustomerDAO getDAO(Context context, DAOType dt)
{
//因為傳進來的是ACTIVITY,要把CONTEXT轉成ACTIVITY
Activity act = (Activity) context;
DAOApplication app = (DAOApplication) act.getApplication();
CustomerDAO dao = null;
switch(dt)
{
case Memory:
dao = app.dao;
break;
case DB:
dao = new CustomerDAODBImpl(context);
break;
}
return dao;
}
}
 
 
(3)新增enum DAOType
//列舉DAO類型
public enum DAOType {
Memory,DB
}
 
 
 
(4)修改舊程式有new CustomerDAO之處
mainactivity改
 
1.在最外面宣告DAOType
//只要控制MainActivity DAOType,就可ˇ決定要存到記憶體還是資料庫
public static DAOType dt = DAOType.Memory;
 
2.
super.onResume();
CustomerDAO dao=  CustomerDAOFactory.getDAO(MainActivity.this, MainActivity.dt);
customer[] cust = dao.getList();
...
sec改
CustomerDAO cdd=  CustomerDAOFactory.getDAO(sec.this, MainActivity.dt);
customer c=new customer();
...
 
editinfo改
1.
int index=Integer.parseInt(i);
CustomerDAO dao= CustomerDAOFactory.getDAO(editeInfo.this, MainActivity.dt);
...
2.
public void delete(View view) {
    CustomerDAO dao=  CustomerDAOFactory.getDAO(editeInfo.this, MainActivity.dt);
...
3.
public void update(View view) {
    CustomerDAO dao=  CustomerDAOFactory.getDAO(editeInfo.this, MainActivity.dt);
...
 
(四)寫到json中(資料會寫在檔案中)
(1)Enum要新增File,並且把mainActivity的DAOType改為File
 
(2)新增一個class
public class CustomerDAOFileImpl implements CustomerDAO {
ArrayList<customer> datalist = new ArrayList();
String File_Name = null;
 
//要建constructor
public CustomerDAOFileImpl(Context context)
{
//因為filename在建構式中不能直接getFileDir(),因為這是我們自己寫的類別,所以要先取得activity
Activity act = (Activity) context;
File_Name = act.getFilesDir().getAbsolutePath() + File.separator + "mydata.json";
FileReader fr = null;
try {
//要確保檔案存在,檢查檔案有存在才能讀取
File f = new File(File_Name);
if (f.exists())
{
fr = new FileReader(File_Name);
BufferedReader br = new BufferedReader(fr);
String str = br.readLine();
//因為讀進來的是json檔,所以可以用gson
Gson gson = new Gson();
datalist = gson.fromJson(str, new TypeToken<ArrayList<customer>>(){}.getType());
br.close();
fr.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//要新增一個寫入檔案的程式,只要有做修改最後都要做save to file的動作
private void saveFile()
{
Gson gson = new Gson();
String str = gson.toJson(datalist);
FileWriter fw = null;
try {
fw = new FileWriter(File_Name);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(str);
bw.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
 
@Override
public void addOne(customer c) {
datalist.add(c);
saveFile();
}
 
@Override //getOne因為沒有做修改所以不需要save to file做法就跟memory一樣
public customer getOne(int id) {
customer rtValue = null;
for (customer tmp : datalist)
{
if (tmp.id == id)
{
rtValue = tmp;
break;
}
}
return rtValue;
}
 
@Override
public void clearAll() {
datalist.clear();
saveFile();
}
 
@Override
public customer[] getList() {
return datalist.toArray(new customer[datalist.size()]);
}
 
@Override
public void delete(customer c) {
datalist.remove(c);
saveFile();
}
 
@Override
public void update(customer c) {
for (customer tmp : datalist)
{
if (tmp.id == c.id)
{
tmp.name = c.name;
tmp.addr = c.addr;
tmp.tel = c.tel;
break;
}
}
saveFile();
}
}
 

●程式參考(GitHub):透過Enum選擇Data Storage的方式(SQLite, FileWriter, Memory )

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●程式說明--Android Unit Test

1.androidTest:要開android設備才可以測的=>要用到android元件(可以用模擬器也可以用實機測試)
2.test: 不用開android設備就可以測的 =>純java程式
 
(一)test
*建一個MyMath class
//建一個簡單的class做測試內容
public class MyMath {
    public static int add(int x, int y) {
        return x + y;
    }
}
 
*建一個Test1 class
public class Test1 {
    //一定要加@Test,說明是在測試
    @Test
    public void myTest()
    {
        //assert如果是false,就會中斷
        //assertEquals,當兩個參數的值相等就代表測試成功
        assertEquals(10, MyMath.add(3,7));
    }
 
}
 
=>測試,到檔案按右鍵=>run
android unit test / TestCase
 
=>測試成功會是綠色的
android unit test / TestCase
 
=>測試失敗是紅色的
android unit test / TestCase
 
=>在整個測試檔上選整個專案測試,就會每個測試程式都run
android unit test / TestCase
 
(二)androidTest
=>測試新增的資料是否正確
//一定要加@RunWith,代表測試
@RunWith(AndroidJUnit4.class)
public class MyDAOTest1 {
    @Test
    public void AddandGetTest(){
        //取得context就可以建立phoneDAODBImpl物件
        Context appContext = InstrumentationRegistry.getTargetContext();
        phoneDAODBImpl dao = new phoneDAODBImpl(appContext);
        phone p = new phone();
        p.name "BBB";
        p.tel "123";
        p.addr "aabb";
        dao.addOne(p);
        phone p1 = dao.getOne(1);
        assertEquals("AA",p1.name);
    }
}
 
=>測試失敗的畫面
android unit test / TestCase
 
=>預期結果是AA但是真實結果為BBB
android unit test / TestCase
 
=>測試成功的畫面
android unit test / TestCase
 
 
//清除資料表以後重建一筆測試
@Test
public void clearAndAddOneDataAndGetTest()
{
    Context appContext = InstrumentationRegistry.getTargetContext();
    phoneDAODBImpl dao = new phoneDAODBImpl(appContext);
    phone p = new phone();
    p.name "BBB";
    p.tel "123";
    p.addr "aabb";
    //清除資料後再新增一筆測試,確保資料庫正確
    dao.clearAll();
    dao.addOne(p);
    //取得資料庫的資料放到陣列中
    phone pArray[] = dao.getList();
    //取得第一筆資料中的name來測試
    assertEquals("BBB", pArray[0].name);
 
}
 
//測試刪除
@Test
public void testDelete1(){
Context appContext = InstrumentationRegistry.getTargetContext();
phoneDAODBImpl dao = new phoneDAODBImpl(appContext);
phone p1=new phone("ccc","333","123456");
phone p2=new phone("ddd","444","123456");
dao.clearAll();
dao.addOne(p1);
dao.addOne(p2);
phone pArray[]=dao.getList();
//因為前面new c1時沒有給id,所以這邊設定idc1
p1.id=pArray[0].id;
dao.delete(p1);
phone pArray2[]=dao.getList();
assertEquals("ddd",pArray2[0].name);
}
 
//測試更新
@Test
public void testUpdate(){
Context appContext = InstrumentationRegistry.getTargetContext();
phoneDAODBImpl dao = new phoneDAODBImpl(appContext);
phone p1=new phone("ccc","333","123456");
 
dao.clearAll();
dao.addOne(p1);
phone parray[]=dao.getList();
p1.id=parray[0].id;
p1.name="fff";
dao.update(p1);
phone parray2[]=dao.getList();
assertEquals("fff", parray2[0].name);
}

 

 

●程式參考(GitHub):Android Unit Test單元測試 (測試程式與DAO和SQLite的程式放在一起)

 

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●程式說明--用程式建立SQLite資料庫
=>要先新增一個class繼承SQLiteOpenHelper
SQLite and DAO
 
=>SQLiteOpenHelper是一個抽象類別,所以要override兩個方法
=>還要加建構式,因為SQLiteOpenHelper這個父類別本身只有有參數的建構式,所以一定要加,不然當父類別的參數要使用到時就會有問題
=>加建構式時,選第一個
SQLite and DAO
 
=>新增欄位在add column
SQLite and DAO
 
=>複製新增欄位的語法,按下add column按鈕就會跳出對話框,語法就在對話框上
SQLite and DAO
 
(一)MyHelper.class
package com.example.student.lu101801;
 
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
 
 
public class MyHelper extends SQLiteOpenHelper {
//本來建構式繼承有四個參數,但factory用不到就直接設定null
//資料庫名稱和版本則會直接設定final然後把這三個參數丟到super方法中
//這樣mainactivity要建立MyHelper物件時,就只要丟context這個參數進來就可以
static final String DB_NAME="student.sqlite";
static final int VERSION=4;
 
public MyHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
 
@Override //如果系統中沒有這個資料庫,就會跑onCreate
public void onCreate(SQLiteDatabase sqLiteDatabase) {
//先去SQLite Manager複製create語法,這邊有個技巧事先打好雙引號再貼上語法
//這樣會自動把需要加上反斜線的內容都加好反斜線
//在做版本更新時,除了onUpgrade要加sql語法,onCreate也要加,因為可能有用戶是沒有舊版就直接安裝新版的
String createSQL="CREATE TABLE \"phone\" (\"id\" INTEGER PRIMARY KEY NOT NULL ,\"name\" VARCHAR,\"tel\" VARCHAR,\"addr\" VARCHAR DEFAULT (null) , \"email\" VARCHAR, \"tel2\" VARCHAR)";
//sqLiteDatabase是從參數帶入的SQLiteDatabase
sqLiteDatabase.execSQL(createSQL);
}
 
@Override //如果系統中有這個資料庫且版本不同,就會跑onUpgrade
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldversion, int newversion) {
Log.d("verson","verson:"+oldversion);
//因為new這個物件時會帶入版本參數,這邊就要做版本的判斷來決定要做甚麼
if (oldversion != 4 )
{
String upgradeSQL = "ALTER TABLE \"main\".\"phone\" ADD COLUMN \"tel2\" VARCHAR";
sqLiteDatabase.execSQL(upgradeSQL);
}
}
}
 
 
 
(二)phone.class
package com.example.student.lu101801;
//注意不要把public寫成static,會把屬性值改掉然後指記憶最後一筆資料
//建一個物件用來存放資料庫的資料
public class phone {
public int id;
public String name;
public String tel;
public String addr;
public String email;
public String tel2;
 
}
 
(三)MainActivity.class
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
 
    public void click1(View view) {
        phone p=new  phone();
        p.name="kent";
        p.tel="12345678";
        p.tel2="87654321";
        p.addr="abceddddd";
        p.email="test2@eamil";
 
        add(p);
        phone p1=getOne(5);
        Log.d("p1","p1"+p1.name);
    }
    //新增資料的method
    public void add(phone p){
        //要透過建立MyHelper這個步驟來新增資料庫(沒有就onCreate/有就直接使用/要更新版本就onUpgrade)
        //這邊在宣告MyHelper的時候就run onCreate
        MyHelper helper=new MyHelper(MainActivity.this);
        //呼叫getWritableDatabase()時,就會去執行onCreate中的execSQL();
        SQLiteDatabase db=helper.getWritableDatabase();
 
        ContentValues cv = new ContentValues();
        //寫入時,每個欄位資料都去對應phone 物件的各個field
        cv.put("name",p.name);
        cv.put("tel",p.tel);
        cv.put("addr",p.addr);
        cv.put("email",p.email);
        cv.put("tel2",p.tel2);
        db.insert("phone"null, cv);
 
        db.close();
    }
 
    //讀取資料的method(會傳回phone物件)
    public phone getOne(int id){
        phone p=new  phone();
        MyHelper helper=new MyHelper(MainActivity.this);
        SQLiteDatabase db=helper.getWritableDatabase();
        //因為會帶入一個int id的參數,這邊第三第四個參數也要設定,先在第三個參數設定要如何篩選資料
        // 第四個參數是把丟進來的int id轉成字串陣列的第一筆資料再丟給id(參數3)去篩選,這樣就會找到那一筆id的資料
        Cursor c = db.query("phone",new String[] {"id","name","tel",
        "addr","email","tel2"}, "id=?"
        new String[] {String.valueOf(id)}, nullnullnull);
        c.moveToFirst();
        //設定在讀取時,phone class的每個field會去抓哪個欄位的資料
        p.id=c.getInt(0);
        p.name = c.getString(1);
        p.tel = c.getString(2);
        p.addr = c.getString(3);
        p.email=c.getString(4);
        p.tel2=c.getString(5);
        db.close();
        return p;
    }
}
**說明:
這種利用物件來進行資料庫的操作稱為ORM,而資料庫的處理和前面程式的處理分開稱為DAO
 
●DAO pattern(Design pattern)=>部分程式比照上方
(一)phoneDAO.class
//先用一個DAO定義資料庫有哪些操作(interface),再實作填寫真正的操作內容
public interface phoneDAO {
    public void  addOne(phone p);
    public phone getOne(int id);
    public void clearAll();
    public phone[] getList();
    public void delete(phone p);
    public void update(phone p);
}
 
(二)phone.class
//建一個物件用來存放資料庫的資料
public class phone {
public int id;
public String name;
public String tel;
public  String addr;
public  String email;
public  String tel2;
 
//為了不影響前面程式,要多寫幾個建構式(因為之前程式new物件的時候不用放參數)
public phone() {
}
 
 
//為了方便處理資料,加寫建構式,三個參數是用來新增物件用
public phone(String name, String tel, String addr) {
    this.name = name;
    this.tel = tel;
    this.addr = addr;
}
 
 
//四個參數是用來讀取資料用
public phone(int id, String name, String tel, String addr) {
    this.id = id;
    this.name = name;
    this.tel = tel;
    this.addr = addr;
}
}
 
 
(三) MyHelper.class
public class MyHelper extends SQLiteOpenHelper {
//本來建構式繼承有四個參數,但factory用不到就直接設定null
//資料庫名稱和版本則會直接設定final然後把這三個參數丟到super方法中
//這樣mainactivity要建立MyHelper物件時,就只要丟context這個參數進來就可以
static final String DB_NAME="student.sqlite";
static final int VERSION=4;
 
public MyHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
 
@Override //如果系統中沒有這個資料庫,就會跑onCreate
public void onCreate(SQLiteDatabase sqLiteDatabase) {
//先去SQLite Manager複製create語法,這邊有個技巧事先打好雙引號再貼上語法
//這樣會自動把需要加上反斜線的內容都加好反斜線
//在做版本更新時,除了onUpgrade要加sql語法,onCreate也要加,因為可能有用戶是沒有舊版就直接安裝新版的
String createSQL="CREATE TABLE \"phone\" (\"id\" INTEGER PRIMARY KEY NOT NULL ,\"name\" VARCHAR,\"tel\" VARCHAR,\"addr\" VARCHAR DEFAULT (null) , \"email\" VARCHAR, \"tel2\" VARCHAR)";
//sqLiteDatabase是從參數帶入的SQLiteDatabase
sqLiteDatabase.execSQL(createSQL);
}
 
@Override //如果系統中有這個資料庫且版本不同,就會跑onUpgrade
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldversion, int newversion) {
Log.d("verson","verson:"+oldversion);
//因為new這個物件時會帶入版本參數,這邊就要做版本的判斷來決定要做甚麼
if (oldversion != 4 )
{
String upgradeSQL = "ALTER TABLE \"main\".\"phone\" ADD COLUMN \"tel2\" VARCHAR";
sqLiteDatabase.execSQL(upgradeSQL);
}
}
}
 
(四)
//名稱取名為phoneDAO在DB上的實作(可以套用到雲端、檔案....)
public class phoneDAODBImpl implements phoneDAO {
SQLiteDatabase db;
MyHelper helper;
 
//因為在建立db時要取得mainactivity,所以要加一個建構是在new的時候可以把mainactivity傳進來
public phoneDAODBImpl(Context context) {
helper=new MyHelper(context);
db=helper.getWritableDatabase();
}
//刪除
@Override
public void clearAll()
{
SQLiteDatabase db = helper.getWritableDatabase();
//刪除的語法要用delete from phone,把整個table刪掉
db.execSQL("delete from phone");
db.close();
}
 
@Override
public phone[] getList() {
SQLiteDatabase db = helper.getWritableDatabase();
//建一個phone型別的arraylist
ArrayList<phone> mylist = new ArrayList();
//這邊因為要取全部的資料所以不要用where
Cursor c = db.query("phone", new String[] {"id","name","tel","addr"}, null, null, null, null, null);
if (c.moveToFirst())
{
do {
phone p = new phone();
p.id = c.getInt(0);
p.name = c.getString(1);
p.tel = c.getString(2);
p.addr = c.getString(3);
//把p的資料加到mylist中
mylist.add(p);
}while(c.moveToNext());
}
//把arraylist轉成array,而且要把這個和mylist資料長度一樣的array轉成phone型別
phone rValue[] = mylist.toArray(new phone[mylist.size()]);
return rValue;
}
 
@Override
public void addOne(phone p) {
db=helper.getWritableDatabase();
ContentValues cv = new ContentValues();
//寫入時,每個欄位資料都去對應phone 物件的各個field
cv.put("name",p.name);
cv.put("tel",p.tel);
cv.put("addr",p.addr);
cv.put("email",p.email);
cv.put("tel2",p.tel2);
db.insert("phone", null, cv);
db.close();
}
 
@Override
public phone getOne(int id) {
//要再取得一次database,因為如果先做addOne會把db關閉,這邊要再取得一次
db=helper.getWritableDatabase();
phone p=new phone();
//因為會帶入一個int id的參數,這邊第三第四個參數也要設定,先在第三個參數設定要如何篩選資料
// 第四個參數是把丟進來的int id轉成字串陣列的第一筆資料再丟給id(參數3)去篩選,這樣就會找到那一筆id的資料
Cursor c = db.query("phone",new String[] {"id","name","tel","addr","email","tel2"}, "id=?", new String[] {String.valueOf(id)}, null, null, null);
c.moveToFirst();
//設定在讀取時,phone class的每個field會去抓哪個欄位的資料
p.id=c.getInt(0);
p.name = c.getString(1);
p.tel = c.getString(2);
p.addr = c.getString(3);
p.email=c.getString(4);
p.tel2=c.getString(5);
db.close();
return p;
}
 
   //刪除
    @Override
    public void delete(phone p) {
        SQLiteDatabase db=helper.getWritableDatabase();
        //第二個參數是where clause,第三個參數是指定要刪除的那筆資料id
        db.delete("phone","id=?",new String[] {String.valueOf(p.id)});
        db.close();
    }
 
     //更新
    @Override
    public void update(phone p) {
        SQLiteDatabase db=helper.getWritableDatabase();
        ContentValues cv = new ContentValues();
        cv.put("name",p.name);
        cv.put("tel",p.tel);
        cv.put("addr",p.addr);
        cv.put("email",p.email);
        cv.put("tel2",p.tel2);
        // 第二個參數要更新的資料
        //第三個參數是要用甚麼篩選,第四個參數是要更新哪一筆資料
        db.update("phone",cv,"id=?",new String[] {String.valueOf(p.id)});
        db.close();
    }
}
 
(五)MainAcitivity.class
package com.example.student.lu101801;
 
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
 
public class MainActivity extends AppCompatActivity {
 
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
 
public void click1(View view) {
phone p=new phone();
p.name="kent";
p.tel="12345678";
p.tel2="87654321";
p.addr="abceddddd";
p.email="test2@eamil";
phoneDAODBImpl pi=new phoneDAODBImpl(MainActivity.this);
pi.addOne(p);
phone p1=pi.getOne(3);
 
Log.d("p1","p1"+p1.addr);
 
}
 
 

 

 

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明--推播機制

1.不分android或ios,推播是推給系統,是GOOGLE 推給系統,先把要推播的內容給APP的SERVER,APP的SERVER會告訴GOOGLE SERVER要推給誰,GOOGLER SERVER再推給系統,再把APP叫出來

2.推播一定要有server,我們這邊用firebase,原理和運作方式如下
=>當安裝好app(ex:line)的時候會註冊帳號,會跟google說要一個推播token,要註冊這隻手機需要一個推播token,google就會回傳token,app會收到token
=>把token加上user id 傳到line 的server去,line要用資料庫存下來
=>以後要發訊息的時候,就要用token+訊息+app金鑰(key=>在google developer console申請的)
=>看到key就會知道是哪個app,看到token就會知道是哪支手機,這樣google就會送訊息過來
**同一支手機,不同app,token會一樣,所以token是綁手機
 
3.用firebase console做發送就沒有token問題,送推播就是一次送所有的人

 

●程式說明:

1.tools=>firebase
FCM firebase cloud messaging
 
2.cloud messaging
FCM firebase cloud messaging
 
3.SET UP FIREBASE CLOUD MESSAGING=>CONNECT
FCM firebase cloud messaging
 
連結時會跳出選擇授權goolge帳號,再回android studio設定Firebase project name
 
FCM firebase cloud messaging
 
完成後右下角有出現這個訊息
FCM firebase cloud messaging
 
 
4.點Add FCM to your app. FCM(firebase cloud message)的library=>加入gradle並且sync
FCM firebase cloud messaging
 
5.app每次啟動時跟goolge要一個token,並且註冊到firebase
=>先new一個service:myfirebaseService
FCM firebase cloud messaging
 
6.繼承FirebaseMessagingService,下面IBinder可以刪除
public class MyFirebaseService extends FirebaseMessagingService{
public MyFirebaseService() {
}
}
 
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
 
7.加入onMessageReceived
public class MyFirebaseService extends FirebaseInstanceIdService {
public MyFirebaseService() {
}
 
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
    // ...
 
 
    // TODO(developer): Handle FCM messages here.
    // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
    Log.d(TAG, "From: " + remoteMessage.getFrom());
 
 
    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());
 
 
    }
 
 
    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
        Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }
 
 
    // Also if you intend on generating your own notifications as a result of a received FCM
    // message, here is where that should be initiated. See sendNotification method below.
}
}
 
8.manifest加intent filter(已可收推播)
<intent-filter>
    <action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
 
 
 
9.先離開app,到firebase找到該專案=>左側Cloud Messaging
FCM firebase cloud messaging
 
11.輸入訊息=>審查=>發布
FCM firebase cloud messaging
FCM firebase cloud messaging
 
12.在手機收到
FCM firebase cloud messaging
 
13.完成畫面
FCM firebase cloud messaging

 

●程式參考(GitHub):使用Google Firebase Cloud Messaging接收推播訊息 

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●畫面預覽:

Screenshot_20190711-111047.jpg

 

●程式說明:

admob註冊帳號=google帳號,台灣也有其他掛廣告的廠商(VPON威朋)
google admob
 
 
 
(一)直接使用admob activity
1.開啟android,直接開admob activity
google admob
 
2.改values/strings中的廣告id
google admob
 
 
3.到admob找廣告id=>在admob新增應用程式,建立廣告單元,會產生廣告id,貼回android
google admob
 
**注意不可以自己點廣告,在測試模擬的時候可以使用測試id,就會發送測試廣告不會發送真正的廣告
 
4.接著照導入說明做,下載admob sdk=>在google play service platform中有(在 android sdk manager中),再遵照sdk整合指南,修改xml和ad unit id
google admob
 
5.要再加上gradle,在文件get started處找gradle,要加兩項
 
google admob
google admob
 
 
(二)使用firbase
1.android firebase =>admob
google admob
 
2.連線到firebase,照著步驟做,加gradle
google admob
 
3.strings加(測試用id)
<string name="banner_ad_unit_id">ca-app-pub-ca-app-pub-3940256099942544/6300978111</string>
 
4.xml加
 
<com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentBottom="true"
ads:adSize="BANNER"
ads:adUnitId="@string/banner_ad_unit_id">
</com.google.android.gms.ads.AdView>
 
5.mainactivity改
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private AdView mAdView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
mAdView = (AdView) findViewById(R.id.adView);
AdRequest adRequest = new AdRequest.Builder().build();
mAdView.loadAd(adRequest);
}
}
**在模擬器是測試廣告,但實機會是真實廣告,會提醒妳要輸入測試序號(有範例程式碼),如果輸入測試序號就會發給妳測試廣告可以點

 

**有時候建立好廣告後,廣告並沒有正常顯示,看logcast可以看到一段訊息:Ads:Ad fail to load : 0,出現這段訊息代表你的程式沒有問題,是因為廣告單元剛建立google server還沒有立刻提供適合的廣告,要等一段時間(時間不定)就會正常顯示

google admob

 

●程式參考(GitHub):Google Admob--在自己的app中增加google廣告單元

文章標籤

muchone 發表在 痞客邦 留言(1) 人氣()

●程式說明:

1.開啟一個新的專案,可以直接開google map activity,title可以自己設定
 
2.開啟會跳出這個頁面,要取得google maps api key
GoogleMap
 
3.用模擬器測試時,會跳出google service要更新的訊息,然後按按鈕更新,但不要在這邊做更新會卡住
要到sdk manager=>勾選show package details=>下載google apis intel x86 atom system image
=>更新完以後要砍掉舊的模擬器,因為舊的模擬器是用舊版的模擬器影像檔做出來的
=>要重新安裝一次模擬器
=>目前版本不需要刪除模擬器就可以直接run
=>如果是用手機就可以直接更新
GoogleMap
 
4.用模擬器測試,左下角出現google icon代表google service更新成功
GoogleMap
 
5.解決api key的問題
a.複製網址貼到瀏覽器
GoogleMap
 
b.取得api key
 
API KEY
 
要建立憑證才能取得API金鑰
API KEY
c.把金鑰貼到google_maps_api.xml檔中,位置如下:
 
d.用模擬器跑可以出現地圖
Google Map
 
**注意上傳到github不要有金鑰資料
 
6.修改程式,在mainactivity中有如下程式碼:
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
 
    // Add a marker in Sydney and move the camera
    LatLng sydney = new LatLng(-34151);
    mMap.addMarker(new MarkerOptions().position(sydney)
    .title("Marker in Sydney"));
    mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
}
**說明:
1.onMapReady:地圖載完之後要做的事
2.addMarker:加mark
3.moveCamera:移動相機到指定位置
 
7.調整內容
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;
    LatLng TaipieEgg = new LatLng(25.0513481121.5486728);
    LatLng Hospital= new LatLng(25.0477974121.5486488);
    mMap.addMarker(new MarkerOptions().position(TaipieEgg).title("台北小巨蛋"));
    mMap.addMarker(new MarkerOptions().position(Hospital).title("台安醫院"));
    mMap.moveCamera(CameraUpdateFactory
   .newLatLngZoom(TaipieEgg,18));
}
**說明:
1.再加一個LatLng物件,多一個addMarker,可以同時顯示很多個marker
2.newLatLngZoom,可以設定放大倍率
3.可以查google maps api手冊中的MarkerOptions,裡面有很多方法可以用

 

*注意,在使用內建的Google Maps Activity可能會遇到下面這個問題

(出現訊息:inconvertible types; cannot cast 'android.support.v4.app.fragment' to 'com.google.android.gms.maps.SupportMapFragment' )

=>要把SupportFragmentManger轉為SupportMapFragment時,出現紅線顯示無法轉換type,這是因為在google paly-services-maps:17.0.0中,SupportMapFragmentnow 改為extends androidx.fragment.app.Fragment,而非android.support.v4.app.Fragment

Google Map

=>解決辦法是把play-services-maps改為16.0.1或是把專案整合為androidX版本,這邊是直接改play service版本

Google Map

 

●程式參考(GitHub):使用Android Studio內建Google Maps Activity建立Google Map

文章標籤

muchone 發表在 痞客邦 留言(1) 人氣()

●程式說明:
1.gradle要加:compile 'com.google.android.gms:play-services:11.0.4'
設定權限:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 
2.
 
public class MainActivity extends AppCompatActivity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {
 
GoogleApiClient mGoogleApiClient;
Location mLastLocation;
private String[] mPermissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION};
private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 100; //permission RequestCode
 
@Override
 
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
 
alertPermissions(this);
 
}
 
@Override
public void onConnected(@Nullable Bundle bundle) {
 
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
android.Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
}
 
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(
mGoogleApiClient);
if (mLastLocation != null)
{
// 取到手機地點後的程式碼
Toast.makeText(this,"GPS Location:" + mLastLocation.getLatitude() + "," + mLastLocation.getLongitude(),Toast.LENGTH_SHORT).show();
// Log.d("GPS", "Location:" + mLastLocation.getLatitude() + "," + mLastLocation.getLongitude());
}
}
 
@Override
public void onConnectionSuspended(int i) {
 
}
 
@Override
public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
 
}
 
 
public synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(getBaseContext())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
 
}

//***************************權限相關***********************************
 
//判斷sdk版本是否會彈跳出權限對話框
public boolean isPoupUpPermissions() {
//Build.VERSION.SDK_INT 取得目前sdk版本
//android 6.0=23 因為23以上才會跳出權限對話框
return Build.VERSION.SDK_INT >= 23;
}
 
//判斷是否已經有權限
public boolean hasPermissions(Context context, String permission) {
if (!isPoupUpPermissions()) { //sdk 23以下當作已經有取得權限
return true;
}
 
//確認權限是否為PERMISSION_GRANTED
if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
buildGoogleApiClient();
return true;
}
 
//請求權限(已擁有權限-PERMISSION_GRANTED ,無權限-PERMISSION_DENIED)
private void alertPermissions(Activity context) {
final ArrayList<String> unsatisfiedPermissions = new ArrayList<>(); //用來儲存未取得的權限
for (String permission : mPermissions) {
if (!hasPermissions(context, permission)) {
//如果某個permission檢查後為PackageManager.PERMISSION_DENIED,就加到unsatisfiedPermissions arrayListy中
unsatisfiedPermissions.add(permission);
}
 
}
 
//將unsatisfiedPermissions 轉成 string array,並且對array中的每個permission做request
 
if (unsatisfiedPermissions.size() != 0) {//當所有權限皆允許時unsatisfiedPermissions.size()=0 ,此時不需要requestPermissions
 
ActivityCompat.requestPermissions(context, unsatisfiedPermissions.toArray(new String[unsatisfiedPermissions.size()]), MY_PERMISSIONS_REQUEST_READ_CONTACTS);
 
}
}
}
 
 
●程式參考(GitHub):Android手機定位GPS(二)開啟APP取得手機現在GPS定位
文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●畫面預覽:
 
 
●程式說明:
1.設定權限
粗略定位權限:<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
精細定位權限:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 
2.
public class MainActivity extends AppCompatActivity implements LocationListener {
 
    LocationManager lm;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lm = (LocationManager) getSystemService(LOCATION_SERVICE);
    }
 
    public void click1(View view) {
 
//加了權限會跳出這段程式,這邊沒有寫可以強制開啟模擬器的權限設定        
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            
            return;
        }
        //四個參數:要用的定位來源、最短時間多久一次、最短距離多久一次、只要訂為一改變超過最短時間和最短距離就會呼叫listener
        lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
    }
 
    @Override //位置改變時
    public void onLocationChanged(Location location) {
        Log.d("GPS", "Location Change:" + location.getLongitude() + "," + location.getLatitude());
        Toast.makeText(this,"Location Change:" + location.getLongitude() + "," + location.getLatitude(),Toast.LENGTH_SHORT).show();
    }
 
    @Override
    public void onStatusChanged(String s, int i, Bundle bundle) {
 
    }
 
 
 
    @Override  //關閉定位
    public void onProviderEnabled(String s) {
 
    }
 
    @Override //開啟定位
    public void onProviderDisabled(String s) {
 
    }
 
}
**說明:
這個做在室外沒有問題,因為用gps定位多多少少會移動,但是在市內是用網路定位,沒有動就不會觸發此段程式
 
3.模擬器無法測試移動,可以點下列功能,send點一下就會改變位置,程式就可以抓到位置寫入log
get GPS location
 
文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明--sqlite
 
(一)使用sqllite(輕量化資料庫,有基本的新增修改刪除查詢功能)
=>library占空間很小,跟sql不一樣,所以iphone和android都有內建sqllite
=>當資料量大的時候就使用sqllite,資料量小就用json+文字檔
=>資料庫中可以存很多資料表
=>資料庫兩個概念
a.資料是一筆一筆的,不能像excel一樣跨行,ex:如果一個人同時有兩筆電話資料,不能做成兩行,而是要再設計的時候新增一個電話2的欄位
b.每一筆資料都是由很多欄位組成的,所以設計資料表要先設計欄位
 
(二)把資料庫放到android中有兩個方法
1.先用sqlite manager設計好資料庫,在放到專案中再把資料庫從專案複製到內部儲存空間
=>優點:簡單方便
=>缺點:當app升級時,如果修改欄位放到內部儲存空間,不能蓋過使用者原有資料,所以當有調整資料庫欄位時,就要用程式碼新增修改欄位,但如果使用者沒有跟著版本進度同步更新,就會有問題,變成又得在另外寫程式來處理這種狀況,否則使用者資料會損毀
2.在android程式執行時才建資料庫
 
(三)要把sqllite的檔案放到android中,再把它包在app專案中放到手機裡
=>sqllite是binary檔(打開看起來像亂碼),有原始資料,所以不希望檔案被改變
=>在android中有兩個地方可以存放又不會被改變:
1.res/assets資料夾(自己建)
2.res/raw資料夾(自己建)
=>但程式執行不能直接用,要再寫程式copy至手機的儲存空間才能用

 

●程式說明 --兩個copy資料的方法,和一個讀取sqlLite的內容,最後讓使用者輸入的資料可以加入資料庫

public class MainActivity extends AppCompatActivity {
EditText name,tel,address,et,updatename;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
name=(EditText)findViewById(R.id.name);
tel=(EditText)findViewById(R.id.tel);
address=(EditText)findViewById(R.id.address);
et=(EditText)findViewById(R.id.et);
updatename=(EditText)findViewById(R.id.updateName);
}
//copy sqllite檔案到手機方法一
//將database檔案放在raw下
public void copy(View view) {
//要取得專案中的resources,就要用getResources(),回傳Resources物件
//要抓raw中的資料,要用openRawResource,回傳InputStream物件,再用一個InputStream變數接住
InputStream is=getResources().openRawResource(R.raw.student);
try {
//new一個FileOutputStream物件(FileOutputStream是OutputStream的子類別)
//輸出串流有很多種,網路資料串流,文字資料串流...
OutputStream os=new FileOutputStream(getFilesDir()+ File.separator+"student.sqlite");
//從inputStream讀,從OutputStream寫出去
int i=0;
//每讀一個byte進來就寫一個byte,當i=-1時讀不到就結束
while(i!=-1){
i=is.read();
os.write(i);
}
is.close();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
 
} catch (IOException e) {
e.printStackTrace();
}
}
 
 
//copy sqlite檔案到手機方法二
//用java nio2的path(java7以後才有),用path或Files的method來做
public void copy2(View view) {
//因為path和Files要用api26以上測試,所以不能用android device monitor看檔案是否建立
//要用File+log來看
File f = new File(getFilesDir() + File.separator + "student2.sqlite");
//boolean exists ()
//Tests whether the file or directory denoted by this abstract pathname exists.
// return:true if and only if the file or directory denoted by this abstract pathname exists; false otherwise
//先偵測檔案是否存在,不存在才覆蓋檔案過去
if(!f.exists()){
InputStream is = getResources().openRawResource(R.raw.student);
//uri抓file的格式是file:///tmp/android.txt,而getFilesDir().getAbsolutePath()取出來前面會有一個斜線,所以file:後面只要兩條斜線
//create():URI create (String str)
//Creates a URI by parsing the given string.
URI uri = URI.create("file://" + getFilesDir().getAbsolutePath() + File.separator + "student2.sqlite");
//Path get (URI uri)
//Converts the given URI to a Path object.
Path p = Paths.get(uri);
try {
//long copy (InputStream in, Path target,CopyOption... options)
//Copies all bytes from an input stream to a file.
//CopyOption: options specifying how the copy should be done
//三個參數是inputStream,paht,常數
//StandardCopyOption:Defines the standard copy options.
//REPLACE_EXISTING:Replace an existing file if it exists.
Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
e.printStackTrace();
}
}
Log.d("FILE", String.valueOf(f.exists()));
}
 
 
//讀取sqlLite檔
public void clickRead(View view) {
String path = getFilesDir().getAbsolutePath() + File.separator + "student2.sqlite"; //這邊是讀取student2.sqlite
//宣告db是在抓資料庫student2的資料
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, 0);
//Cusor:This interface provides random read-write access to the result set returned by a database query.
//執行sql時,可以直接用execSQL,下SQL指令,或是用query,sqlLite內建的方法
//Query the given URL, returning a Cursor over the result set.有七個參數,後面五個可以用null,前兩個分別為table name,用字串陣列存的欄位名稱
//注意欄位名稱不可以和sql指令重複ex:add不可用
//宣告c是在抓資料庫裡一筆一筆的資料,cursor是代表指向第幾筆資料
Cursor c = db.query("phone", new String[] {"id", "name", "tel", "addr"}, null, null, null, null, null);
//因為cursor沒有設定的話會指向第一筆資料的上面空白出,所以設定移動到第一筆資料
c.moveToFirst();
//Log.d("COUNT", "NUMBER"+c.getCount());
StringBuilder sb=new StringBuilder();
do{
sb.append(String.valueOf(c.getInt(0)));
sb.append(":");
//Log.d("DB", String.valueOf(c.getInt(0)));//用log print id
for(int i=1;i<4;i++){
sb.append(c.getString(i)+",");
//Log.d("DB", c.getString(i)); //getString(i)取得第i筆的欄位資料
}
sb.append("\n");
et.setText(sb.toString());
//Log.d("DB", c.getString(1));
//moveToNext():Move the cursor to the next row.
}while (c.moveToNext());
 
}
 
 
//新增SQL lite資料
public void clicknew(View view) {
String path = getFilesDir().getAbsolutePath() + File.separator + "student2.sqlite";
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, 0);
//ContentValues:This class is used to store a set of values that the ContentResolver can process.
//ContentValues也是map的概念,put()兩個參數,key和value,key就是欄位名稱
ContentValues cv = new ContentValues();
cv.put("name",name.getText().toString());
cv.put("tel",tel.getText().toString());
cv.put("addr",address.getText().toString());
//long insert (String table, String nullColumnHack, ContentValues values)=>因為插入的內容必須是一個ContentValues,所以前面要先把ContentValues建好
//Convenience method for inserting a row into the database.
db.insert("phone", null, cv);
db.close();
 
}
 
//刪除table欄位資料
public void delete(View view) {
String path = getFilesDir().getAbsolutePath() + File.separator + "student2.sqlite";
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE);
/*delete
int delete (String table,String whereClause, String[] whereArgs)
Convenience method for deleting rows in the database.
參數說明:
1.table name
2.要如何篩選資料,寫法:id=? 代表要把id都是?的資料都找出來,同時對應到下個參數為指定數值的資料
=>如果有兩個不同欄位寫法為id=? and tel=? ,同時陣列的參數就要變成new String[] {"2","2"},指定不同欄位的第幾筆資料
=>如果同一個欄位要刪除多筆資料寫法範例:id IN (?,?,?)
3.這個參數是陣列型態*/
//注意三四個參數絕對不能NULL,否則會把所有的資料全部刪除
db.delete("phone", "id IN (?,?)", new String[] {"2","5"}); //這邊刪除第二和第五筆資料
db.close();
}
 
 
//更新table欄位資料
public void update(View view) {
String path = getFilesDir().getAbsolutePath() + File.separator + "student2.sqlite";
SQLiteDatabase db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE);
ContentValues cv = new ContentValues();
cv.put("name", updatename.getText().toString());
//int update (String table, ContentValues values, String whereClause, String[] whereArgs)
//Convenience method for updating rows in the database.
//參數說明:1.資料表名稱 2.要更新的資料
//注意三四個參數絕對不能NULL,否則會把所有的資料全部更新
db.update("phone", cv, "id=?", new String[] {"1"}); //這邊更新第一筆資料的name欄位
db.close();
}
}
 
 
 
文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明--加密

(一)HASH(雜湊):不能解密,不能反算,單向加密,用在數位簽章和密碼雜湊
1.MD5
2.SHA1
 
(二)明文,雙向的,可以解密
1.RSA:非對稱式加密法(速度慢)
=>用A(公)金鑰加密,只能用B(私)金鑰解密,用B金鑰加密,只能用A金鑰解密
=>先用公鑰加密,把公鑰和資料傳過去,私鑰留著,收到者再用私鑰解密
=>SSL加密:先建立RSA加密連線,公鑰傳過去,對方把DES加密金鑰丟回,再用私鑰解開得到對方的DES加密金鑰
=>因為SSL加密駭客會用騙公鑰的做法,假造公鑰攔截丟過去在繼續後面傳輸動作,所以後來都把公鑰放在第三方公正機構(要收費),所以有些網站像是學校或是一些政府單會,會跳出簽章不可信任的訊息,因為沒有經費
 
2.DES對稱式加密法(速度快),建議專案使用
=>A金鑰加密,A金鑰解密

 

●程式說明--AES加密(代替DES,做完加密以後,還要把程式混亂,可以使用proguard反編譯工具)

1.先在build.gradle中加入compile 'com.scottyab:aescrypt:0.0.1'
2.程式如下:
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
          
        String password = "password";
        String message = "TESTTESTTEST";
        try {
            //加密  
            String encryptedMsg = AESCrypt.encrypt(password, message);
            Log.d("ENC", encryptedMsg);
        }catch (GeneralSecurityException e){
            //handle error
        }
        // dT4GAbl/CWOzjueYjSB5WA==
        String encryptedMsg = "dT4GAbl/CWOzjueYjSB5WA==";
        try {
            //解密
            String messageAfterDecrypt = AESCrypt.decrypt(password, encryptedMsg);
            Log.d("ENC", messageAfterDecrypt);
        }catch (GeneralSecurityException e){
            //handle error - could be due to incorrect password or tampered encryptedMsg
        }
 
    }
}
=>加密後可以用LOG看到密碼為第一行,解密就可以得到原始輸入密碼
android aes加密
=>可以把加密後的密碼存在文字檔,要用再讀出來解密

 

●程式參考(GitHub):Android AES加密(使用com.scottyab:aescrypt)

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●程式說明--多重執行緒補充

=>run倒數計時

(一)conuntDownTimer=>非多重執行緒,是在主執行緒做
public class MainActivity extends AppCompatActivity {
    TextView mTextField;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextField=(TextView)findViewById(R.id.mTextField);
        //每秒鐘執行一次,總共執行30CountDownTimer,都是在主執行緒執行
        new CountDownTimer(300001000) {
           //倒數時要run的程式
            public void onTick(long millisUntilFinished) {
                mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
            }
            //結束後要run的程式
            public void onFinish() {
                mTextField.setText("done!");
            }
        }.start();
    }
}
**說明:
1.CountDownTimer (long millisInFuture, long countDownInterval)
=>Parameters
millisInFuture long: The number of millis in the future from the call to start() until the countdown is done and onFinish() is called.
countDownInterval long: The interval along the way to receive onTick(long) callbacks.
 
=>問題:因為onTick和onFinsish都是在主執行緒上跑,如果在這兩個method中跑很花時間的程式,還是會讓程式卡住ex,如果再onTick中加入,就會跑一下跳過兩秒,29=>26=>23....程式被卡住了
try {
    Thread.sleep(2000);
catch (InterruptedException e) {
    e.printStackTrace();
}
 
(二)使用runOnUi
public void runonui(View view) {
        new Thread(){
            int 5;
            @Override
            public void run() {
                super.run();
                do {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            tv2.setText(String.valueOf(i));
                        }
                    });
                    i--;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } while (i>=0);
            }
        }.start();
    }
}
 
(三)使用Handler
public class MainActivity extends AppCompatActivity {
    TextView tv3;
    Handler handler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv3=(TextView)findViewById(R.id.tv3);
handler=new Handler();
}
public void onhandler(View view) {
new Thread(){
int i = 5;
@Override
public void run() {
     super.run();
do {
     handler.post(new Runnable() {
     @Override
     public void run() {
          tv3.setText(String.valueOf(i));
     }
     });
i--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} while (i>0);
}
}.start();
}
}
**說明:
1.handler是android才有,java沒有的class
2.handler的method會在宣告handler的執行緒上run
3.這裡在主執行緒上宣告handler物件,在其他執行緒上用handler.post(~),就會在主執行緒上執行post中的內容
4.實際上run的方法則是和runOnUi一樣,但使用上比較彈性,不限定只能在主執行緒上跑
 
(四)AsynTask=>處理執行緒和ui的機制,有六大function
=>因為是抽象類別而且有抽象方法,所以要繼承和override
=>繼承的時候,可以看到有三個泛型參數
1.執行 AsynTask要傳甚麼類型的資料(傳入的型別)
2.回報進度的型別
3.結果的型別(ex:如果是抓網路文字資料就是字串,如果是抓網路圖片就是bitmap)
 
//建立一個內部類別繼承AsynTask
class MyAsynTask extends AsyncTask<Integer,Integer,String>{
    //因為要使用MainActivity的tv4但是不要設定static,就利用建構式把tv4傳進來
    TextView tv;
    public MyAsynTask(TextView tv){
        this.tv=tv;
    }
 
 
    //override doInBackground方法=>在背景執行
    //這邊的參數類型會和繼承時決定的傳入型別一樣,...是參數列表與陣列類似,可以一個或多個參數
    //ex:abc(int [ ] x):void; 或 abc(int... x):void;
    @Override
    protected String doInBackground(Integer... integers) {
        int n = integers[0];
        int i;
        for(i=n;i>=0;i--)
        {
            Log.d("TASK", "i:" + i);
            //publishProgress(Progress... values): to publish updates on the UI thread while the background computation is still running
            //參數:values Progress: The progress values to update the UI with.
            publishProgress(i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //Returns true if this task was cancelled before it completed normally
            if (this.isCancelled() == true)
            {
                break;
            }
        }
        return "OK";
    }
 
 
    @Override
    //在執行之後
    //會在doInBackground執行完之後才執行,而且是在主執行緒執行,並且doInBackground return的內容會丟到這邊的參數帶入
    //所以看log這邊會顯示ok
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        Log.d("TASK", s);
    }
 
 
    @Override
    //進度更新,也是在主執行緒跑
    //這邊的Integer...values是MyAsynTask<>第二個帶入的參數類型,由publishProgerss()傳值過來
    //必須在 doInBackground加上publishProgress();
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        tv.setText(String.valueOf(values[0]));
    }
 
 
    @Override //當呼叫cancel method,會run onCancelled()
    /*onCancelled
    Applications should preferably override onCancelled(Object). This method is invoked by the default implementation of onCancelled(Object).
    Runs on the UI thread after cancel(boolean) is invoked and doInBackground(Object[]) has finished.*/
    protected void onCancelled() {
        super.onCancelled();
 
 
    }
}

 

 

●程式參考(GitHub):多重執行續的補充說明(CountDownTimer、Handler、runOnUiThread、AsynTask)

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●程式說明

(一)Firebase Realtime Database

=>使用雲端資料庫交換資料

=>先連結google帳號和firebase

 
1.開新專案=>tools=>firebase
firebase_realtime_database
 
2.右邊開啟視窗,選Realtime Database
firebase_realtime_database
 
3.點Save and retrieve data,第一步先connect to Firebase,完成後會變成綠色的
firebase_realtime_database
 
用project模式看,會發現app下面自動產生了一個google-service.json檔,裡面存了金鑰
firebase_realtime_database
 
4.第二步點add the realtime database to your app,會自動幫你做好設定
firebase_realtime_database
 
選擇是否要在firebase新建專案 或是使用舊專案
firebase_realtime_database
 
5.第三步,回firebase看到新建的專案,點進去=>database=>開始使用=>規則,會看到如下內容
=>"auth != null"代表驗證是空的就不行讀取,沒驗證的話不能讀取
=>把內容改成read:true,write:true
=>發佈
firebase_realtime_database
 
=>規則公開,任何人都可以讀取,開放資料庫讓app讀取
firebase_realtime_database
 
(二)回到android程式
public class MainActivity extends AppCompatActivity {
TextView tv;
EditText et;
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef;
 
@Override.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv=(TextView)findViewById(R.id.tv);
et=(EditText) findViewById(R.id.et);
}
 
public void write(View view) {
myRef = database.getReference("message");
myRef.setValue(et.getText().toString());
}
 
public void read(View view) {myRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
String value = dataSnapshot.getValue(String.class);
tv.setText(value);
 
}
 
@Override
public void onCancelled(DatabaseError error) {
// Failed to read value
//Log.w(TAG, "Failed to read value.", error.toException());
}
});
}
}
**說明:
1.FirebaseDatabase database = FirebaseDatabase.getInstance();=>連結資料庫
2. myRef = database.getReference("message");=>連結資料,資料名稱為message
3.myRef.setValue=>把message資料寫入
4.寫入完成可以去firebase看資料,可以看到message中有value
5.讀取時,增加一個值的監聽器myRef.addValueEventListener ,隨時監聽資料有無改變,當值一改變就觸發下面的程式,因為是放在click裡面,所以只要點一次按鈕,就會一直持續監聽,如果要取消監聽,可以用removelistener,但是remove的話要把監聽做成一個物件,才能使用
 
=>資料可以有很多reference,大量資料可以存成jason來用
 
 
(三)全域變數如果要同時給很多個activity使用,有兩種做法
1.直接在mainactivity宣告:public static String 變數
2.宣告一個類別來繼承application,在這個類別中宣告public 變數,不需要static
firebase_realtime_database
public class MyApplication extends Application {
    public String str;
}
 
並且在AndroidManifest.xml中設定application去抓myapplication,這邊的設定是告訴android要呼叫哪一個class來當最底層的application,所以我們要把最底層的系統application改成我們的application
<application
    android:name=".MyApplication"
    ...
</application>
 
在要使用變數的activity中加入下列程式,因為在啟動程式時,會先啟動一個最底層的application物件,然後mainactivity會放在上面,而getApplication是AppCompatActivity中的方法,可以取得這個activity最底層的application物件並回傳,但因為我們要使用我們自己寫的MyApplication,所以要轉型為我們的類別,這樣就可以直接透過.str使用str變數
MyApplication app = (MyApplication) getApplication();
app.str = ...(省略)

 

 

 

●程式參考(GitHub):透過Android Studio內建機制使用FireBase Realtime Database

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●程式說明

(一)使用ImageRequest=>需要七個參數:url、成功要做甚麼、最大寬、最大高、 scaletype、顏色、失敗做甚麼
 
RequestQueue queue= Volley.newRequestQueue(MainActivity.this);
ImageRequest request1=new ImageRequest("http://images.parents.mdpcdn.com/sites/parents.com/files/styles/width_300/public/images/p_101395269.jpg",
        new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                ImageView iv1=(ImageView)findViewById(R.id.iv1);
                iv1.setImageBitmap(response);
            }
        }, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.RGB_565
        , new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
    }
});
queue.add(request1);
 
**說明:
1.要先拉ImageView
2.最大寬高設為0的時候,代表不設定寬高限制
3.顏色通常都用RGB_565

 

(二)使用picasso
=>好處是會作暫存,如果都從同一個網站下載圖片,以新聞網為例,可能幾分鐘後又去看一次新聞,picasso會自動偵測暫存,如果有資料就不會再次下載,但使用ImageRequest則會重複下載,看一次就會下載一次
=>加build.gradle compile 'com.squareup.picasso:picasso:2.5.2'
 
ImageView iv2=(ImageView)findViewById(R.id.iv2);
Picasso.with(this)
        .load("https://thumb1.shutterstock.com/display_pic_with_logo/3138983/499778260/stock-vector-cartoon-ladybug-vector-illustration-cute-red-ladybug-isolated-in-a-flat-style-499778260.jpg")
        .into(iv2);

 

**上述兩種做法的缺點:當要載入的圖片很大時,不會出現進度條,要等全部載完才會有反應,但可以使用轉圈圈的方式

 

●程式參考(GitHub):使用volley ImageRequest和Picasso下載圖片資源

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明​​

(一)用volley來寫網路的存取程式

=>volley是google官方作的,且為google官方推薦的第三方元件,已經把抓資料的細節都包好了

=>使用volley有四個步驟

1.建立queue(佇列)=>排隊

2.建立request(要求)=>去存取一個網路資源的要求,執行第二個步驟時,要丟三個參數

=>

a.url

b.成功了要做甚麼=>這是在主執行緒上做,所以可以動到ui

c.失敗了要做甚麼

3.把2加入1(讓request排隊)

4. 1.start();(叫隊伍開始動)

=>做之前要先設定權限

<uses-permission android:name="android.permission.INTERNET"/>
 
=>在build.gradle(android建置系統的設定檔)的dependencies中加入下面內容
dependencies {
   ....
    compile 'com.android.volley:volley:1.0.0'     
}
**說明:
這個做法就可以直接外掛這個library進來,完成後點上面的sync now,此時網路不能斷,因為會去抓library
 
(二)openData的概念:政府利用人民的稅金收集到很多資料,應該要去識別化(和隱私脫鉤),讓人民能夠很方便的使用這些資料
 
(三)使用openData做的app可以賣錢也可以掛廣告,行政院的態度是不能賣raw data,但是可以加值資料(ex變成圖片),就可以賣錢,但部分下屬單位如高公局的即時車速就規定不能賣錢
 
(四)JSON:javascript轉出來的物件標註格式
=>這邊以新北市政府動物之家的資料為例,做json之前要先看資料格式
=>[]代表陣列,{}代表物件
=>可以看到這個資料是一整個陣列,裡面包了很多物件
=>district、address這些是屬性,八里、八里區長坑里...這些是內容
Snap4.png

 

●程式說明

(ㄧ)Volley的部分

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RequestQueue queue= Volley.newRequestQueue(MainActivity.this);
        StringRequest request=new StringRequest("http://google.com.tw", new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.d("Net",response);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.d("NET",error.toString());
            }
        });
        queue.add(request);
        //queue.start();
    }
}
 
**說明:
1.因為queue只能有一個,所以不能new一個RequestQueu,而是透過 Volley.newRequestQueue來取得queue
=>這是一個Singleton/single pattern的作法:當有一些系統上的資源,你希望那個物件在系統上只有一份,唯一的一份不要有第二份,就不能讓它new,要做一個static method,利用這個靜態method傳回你要傳回的物件
2. StringRequest我們這邊用三個參數的版本(還有四個參數的版本),上網抓到的字串就會放在 String response中
3.如果要抓圖片可以用ImageRequest
4.要注意的是,最後不需要使用queue.start(),因為Volley在初始化RequestQueu時已經call過start()了,如果在呼叫一次會出現錯誤訊息:com.android.volley.NoConnectionError: java.io.InterruptedIOException

 

(二)Json的部分可以分為兩種做法

作法一:使用JSONArray和JSONObject

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RequestQueue queue= Volley.newRequestQueue(MainActivity.this);
        StringRequest request=new StringRequest("http://data.ntpc.gov.tw/od/data/api/BF90FA7E-C358-4CDA-B579-B6C84ADC96A1?$format=json", new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                try {
                    JSONArray array=new JSONArray(response);
                    for (int i=0;i<array.length();i++){
                        JSONObject obj=array.getJSONObject(i);
                        Log.d("Net","district:"+obj.getString("district"));
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.d("NET",error.toString());
            }
        });
        queue.add(request);
    }
}
 
**說明
1.把抓下來的jSON資料放在response中,就可以轉出來變成json陣列
2.用迴圈去跑每一個array的物件取出來,然後用log顯示該署性的內容(因為要用getString,所以不能用foreach)
3.因為這份資料是用array包裹物件,所以是用JSONArray 去轉,如果是用物件包裹array,就要用 JSONObject去轉(依資料格式來決定)

 

作法二:利用gson函式庫(https://github.com/google/gson) 也是google的

=>一樣要在build.gradle中掛library
compile 'com.google.code.gson:gson:2.8.2'
=>看raw data的資料有四個屬性,要先寫一個類別加上四個屬性,內容必須和資料中完全一樣
 
自建類別:AnimalHome.java
public class AnimalHome {
public String district;
public String address;
public String tel;
public String opening_hours;
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RequestQueue queue= Volley.newRequestQueue(MainActivity.this);
        StringRequest request=new StringRequest("http://data.ntpc.gov.tw/od/data/api/BF90FA7E-C358-4CDA-B579-B6C84ADC96A1?$format=json", new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Gson gson=new Gson();
                AnimalHome[] ah;
                ah=gson.fromJson(response,AnimalHome[].class);
                for(AnimalHome a:ah){
                    Log.d("net",a.district);
                    Log.d("net",a.address);
                }
                String str = gson.toJson(ah);
                Log.d("NET", str);
                ArrayList<AnimalHome> list1 = new ArrayList<>();
                for (AnimalHome a : ah)
                {
                    list1.add(a);
                }
                String str2 = gson.toJson(list1);
                Log.d("NET", str2);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                Log.d("NET",error.toString());
            }
        });
        queue.add(request);
    }
}
**說明:
1.起一個gson物件
2.因為要用泛型,所以先建立我們自訂類別AnimalHome的陣列
3.用fromJson方法看下圖,第一個參數是json的字串內容,第二個參數是泛型類別(T就是Type),也就是要轉出來的型別,這邊我們要轉成我們自訂的類別,寫法為
AnimalHome[].class,把json的每一個屬性放進class中(即把json字串內容--resopnse轉成自訂的類別的陣列)
volley
4.透過foreach迴圈,抓陣列每個屬性的值
5.如果要把陣列或arraylist或是string再轉回jason,就用 gson.toJson
6.測試,建立一個新的arraylist <AnimalHome>,把ah中的每一筆資料透過foreach迴圈放到新建的arraylist中,再用 gson.toJson轉成JSON
=>當資料大概幾百筆,且不需要很多搜尋和篩選,就可以用此做法把ArrayList存成JSON格式(文字檔),隨時可以讀出來再轉成Arrayloist(非常簡單的存檔作法,修改刪除只需要用add和remove就可以,不需要動用到資料庫)
=>當資料量大且需要很多搜尋和篩選時,才需要使用資料庫

 

●用volley抓網路資料可能會遇到的問題(以抓聯合新聞的rss為例)
1.先授權<uses-permission android:name="android.permission.INTERNET"/>
2.build.gradle加入compile 'com.android.volley:volley:1.0.0'
3. 如果直接用之前的做法會發現抓回來的資料都是亂碼
RequestQueue queue= Volley.newRequestQueue(MainActivity.this);
StringRequest request=new StringRequest("https://udn.com/rssfeed/news/2/6638?ch=news",
        new Response.Listener<String>() {
            @Override
            public void onResponse(String response) {
                Log.d("NET", response);
            }
        }, new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
 
    }
});
queue.add(request);
volley
 
=>這是因為聯合新聞網沒有在xml的開頭準確的標示編碼要用utf8,所以volley不知道要用utf8編碼去處理
=>有些網站有這個問題還不會出現亂碼,而是直接沒有反應,這樣就會很難查
 
=>做法:自己寫一個utf8StringRequest class繼承StringRequest
 
public class UTF8StringRequest extends StringRequest {
public UTF8StringRequest(int method, String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
super(method, url, listener, errorListener);
}
public UTF8StringRequest(String url, Response.Listener<String> listener, Response.ErrorListener errorListener) {
super(url, listener, errorListener);
}
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response) {
 
String utf8String = null;
try {
utf8String = new String(response.data, "UTF-8");
return Response.success(utf8String, HttpHeaderParser.parseCacheHeaders(response));
 
} catch (UnsupportedEncodingException e) {
return Response.error(new ParseError(e));
}
}
}
**說明:
1.utf8String = new String(response.data, "UTF-8");
這邊的response.data是一個byte array,要用UTF-8的方法轉成字串
2.再把MainActivity.java的
StringRequest request=new StringRequest 改成 StringRequest request=new UTF8StringRequest
 
 

●程式參考(GitHub):

實作透過Volley、JSONArray、JSONObject、GSON取得Open Data資料

客製化UTF8StringRequest

 

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●程式說明

=>把抓到的rss新聞標題用listview的方法呈現出來

=>點擊每一個listview就要抓到它的link連出去(丟到下一頁,載入webview)

=>再寫一個arraylist不是很好的做法,比較好的做法是寫一個類別來存rss的資料,或是用MAP/SimpleAdaper來做
 
(一)先自訂一個類別存放rss資料:RSSNewsItem=>這邊不用get和set,只放兩個field
public class RSSNewsItem {
    public String title;
    public String link;
}

 

(二)MyDataHandler

public class MyDataHandler extends DefaultHandler {
    boolean isTitle = false;
    boolean isItem =false;
    boolean isLink = false;
    public ArrayList<RSSNewsItem> data = new ArrayList<>();
    RSSNewsItem item;
 
 
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        super.startElement(uri, localName, qName, attributes);
        if (qName.equals("title"))
        {
            isTitle = true;
        }
        if (qName.equals("item"))
        {
            isItem = true;
            item = new RSSNewsItem();
        }
        if (qName.equals("link"))
        {
            isLink = true;
        }
 
 
    }
 
 
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        super.endElement(uri, localName, qName);
        if (qName.equals("title"))
        {
            isTitle = false;
        }
        if (qName.equals("item"))
        {
            isItem = false;
            data.add(item);
        }
        if (qName.equals("link"))
        {
            isLink = false;
        }
    }
 
 
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        super.characters(ch, start, length);
        if (isItem && isTitle)
        {
            item.title = new String(ch, start, length);
            //Log.d("NET", new String(ch, start, length));
        }
        if (isItem && isLink)
        {
            item.link = new String(ch, start, length);
            // Log.d("NET", new String(ch, start, length));
        }
    }
}
 
**說明:
1.public ArrayList<RSSNewsItem> data new ArrayList<>();=>使用泛型來儲存 RSSNewsItem 物件
RSSNewsItem item;=>設定item為 RSSNewsItem物件
2.但抓回來資料會發現第一個頁面標題也被抓進來了,我們只需要在item tag中的標題,所以多一個判斷變數isItem=>當tag開始為item為true,並且最後判斷要isItem和isTitle都是true才抓標題資料,boolean isLink false; =>用來判斷link tag的開頭結尾
2.在判斷item開頭結尾的時候,如果是開頭,就立刻new一個 RSSNewsItem物件,如果是結尾,就把這個物件加到data這個arraylist中
3.增加isLink的判斷
4.最後判斷如果是在item中而且是title中的內容,就把資料存進item.title中; 如果是在item中而且是link中的內容,就把資料存進item.link中
 

(三)MainActivity.java

public class MainActivity extends AppCompatActivity {
    ListView lv;
    ArrayAdapter<String> adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv=(ListView)findViewById(R.id.listview);
    }
 
 
    public void click(View view) {
        new Thread(){
            @Override
            public void run()
            {
                URL url = null;
                try {
                    url = new URL("https://udn.com/rssfeed/news/2/6638?ch=news");
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setRequestMethod("GET");
                    conn.connect();
                    InputStream inputStream;
                    inputStream = conn.getInputStream();
 
 
                    BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
                    final StringBuilder sb = new StringBuilder();
                    String str;
                    while ((str = br.readLine()) != null)
                    {
                        sb.append(str);
                    }
                    String result = sb.toString();
                    final MyDataHandler dataHandler = new MyDataHandler();
                    SAXParserFactory spf = SAXParserFactory.newInstance();
                    SAXParser sp = spf.newSAXParser();
                    XMLReader xr = sp.getXMLReader();
                    xr.setContentHandler(dataHandler);
                    xr.parse(new InputSource(new StringReader(result)));
 
 
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            ArrayList<String> forAdaper=new ArrayList<String>();
                            for (RSSNewsItem i:dataHandler.data){
                                forAdaper.add(i.title);
                            }
                            adapter=new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_item_1,forAdaper);
                            lv.setAdapter(adapter);
                            lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                                @Override
                                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                                    Intent in=new Intent(MainActivity.this,Detail.class);
                                    in.putExtra("link",dataHandler.data.get(i).link);
                                    startActivity(in);
                                }
                            });
                        }
                    });
 
 
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (ProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (ParserConfigurationException e) {
                    e.printStackTrace();
                } catch (SAXException e) {
                    e.printStackTrace();
                }
            }
 
 
        }.start();
    }
 
**說明:
1.前面都和抓rss作法一樣,但因為listview是在主執行緒,只有建立ui的執行緒才能使用它,要回ui thread,就要使用 runOnUiThread
2.透過arrayadapter把arraylist的資料丟給listview
3.因為listview只需要放title資料不需要放link資料,而且現在data這個arraylist中存放的是RSSNewsItem物件,所以這邊要再建一個 ArrayList<String> forAdapter,用來存放從data中抓出來的title資料
2.透過foreach迴圈,把data中的每一個title加到forAdapter中
3. new ArrayAdapter<String>時的資料來源就要改為forAdapter
4.設定OnItemClick監聽,並啟動intent
5.要丟過去的資料是 dataHandler.data.get(i).link=>data這個arraylist中的第i項物件,因為是 RSSNewsItem物件,所以可以用.link取得這個物件的link資料
 
 
(四)Detail.java
public class Detail extends AppCompatActivity {
WebView wv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
wv=(WebView)findViewById(R.id.wv);
wv.getSettings().setJavaScriptEnabled(true);
wv.setWebChromeClient(new WebChromeClient());
Intent in=getIntent();
String url=in.getStringExtra("link");
wv.loadUrl(url);
}
**說明:
1.設定webview=>javascript和 WebChromeClient
2.setWebChromeClient()void setWebChromeClient (WebChromeClient client)
=>Sets the chrome handler. This is an implementation of WebChromeClient for use in handling JavaScript dialogs, favicons, titles, and the progress. This will replace the current handler.
=>Parameters
client WebChromeClient: an implementation of WebChromeClient
3.用getIntet取得前面丟過來的intent,再用 getStringExtra取得前面丟過來的link資料,把link放到loadUrl中

 

●程式參考(GitHub):實作讀取新聞網站的RSS

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●畫面預覽

=>點擊SAX按鈕後,下方出現Udn RSS新聞列表

Screenshot_2019-06-14-10-42-37.png

 

●補充說明

(一)RSS的讀取(讀取新聞網站的RSS)
=>RSS是XML格式,android讀進來以後要再用XML的解析工具解開,然後再從中找出我們要的內容
=>RSS一般是要使用RSS閱讀器,指定RSS位置,就可以看到標題和簡短的內容(不能看全文是因為要導引到網站,商業考量)
=>資料交換的格式有:CSV(用,分開)、XML、JSON
=>在JAVA中解XML有兩種方法
 
1.XML DOM Parser=>xml有結構,作法是把整份文件讀下來再解成整顆樹,是一個樹狀資料結構,再用樹的walkthrough方法,從樹的根開始一條條走,用樹的軸覽方法把資料讀取出來
Rss Reader
 
 
2.SAX:讀到元件或文字就會跳出相對應的事件出來(我們自己寫的程式),一步一步做
=>一樣要先開起權限<uses-permission android:name="android.permission.INTERNET"/>
(MainActivity)
public void click(View view) {
new Thread(){
@Override
public void run()
{
URL url = null;
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
InputStream inputStream;
inputStream = conn.getInputStream();
 
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder sb = new StringBuilder();
String str;
while ((str = br.readLine()) != null)
{
sb.append(str);
}
String result = sb.toString();
MyDataHandler dataHandler = new MyDataHandler();
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
xr.setContentHandler(dataHandler);
xr.parse(new InputSource(new StringReader(result)));
 
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
}
}.start();
}
**說明:
1.sax這幾行的作法是固定的,可自己研究內容
2.setContentHandler(dataHandler),設定xml讀取器為我們的class
3. xr.parse(new InputSource(new StringReader(result)));這邊把rss資料載完
 
(MyDataHandler)=>要自己寫一個class,繼承DefaultHandler(繼承的時候要選org.xml這一個繼承),並override三個method
Rss Reader
public class MyDataHandler extends DefaultHandler {
boolean isTitle = false;
boolean isDes=false;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (qName.equals("title"))
{
isTitle = true;
}
}
 
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if (qName.equals("title"))
{
isTitle = false;
}
}
 
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
if (isTitle)
{
Log.d("NET", new String(ch, start, length));
}
}
}
**說明:
擷取.PNG
=>每一個item的結構都是item中有title、link、pubDate、descritpion、guid
1.這邊的qName就是tag的名稱(title)
2.最後把整個字元陣列ch取出來轉成字串,同時要取start和length才不會有問題
3.characters():void characters (char[] ch, int start, int length)
=>Receive notification of character data inside an element.
=>By default, do nothing. Application writers may override this method to take specific actions for each chunk of character data
(such as adding the data to a node or buffer, or printing it to a file).
=>Parameters
ch-char: The characters.
start-int: The start position in the character array.
length-int: The number of characters to use from the character array.

 

 

(二)RSS xml的取得

=>以udn新聞網為例,選取上方RSS,再到下方選定要Feed的rss類別

Snap1.png

=>開啟RSS XML 的URL位址

Rss Reader

 

 

●程式參考(GitHub):實作讀取新聞網站的RSS

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明和程式說明--存取網路資源
=>先開啟網路權限
<uses-permission android:name="android.permission.INTERNET" />
 
(一)程式流程:當button按下時,抓取google資料,並用log讀出來
public void click1(View view) {
new Thread(){
@Override
public void run()
{
try {
URL url = new URL("http://google.com.tw");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String str;
while ((str=br.readLine()) != null)
{
sb.append(str);
}
String result = sb.toString();
Log.d("mynet","read:"+result);
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
**說明:
1.android規定,抓取網路資料要在不同的執行緒,所以要用Thread&override run method
2.起一個URL物件(網址)=>這邊要用try...catch
 
3.建立連線:HttpURLConnection=>A URLConnection with support for HTTP-specific features.
Obtain a new HttpURLConnection by calling URL.openConnection() and casting the result to HttpURLConnection.(用openConnection取得的是OpenConnection物件,要轉成HttpURLConnection物件)
4.openConnection():Returns a URLConnection instance that represents a connection to the remote object referred to by the URL.
5.setRequestMethod(String method):Set the method for the URL request,ex:GET,POST,設定資料傳送方法,一般上網抓資料或看網頁都是用GET,填寫表單傳送是用POST
6.連線 :connect():Opens a communications link to the resource referenced by this URL, if such a connection has not already been established.
7.URLConnection.getInputStream:InputStream getInputStream=>Returns an input stream that reads from this open connection.
=>取到連線物件後,只提供轉成輸入串流的方法
 
8.透過InputStream(位元組串流)產生InputStreamReader物件,An InputStreamReader is a bridge from byte streams to character streams: It reads bytes and decodes them into characters using a specified charset
9.BufferedReader的建構式是BufferedReader(Reader in),而InputStreamReade和FileReader都繼承Reader,所以都可以用來new BufferedReader=>把InputStreamReader轉為BufferedReader,即把網路資料轉成String,這樣就可以使用readLine()
10.最後就是透過while把讀取到的每一行資料放到StringBuilder中,再轉成string,用log顯示,最後就可以看到google網頁的資料如下:
 
(二)抓取台銀日幣賣出的即期匯率
=>利用String下的indexOf()和substring()來做

Snap1.png

=>indexOf
1.public int indexOf(int ch)
Returns the index within this string of the first occurrence of the specified character. If a character with value ch occurs in the character sequence represented by this String object, then the index (in Unicode code units) of the first such occurrence is returned.
2.public int indexOf(int ch,int fromIndex)
Returns the index within this string of the first occurrence of the specified character, starting the search at the specified index.
 
=>substring
public String substring(int beginIndex,int endIndex)=>(起始點,終止點的下一個)
Returns a new string that is a substring of this string. The substring begins at the specified beginIndex and extends to the character at index endIndex - 1. Thus the length of the substring is endIndex-beginIndex.
public void click1(View view) {
new Thread(){
@Override
public void run()
{
try {
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.connect();
InputStream is = conn.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String str;
while ((str=br.readLine()) != null)
{
sb.append(str);
}
String result = sb.toString();
int loc1 = result.indexOf("0.2805");
Log.d("LOC1", "loc1:" + loc1);
int loc2 = result.indexOf("日圓 (JPY)");
Log.d("LOC2", "loc2:" + loc2);
int loc3 = result.indexOf("本行現金賣出", loc2);
Log.d("LOC3", "loc3:" + loc3);
 
int locJPY = loc3 + 56;
String jpy = result.substring(locJPY, locJPY + 6);
Log.d("LOC", "JPY:" + jpy);
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
**說明:
1.前面和抓googl一樣,只是網址換成台銀匯率網頁
2.不能直接用indexof抓匯率數字的位置,因為當網頁調整增加一行或甚至只是整數去0,都會影響位置的改變,所以要用別的方法
3.先用while迴圈把所有文字抓近來,再來定位JPY,做法是先找日圓=>再找本行現金賣出=>再推算位置
4.測試(用LOG)0.2805這個匯率的位置(loc1)為27339,如果indexof出來結果為-1,就代表沒讀到資料,可能是因為匯率改變要重查
5.再測試日圓 (JPY)的位置(loc2)為26985,測試從loc2的位置開始找"本行現金賣出"的位置(loc3)為27386(因為每個匯率都有"本行現金賣出",要找日圓後面的)
6.如此就可以計算書從"本行現金賣出"到"日圓即期匯率"的位置距離為loc1-loc3=56(loc3+56),這個是固定的不會被改變
7.因為匯率0.2805總共6個字元,所以當使用substring取字時,就要從locJPY開始取,到(locJPY+6)-1這六個字元
=>上述做法就不用擔心位置會跑掉
 
**結果:

Snap3.png

 

●程式參考(GitHub):利用HttpURLConnection、InputStream、BufferReader存取網路資源

 

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明和程式說明
(一)讀寫外部空間資料--在規劃好的資料夾中
1.寫入
=>用android device moniter看檔案位置會在如下圖的地方,這裡不需要使用權限,但當別的程式有完整讀取sd卡的權限時,就可以讀取到這邊的檔案,隱私性安全較差
讀寫外部資料與設定讀寫外部資料權限
public void outWrite(View view) {
    String path = getExternalFilesDir("file").getAbsolutePath();
    String fname=path + File.separator "test1.txt";
    try {
        FileWriter fw = new FileWriter(fname);
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write("happy to find you");
        bw.newLine();
        bw.write("it is sad");
        bw.close();
        fw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
**說明:
1. 取得外部存檔的資料夾位置:getExternalFilesDir(檔案類型):參數的檔案類型可以自訂
=>File getExternalFilesDir (String type)
=>Returns absolute paths to application-specific directories on all shared/external storage devices where the application can place persistent files it owns.
2.後面做法都跟之前(BufferReader and BufferWriter)一樣
 
2.讀取
public void outRead(View view) {
String path = getExternalFilesDir("file").getAbsolutePath();
String fname=path + File.separator + "test1.txt";
try {
FileReader fr = new FileReader(fname);
BufferedReader br = new BufferedReader(fr);
String str;
while ((str=br.readLine()) != null)
{
Log.d("FNAME", "Read:" + str);
}
br.close();
fr.close();
 
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
 
(二)讀寫外部空間資料(自由的尋覽sd卡)
=>但現在android不建議如此做,基於隱私的考量以及避免在外部空間製造很多垃圾
=>先在AndroidManifest.xml設定權限(只要想要存取規劃的資料夾外的資料都要設定權限)
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
1.android6(api<23)以前的寫入
File f = Environment.getExternalStorageDirectory();
Log.d("FNAME","read"+f.getAbsolutePath());
File f2 = new File(f.getAbsolutePath() 
              + File.separator "mydata");
f2.mkdir();
File txtFile = new File(f2.getAbsolutePath() 
                 + File.separator "data5.txt");
try {
    FileWriter fw = new FileWriter(txtFile);
    BufferedWriter bw = new BufferedWriter(fw);
    bw.write("this is test");
    bw.newLine();
    bw.write("This is test2");
    bw.close();
    fw.close();
catch (IOException e) {
    e.printStackTrace();
}
**說明
1.Environment.getExternalStorageDirectory():取得sd卡路徑
=>File getExternalStorageDirectory ()
=>Return the primary shared/external storage directory.
=>前面兩筆是使用getExternalFilesDir("file").getAbsolutePath();取得的路徑=>是android指定的不需要授權的資料夾
=>後面一筆是使用Environment.getExternalStorageDirectory().getAbsolutePath();取得的路徑
 讀寫外部資料與設定讀寫外部資料權限
 
2.mkdir()=>Creates the directory named by this abstract pathname.(建立資料夾mydata)
3.建立資料夾內容
4.android6以前的權限設定如上即可,使用早期版本的模擬器測試,只要有上面權限設定,寫入後就會在sdcard資料夾中產生我們要的資料夾
讀寫外部資料與設定讀寫外部資料權限
 
 
2.android6(api<23)以前的讀取
File f = Environment.getExternalStorageDirectory();
Log.d("FNAME", f.getAbsolutePath());
File f2 = new File(f.getAbsolutePath() 
           + File.separator "mydata");
File txtFile = new File(f2.getAbsolutePath() 
          + File.separator "data5.txt");
try {
    FileReader fr = new FileReader(txtFile);
    BufferedReader br = new BufferedReader(fr);
    String str;
    while ((str=br.readLine()) != null)
    {
        Log.d("FNAME""Read:" + str);
    }
    br.close();
    fr.close();
 
catch (FileNotFoundException e) {
    e.printStackTrace();
catch (IOException e) {
    e.printStackTrace();
}
**說明:
1.當使用android6以後的模擬器,只在AndroidManifest.xml設定權限會不夠,用android6以後的模擬器測試這段讀取程式,就會出現下列exception:FileNotFoundException,因為在android6以後的模擬器寫入不成功,所以也讀不到檔案
讀寫外部資料與設定讀寫外部資料權限
2.做法有兩種一種是讓使用者自己設定,在手機=>設定=>app設定=>app permisssions=>storage permissions=>我們app開啟讀寫外部資料與設定讀寫外部資料權限
但這是不好的做法,應該是把設定寫在程式裡
 
3.android6以後的寫入與讀取
=>程式的流程應該是在每次要使用時,都會檢查程式是否有被授予權限,如果沒有就要跳出視窗詢問使用者是否授予權限,且這個跳出的視窗是android訂好的規格,我們不能改變或自訂
=>使用者決定允許或拒絕後,callback結果
=>參考檔案可以發現WRITE和READ是同一類權限,有開WRITE則READ就會有開
=>實際上讀取應該也要加權限,否則若是還沒有授權寫入或是被取消授權後直接按讀取就會有問題
 public void nolimitwrite(View view) {
    int permission = ActivityCompat.checkSelfPermission(this,
            WRITE_EXTERNAL_STORAGE);
    if (permission != PackageManager.PERMISSION_GRANTED) {
        //未取得權限,向使用者要求允許權
       //因為要辨別使用者允許的是寫入還是讀取所以這邊的string只放write
        ActivityCompat.requestPermissions(this, new String[]{WRITE_EXTERNAL_STORAGE},
                REQUEST_EXTERNAL_STORAGE); 
    }else{
        //已有權限,可進行檔案存取
        sdwrite();

    }
}

@Override
public void onRequestPermissionsResult
    (int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(requestCode==REQUEST_EXTERNAL_STORAGE){
        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            //取得權限,進行檔案存取
            String str=permissions[0];
             //這邊用if判斷時要用equals(),不能用==
            if(str.equals("android.permission.WRITE_EXTERNAL_STORAGE") ){  
                sdwrite();
            }else {
                sdread();
            }

        } else {
            //使用者拒絕權限,停用檔案存取功能
        }
    }
}

private void sdwrite(){
    File f = Environment.getExternalStorageDirectory();
    Log.d("FNAME","read"+f.getAbsolutePath());
    File f2 = new File(f.getAbsolutePath() + File.separator + "mydata");
    f2.mkdir();
    File txtFile = new File(f2.getAbsolutePath() + File.separator + "data5.txt");
    try {
        FileWriter fw = new FileWriter(txtFile);
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write("this is test");
        bw.newLine();
        bw.write("This is test2");
        bw.close();
        fw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void nolimitread(View view) {
    int permission = ActivityCompat.checkSelfPermission(this,
            READ_EXTERNAL_STORAGE);
    if (permission != PackageManager.PERMISSION_GRANTED) {
        //未取得權限,向使用者要求允許權
        ActivityCompat.requestPermissions(this, new String[]{READ_EXTERNAL_STORAGE},
                REQUEST_EXTERNAL_STORAGE);
    }else{
        //已有權限,可進行檔案存取
        sdread();
    }
}

private void sdread(){
    File f = Environment.getExternalStorageDirectory();
    Log.d("FNAME", f.getAbsolutePath());
    File f2 = new File(f.getAbsolutePath() + File.separator + "mydata");
    File txtFile = new File(f2.getAbsolutePath() + File.separator + "data5.txt");
    try {
        FileReader fr = new FileReader(txtFile);
        BufferedReader br = new BufferedReader(fr);
        String str;
        while ((str=br.readLine()) != null)
        {
            Log.d("FNAME", "Read:" + str);
        }
        br.close();
        fr.close();

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
**說明
1.先檢查是否已經取得使用者的授權,檢查權限語法:int checkSelfPermission(Context context, String permission)
=>Determine whether you have been granted a particular permission.
=>String: The name of the permission being checked.
=>return int:PERMISSION_GRANTED if you have the permission, or PERMISSION_DENIED if not.
=>PackageManager.PERMISSION_GRANTED
int PERMISSION_GRANTED
Permission check result: this is returned by checkPermission(String, String) if the permission has been granted to the given package.
Constant Value: 0 (0x00000000)
=>PackageManager.PERMISSION_DENIED
int PERMISSION_DENIED
Permission check result: this is returned by checkPermission(String, String) if the permission has not been granted to the given package.
Constant Value: -1 (0xffffffff)
 
2.如果沒有取得權限,跳出允許授權對話框
=>requestPermissions:void requestPermissions (Activity activity, String[] permissions, int requestCode)
=>第一個參數是context,第二個參數是要求允許的權限,第三個參數是本次請求的辨識編碼(因為callback時要辨認是哪裡callback的)
=>因為後面要辨別這邊帶入的權限是write還是read,所以兩個click放的參數不同
 
3.把寫入和讀取的程式寫成兩個method,當授權ok或是已經有授權的時候直接呼叫這兩個方法
 
4.當跳出授權對話框後,對使用者的確認與取消授權做處理,用onRequestPermissionsResult
=>void onRequestPermissionsResult (int requestCode, String[] permissions, int[] grantResults)
Callback for the result from requesting permissions. 
=>requestCodeint: The request code passed in requestPermissions(android.app.Activity, String[], int)
=>permissionsString: The requested permissions. Never null.
=>grantResultsint: The grant results for the corresponding permissions which is either PERMISSION_GRANTED or PERMISSION_DENIED. Never null.
 
5.先確認requestCode正確,再確認授權結果必須為GRANTED,再判斷傳回的permission為哪一個,來決定下一步是要寫入還是讀取,這邊的permissions陣列判斷不可以用==而是要用equals,因為在前面有new String,是不同物件不能用==,==兩邊必須是相同的參考物件,相關資訊可以參考說明:https://ppt.cc/fam5fx
 
6.如果想要查詢pemissions字串中的內容,可以用log看
Log.d("permission","read:"+permissions[0]);
讀寫外部資料與設定讀寫外部資料權限
 

 

●程式參考(GitHub):讀寫外部資料與設定讀寫外部資料權限

 

文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明和程式說明

(一) 檔案的讀取使用FileReader比較不好用,因為讀出來會是字元陣列,一般會使用BufferedReader和BufferedWriter=>可以直接處理字串

(二)BufferedReader和BufferedWriter
public void write(View view) {
    String path = getFilesDir().getAbsolutePath();
    File fname = new File(path + File.separator "test.txt");
    try {
        FileWriter fw = new FileWriter(fname);
        BufferedWriter bw = new BufferedWriter(fw);
        bw.write("Hello World");
        bw.newLine();
        bw.write("This is android");
        bw.close();
        fw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
**說明:
1.BufferedWriter要透過FileWriter來建立,所以要先建立FileWriter物件
2..newLine()是換行
3.關閉兩個檔案
 
 
public void read(View view) {
String path = getFilesDir().getAbsolutePath();
String fname=path + File.separator + "test1.txt";
try {
FileReader fr = new FileReader(fname);
BufferedReader br = new BufferedReader(fr);
String str;
while ((str=br.readLine()) != null)
{
Log.d("FNAME", "Read:" + str);
}
br.close();
fr.close();
 
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
**說明:
1.BufferedReader要透過FileReader來建立,所以要先建立FileReader物件
2..readLine()只讀取一行,把讀出來的內容存入字串str,如果讀不出來會是null,因為資料不是只有一行,而且有空行,所以可以用while來判斷,只要讀到的不是null就把資料寫入log中,就可以抓取所有資料
3.最後要記得關閉兩個檔案
 
(三)擺放資料的方法,要如何把整數陣列寫入資料並讀取
1.寫入
public void write2(View view) {
int[] data = {4,1,7,3,4,8,2,1};
StringBuilder sb = new StringBuilder();
for (int i : data)
{
sb.append(i + ",");
}
String path = getFilesDir().getAbsolutePath();
File fname = new File(path + File.separator + "test2.txt");
try {
FileWriter fw = new FileWriter(fname);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(sb.toString());
bw.close();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
 
 
}
**說明:
1.要利用StringBuilder,所以先建立StringBuilder物件
2.透過foreach迴圈,把i+","放入StringBuilder,此時sb已經成為整數+,的組合了
3.寫入時再把sb用toString();轉成字串寫入即可
 
2.讀取
public void read2(View view) {
    String path = getFilesDir().getAbsolutePath();
    File fname = new File(path + File.separator "test2.txt");
    ArrayList<Integer> mylist = new ArrayList();
    try {
        FileReader fr = new FileReader(fname);
        BufferedReader br = new BufferedReader(fr);
        String str;
        str = br.readLine();
        String strdata[] = str.split(",");
        for (String s : strdata)
        {
            if (s.length() > 0)
            {
                mylist.add(Integer.valueOf(s));
            }
        }
        br.close();
        fr.close();
 
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    for (int i : mylist)
    {
        Log.d("FNAME""data:" + i);
    }
}
**說明:
1.讀取時只要讀一次,要先建立一個arraylist來放資料陣列
2.把讀取進來的資料存入str中,因為前面寫入時每筆資料後面都有逗號,再用split()方法以逗號分割每筆資料放入陣列中
=>public String[] split(String regex)
=>Splits this string around matches of the given regular expression
=>Returns:the array of strings computed by splitting this string around matches of the given regular expression
3.因為最後一個逗號後面沒有資料,所以如果用,做區隔,最後會多一個空白,所以當跑foreach迴圈時,要判斷s.length>0
=>字串的長度=>public int length():Returns the length of this string. The length is equal to the number of Unicode code units in the string.
4.用foreach迴圈把每一個值轉成整數放入arraylist中
5.valueOf:Integer valueOf (String s)=>Returns an Integer object holding the value of the specified String.
=>把字串轉成Integer物件 
6.再用foreach迴圈配合log,看每一筆arraylist的資料內容
 
(四)如果資料存放在物件中(如自己做了一個類別存放資料),這種讀取方式後面會講
 
 
文章標籤

muchone 發表在 痞客邦 留言(0) 人氣()

●補充說明和程式說明---檔案存取

(一)在做檔案存取時
1.在本機下的data/data/packagename這個資料夾,會在install的時候產生,移除的時候刪除,這個資料夾下的所有內容可以不受限制任意存取
2.android 6以前,sdcard/data/packagename下的資料夾,跟上面一樣, 會在install的時候產生,移除的時候刪除,這個資料夾下的所有內容可以不受限制任意存取
3. android 6以後,sdcard下的資料夾只要app被授權,就可以任意存取sd卡中的內容
 
(二)android寫入
public void clickwrite(View view) {
 
String fname=getFilesDir().getAbsolutePath();
try {
FileWriter fw=new FileWriter(fname+ File.separator+"data.txt");
fw.write("android is fun");
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
**說明:
1. getFilesDir()和getCacheDir(),兩個不同資料夾(File和Cache),file放重要的資料,cache放暫時使用的資料,在手機設定裡面的app中,有兩個按鈕,clear data是清除file中的內容,clear cache是清除cache中的內容
2.用 getFilesDir().getAbsolutePath(); 取得內部儲存空間file資料夾名稱和絕對路徑位置
3.建立一個寫入資料的物件,寫入的資料檔案路徑和名稱為 fname+ File.separator+"data.txt"=> File.separator是分隔線, data.txt  是檔名
4.fw.write(寫入的內容)
 
設定/app/專案名稱
fileWriter and fileReader
完成後可以看到檔案成功寫入,可以匯出桌面看內容
fileWriter and fileReader
 
(三)android讀取
public void clickread(View view) throws FileNotFoundException {
    char [] ch=new char[100];
    String fname=getFilesDir().getAbsolutePath();
    try {
        FileReader fr=new FileReader(fname+File.separator+"data.txt");
        fr.read(ch);
        String str=new String(ch);
        Toast.makeText(this,str,Toast.LENGTH_SHORT).show();
    }catch (FileNotFoundException e){
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }
**說明:
1.因為 FileReader讀進來的是一個char array,所以要先建一個char array來儲存之
2.一樣要先抓到要讀取的檔案
3.read()=>int read (byte[] b) :Reads some number of bytes from the input stream and stores them into the buffer array b.
4. String str=new String(ch);=>把ch轉成String
=>在String類別中有一個建構式:String(char[] value)
=>Allocates a new String so that it represents the sequence of characters currently contained in the character array argument.
 

 

 

●程式參考(GitHub):使用FileWriter和FileReader做檔案(File)的讀取與寫入

muchone 發表在 痞客邦 留言(0) 人氣()

1 23